UMD modules in a Chrome Extension - javascript

The UMD module definition is approximately this:
(function (root, factory) {
if (typeof define === 'function' && define.amd) {
// AMD. Register as an anonymous module.
define(['exports', 'b'], factory);
} else if (typeof exports === 'object' && typeof exports.nodeName !== 'string') {
// CommonJS
factory(exports, require('b'));
} else {
// Browser globals
factory((root.commonJsStrict = {}), root.b);
}
}(this, function (exports, b) {
//use b in some fashion.
// attach properties to the exports object to define
// the exported module properties.
exports.action = function () {};
}));
The issue is that Chrome Extensions don't support any of these methods of exporting the module:
define doesn't exist
exports doesn't exist
this isn't bound to window
For this reason, it seems that UMD modules fail in Chrome Extension environments. Is there any workaround to get a UMD module to correctly export into the window object in a Chrome Extension?

As #wOxxOm has correctly pointed out, the Chrome Extension environment is the same as the browser, and this is indeed bound to window, so UMD modules can and should work with extensions.
It turns out the actual problem is that babel was producing a bundle with this replaced by undefined, which is the problem outlined and resolved in this issue: How to stop babel from transpiling 'this' to 'undefined' (and inserting "use strict").

Related

Implementing require in the absence of node

Modules generally start something like this
(function(root, factory)
{
/* globals define */
if (typeof define === 'function' && define.amd)
{
// AMD. Register as an anonymous module.
define([], factory);
}
else if (typeof module === 'object' && typeof exports !== 'undefined')
{
// Node. Does not work with strict CommonJS, but
// only CommonJS-like environments that support module.exports,
// like Node.
module.exports = factory();
}
else
{
// Browser globals (root is window)
root.Papa = factory();
}
I'm trying to implement require to handle node style CommonJS-like modules.
Find the package folder, parse package.json to learn the entrypoint and dependencies, recursively descent with a shared cache to load dependencies... that stuff works.
But having loaded the script for a module, I'm having trouble executing it in such a way as to have it populate module.exports
This will all end up running on Jint (a JS interpreter) so node isn't supplying the usual furniture, I have to build it all myself. There's no step-debug with Jint so I'm using node from VS Code and faking Jint like this.
import * as fs from "fs";
var code = fs.readFileSync("node_modules/papaparse/papaparse.js").toString();
let x = 3;
console.log(eval("x*x"))
let result = eval(`
let module = { exports: "dunno" };
${code}
console.log(module.exports);
`);
This is in a file test.js and package.json nominates this file as main and specifies a type of module. It launches, reads the module code, creates module and runs the code, which promptly complains that it Cannot set properties of undefined (setting 'Papa').
Looking at the snippet above, that tells us it's executing the last else clause, which means it's not seeing module. I thought it might be some sort of scope thing for eval which is where this came from
let x = 3;
console.log(eval("x*x"))
but that duly writes 9 to the console so the question is why module isn't in scope.
This is one of those JavaScript closure weirdnesses; can anyone guide me on how to provide the module object so that the second clause will take effect populating module.exports with the result of factory()?
I know I said it's running in the absence of Node, but I'm debugging it using Node mostly because that's what you get when you launch a js file in VS Code. As already mentioned the production target is Jint. The debug environment is as close an approximation as I can manage, and I'm refraining from using facilities that won't be available in production.
In the debug environment I've created a package.json file that governs whether it's treated as CommonJS or ES6. Experiments show that the production environment behaves like more ES6 than commonjs. The trouble is that most packages expect that they will run either in a browser or in node. They don't consider the possibility of another headless environment. The code above decides it's browser hosted and tries to to install Papa in a DOM that isn't there.
The solution is to wrap the module like so (I'm constructing a string in C#).
string wrapped =
#"(function () {
var module = { exports: { } }; " +
jsSource + #"
var strays = Object.keys(this).filter(itemName => itemName !== 'module');
if (strays.length === 1)
module.exports = this[strays[0]]
else
strays.forEach(itemName => module.exports[itemName] = this[itemName]);
return module.exports;
}).apply({});";
Wrapping it in an anonymous function and using apply to set this for the call allows the "DOM write" to succeed, and a little follow-up code looks for these strays, gathering them up into module exports before returning to normal operation.

Webpack imports-loader with messageformat and angular-translate

I'm having some difficulty making angular-translate-interpolation-messageformat play nice with the imports-loader for MessageFormat. I outline the issue in this issue.
Copied:
Even though the module is exposed using UMD (yay) it is actually using the global MessageFormat object here. This is forcing me to either expose MessageFormat onto window (which I would prefer not to do), or make a workaround with webpack (which is also cumbersome). The real solution is to use UMD correctly and not depend on globals but rather require things correctly.
Here's what the UMD format looks like now:
(function (root, factory) {
if (typeof define === 'function' && define.amd) {
// AMD. Register as an anonymous module unless amdModuleId is set
define([], function () {
return (factory());
});
} else if (typeof exports === 'object') {
// Node. Does not work with strict CommonJS, but
// only CommonJS-like environments that support module.exports,
// like Node.
module.exports = factory();
} else {
factory();
}
}(this, function () {
// interpolation-messageformat code that uses the global MessageFormat variable
}));
Here's what it should look like:
(function (root, factory) {
if (typeof define === 'function' && define.amd) {
// AMD. Register as an anonymous module unless amdModuleId is set
define(['messageformat'], function (MessageFormat) { // <-- changed line
return (factory(MessageFormat)); // <-- changed line
});
} else if (typeof exports === 'object') {
// Node. Does not work with strict CommonJS, but
// only CommonJS-like environments that support module.exports,
// like Node.
module.exports = factory(require('messageformat')); // <-- changed line
} else {
factory(root.MessageFormat); // <-- changed line
}
}(this, function (MessageFormat) { // <-- changed line
// interpolation-messageformat code that uses the global MessageFormat variable
}));
Thanks!
So, until that issue gets resolved, I need to do a workaround. I'd really prefer to avoid globals. Here's my current solution with the imports-loader:
require('imports?MessageFormat=messageformat!angular-translate/dist/angular-translate-interpolation-messageformat/angular-translate-interpolation-messageformat');
With that, everything builds fine, however, when I open Chrome, the app breaks when running the angular-translate-interpolation-messageformat function that uses MessageFormat saying that MessageFormat is not defined
Here's where things get weird...
If I open the app in any other browser (other than Chrome) it works just fine. Also, if I open the app when it's deployed, it works fine (even in Chrome).
Here's where things get weirder...
If I open my Chrome DevTools and then open the local app in Chrome, everything works just fine. o_O
So, anyway, I'm wondering if maybe I'm using the imports-loader incorrectly or something. Any help appreciated!
Are you using by any chance devtool: 'eval'? I've seen that same weird behavior you describe, and it went away switching to devtool: 'source-map'.

Javascript pattern for library with dependencies

Sometimes I've come across a particular pattern present in some javascript libraries. Maybe it's a coincidence but I've seen it in libraries with dependencies. The syntax is as follows (The sample is taken from Backbone which has a hard dependency on underscore)
(function(root, factory) {
if (typeof define === 'function' && define.amd) {
define(['underscore', 'jquery', 'exports'], function(_, $, exports) {
root.Backbone = factory(root, exports, _, $);
});
} else if (typeof exports !== 'undefined') {
var _ = require('underscore');
factory(root, exports, _);
} else {
root.Backbone = factory(root, {}, root._, (root.jQuery || root.Zepto || root.ender || root.$));
}
}(this, function(root, Backbone, _, $)
Can someone explain why this pattern is used. The parts I don't understand very well is the use of the factory variable, why is testing for the property define.amd and why exports is loaded as a dependency in define(['underscore', 'jquery', 'exports'].
I'm familiar with AMD modules but seeing this makes me wonder if I should use the same pattern if I'm writting a library with a dependency or this pattern should be used everytime even if I have no dependencies at all.
The gist
This is called the universal module definition pattern. It comes in countless variations. The core of it is that JavaScript has no builtin module system (up until ES6 modules become widely adopted) and there are numerous of these out there to fill the gap (e.g. requirejs, yepnope, labjs). So the UMD is a pattern that has been conceived in order for your modules to support module systems in multiple environments, since JavaScript is now being used almost everywhere.
Your example
In your example you will be supporting script loaders that understand the Asynchronous Module Definition, CommonJS module loaders like the one that NodeJS introduced as well as plain browsers or other environments that don't expose a module system that the definition is aware of:
(function(root, factory) {
if (typeof define === 'function' && define.amd) {
// This branch is to support AMD loaders
define(['underscore', 'jquery', 'exports'], function(_, $, exports) {
root.Backbone = factory(root, exports, _, $);
});
} else if (typeof exports !== 'undefined') {
// This supports the CommonJS module system that NodeJS uses
var _ = require('underscore');
factory(root, exports, _);
} else {
// Attaching your library root in an environment that has no
// module system or a system you don't support like a plain
// browser before ES6 modules become the standard
root.Backbone = /* ... */
}
}(this, function(root, Backbone, _, $)
With this boiler plate code you can write your module once and at the same time it can be used with different loaders/build tools in the browser, on the server and everywhere else you may find a JavaScript environment.
I suggest you read up on JavaScript modules/module systems to get a deeper insight. Addy osmani's blog is also a good place to start. There are dozens of resources on the topic.
Hope this helps! Happy coding.

Grunt test for UMD

I'm trying to figure out the best approach to testing a Javascript module definition using a UMD factory, similar to this: https://github.com/umdjs/umd/blob/master/returnExportsGlobal.js
I don't want to test the module itself, I want to test that the module is 'exported/created' correctly in the various environments:
If CommonJS (node), is the module exported correctly?
If AMD, is it defined correctly?
If browser (without requirejs), is the correct global created?
I would like to run these tests using grunt and jasmine. I can use grunt-contrib-jasmine to test for points 2 and 3, but not for point 1.
I guess I can use a concoction of grunt-contrib-jasmine and grunt-jasmine-node to test for correct module definitions (specific implementation i'd still need to figure out), but it feels very messy.
At a high level, does anyone know of any existing methods to achieving this without using multiple grunt plugins?
In the end, i decided to go with the hybrid tasks, using grunt-contrib-jasmine for browser global and browser AMD tests, and jasmine_node for CommonJS testing. I only have one spec
file which supports all 3 module type tests.
Here's my grunt config:
grunt.initConfig({
pkg: grunt.file.readJSON('package.json'),
jasmine: {
browserGlobal: {
src: ['src/Foo.js'],
options: {
specs: 'spec/**/*.spec.js'
}
},
browserAMD: {
src: ['src/Foo.js'],
options: {
specs: 'spec/**/*.spec.js',
template: require('grunt-template-jasmine-requirejs')
}
}
},
jasmine_node: {
specNameMatcher: 'spec',
projectRoot: 'spec/'
}
});
My jasmine spec files now support UMD:
(function (root, factory) {
if (typeof module === 'object' && module.exports) {
// Node/CommonJS
factory(
require('modulename')
);
} else if (typeof define === 'function' && define.amd) {
// AMD
define([
'modulename'
], factory);
} else {
// Browser globals
factory(root.ModuleName);
}
}(this, function factory(ModuleName) {
// Tests here
}));
And here's the UMD factory i'm using for my module:
(function (root, factory) {
if (typeof module === 'object' && module.exports) {
// Node/CommonJS
module.exports = factory();
} else if (typeof define === 'function' && define.amd) {
// AMD. Register as an anonymous module.
define(factory);
} else {
// Browser globals
root.ModuleName = factory();
}
}(this, function factory() {
// public API
return {
foo: 'bar'
};
}));
You can also use uRequire and save your self from all the UMD boilerplate in all your modules, while using declarative features.
You simply write plain AMD or plain CommonJS modules (or a mix of two) and convert to UMD (or an rjs optimized combined.js that runs as-is on nodejs, Web/AMD & Web/Script) with a simple build step and config, either in CLI or in grunt.
The UMD produced is based on well known templates like https://github.com/umdjs/umd/blob/master/templates/returnExportsGlobal.js, with various tweaks, one of them being that you can declaratively export to window/global.
You can then convert you plain AMD or commonJS specs to UMD and/or 'combined.js' and hit both in a browser or grunt-mocha. See uBerscore for many trivial and more advanced examples.

How to use node modules with in UIAutomation

According to apple's documentation I can import one JS file into another with an import statement. And yes I am able to use JS functions and recursively call other JS functions.
But can I include node modules into my automation. Node/npm module seems to have a lot of tools that makes life easier and avoid code duplication.
And actually I was able to use one node module called moment.js through the following call in my code
#import "../node_modules/moment/moment.js"
But I am not have the same luck with other npm modules. I tried couple Faker.js, Charlatan.js and I getting the following error in Faker.js
Script threw an uncaught JavaScript error: Can't find variable: window
on line 618 of Faker.js
Looking at *.js files it looks like it has something to do with the way these modules are packaged. My JS knowledge isn't getting me anywhere.
The last few lines of moment js file
// CommonJS module is defined
if (hasModule) {
module.exports = moment;
}
/*global ender:false */
if (typeof ender === 'undefined') {
// here, `this` means `window` in the browser, or `global` on the server
// add `moment` as a global object via a string identifier,
// for Closure Compiler "advanced" mode
this['moment'] = moment;
}
/*global define:false */
if (typeof define === "function" && define.amd) {
define("moment", [], function () {
return moment;
});
}
Last few lines of Faker js file
if (typeof define == 'function'){
define(function(){
return Faker;
});
}
else if(typeof module !== 'undefined' && module.exports) {
module.exports = Faker;
}
else {
window.Faker = Faker;
}
I am perfectly able to play with these modules in node console, so nothing wrong with the modules, it just how to include/require them in my JS files.
Had to do two things for Faker to work for me
remove 'use strict'
Check if window is undefined
Add this statement
this['Faker'] = Faker;

Categories