Using UMD modules in the browser environment - javascript

I am building a website and am using es6 modules in development which I need to transpile into a more browser compatible format.
One option seems to be UMD - Universal Module Definition, which states it runs in the browser and other environments.
I am using Rollup and Babel to handle the transpilation.
I don't fully understand how UMD works, and therefore don't fully understand how widely in the browsers this will work.
The point that concerns me / confuses me most in the output, is the following two lines:
exports.PrintGreeting = PrintGreeting;
Object.defineProperty(exports, '__esModule', { value: true });
I do not understand exports or the Object method.
It would be good if somebody more knowledgable could offer up a more detailed explanation of how this works but most importantly my main reason for writing this question is -
'Is this cross browser compatible?'
and
'Is this a good way to go for production env code?'
I have included my dev code, transpilation code and umd output code below.
Thank you in advance for your help.
My es6 module:
export function PrintGreeting() {
console.log('hello world')
}
My rollup file:
import babel from "rollup-plugin-babel";
export default {
input: "main.js",
output: {
file: "dist/bundle.js",
format: "umd",
name: "Entry"
},
plugins: [
babel({
exclude: "node_modules/**"
})
]
};
My output js (UMD) file:
(function (global, factory) {
typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports) :
typeof define === 'function' && define.amd ? define(['exports'], factory) :
(global = global || self, factory(global.Entry = {}));
}(this, (function (exports) { 'use strict';
function PrintGreeting() {
console.log('hello world');
}
exports.PrintGreeting = PrintGreeting;
Object.defineProperty(exports, '__esModule', { value: true });
})));

Related

UMD modules in a Chrome Extension

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").

Why can't my electron app access the 'fs' module?

I have a relatively simple electron app - brunch, react, redux, electron. I'm now trying to read a json file using the "fs" node module, but it's throwing an error:
TypeError: _fs2.default.readFile is not a function
After investigating it looks like the module doesn't contain anything. Compilation says it's available and bundled, but the resulting vendor.js looks weird. Here's the probably relevant part:
[...]
var global = typeof window === 'undefined' ? this : window;require.register("fs", function(exports, require, module) {
module.exports = {};
});
[...]
It doesn't look like brunch correctly bundles these native modules. I also tried it with the electron module and ipc but there it already fails upon loading electron/index.js when trying to require('path').
Here's my brunch-config.js:
exports.files = {
javascripts: {
joinTo: {
'js/vendor.js': /^(?!app)/,
'js/app.js': /^app/
}
},
stylesheets: {joinTo: 'app.css'}
};
exports.plugins = {
babel: {
presets: [
['env',
{
targets: {
"browsers": ["Electron >= 1.7.8"]
}
}
],
'stage-3',
'react'
]
}
};
Did I forget anything in my brunch config? I'm using ES6 imports, but the problem also happens if I use commonjs.

How to import JavaScript functions across files in manner that is simultaneously compatible with Node.js and with browser JavaScript

In vanilla browser-based JavaScript, I can do this:
<html>
<head>
</head>
<body>
<script type="text/javascript" src="file1.js"></script>
<script type="text/javascript" src="file2.js"></script>
</body>
</html>
Then:
// file1.js
function abc(foo) {
console.log("abc received:", foo);
}
And:
// file2.js
abc(36);
...and things behave as expected. (I.e., "abc received 36" is printed to the console.)
How can I include functions from file1.js in file2.js in node.js, so as to preserve the functionality above, while also keeping the web page as-is? Specifically, I would like to keep the html as it appears above, while being able to run
node file2.js
at the command line, and getting abc received 36 at the terminal.
Edit
I found this question with a very similar motivation, by a seemingly more advanced user. But the main link in the answer to that question is dead, and I don't understand how the stated answer works, or what it's supposed to do. Maybe someone can provide me with a MWE tailored to my file1.js, file2.js?
In order for scripts to be functional as both Node.js modules and browser vanilla (non-modular) scripts, they should be defined as UMD modules.
UMD is basically a boilerplate factory function that detects modular environment and falls back to global variables if needed.
E.g. for CommonJS and globals (no AMD) the definition would be:
file1.js
(function (root, factory) {
if (typeof exports === 'object' && typeof exports.nodeName !== 'string') {
// Node
module.exports = factory();
// Global
} else {
root.file1 = factory();
}
}(this, function () {
// module body
function abc(foo) {
console.log("abc received:", foo);
}
return { abc: abc };
}));
file2.js
(function (root, factory) {
if (typeof exports === 'object' && typeof exports.nodeName !== 'string') {
// Node
module.exports = factory(require('./file1'));
// Global
} else {
root.file2 = factory(root.file1);
}
}(this, function (file1) {
// module body
file1.abc(36);
}));
Omitting this boilerplate is the task that bundling tools are supposed to solve, notably Browserify that is intended specifically to bundle CommonJS modules to browser scripts.

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.

Categories