This post is part of a series of three:
This blog post explains ways of targeting multiple platforms via the same npm package.
Before we get into the actual topic, let’s quickly review common JavaScript module formats.
This following table gives an overview of standard properties in package.json that are used for pointing to source code.
main |
browser |
module |
es2015 |
|
|---|---|---|---|---|
| ES version | ES5+ | ES5+ | ES5+ | ES6 |
| Module format | CJS | CJS | ESM | ESM |
| webpack | ✔ | ✔ | ✔ | – |
| Rollup | (✔) | (✔) | ✔ | – |
| jspm | ✔ | – | – | – |
Note: “ES5+” means “whatever language features are supported by the JavaScript engines you are targeting”.
At the moment, these are the most common JavaScript module formats:
The idea of UMD is that you can implement a JavaScript module in such a manner that it supports (up to) three formats at the same time: AMD, CJS and delivery via a global variable.
This is a UMD module that supports AMD and CJS (source):
(function(define) {
define(function (require, exports, module) {
var b = require('b');
return function () {};
});
}( // Help Node out by setting up define.
typeof module === 'object' && module.exports && typeof define !== 'function' ?
function (factory) { module.exports = factory(require, exports, module); } :
define
));
Documentation:
require is a function may not work if require.js is being used).You can only deliver source code for a single platform via an npm package. Property engines of package.json lets you specify exactly what platform that is:
{ "engines" : { "node" : ">=0.10.3 <0.12" } }
{ "engines" : { "npm" : "~1.0.20" } }
However, that doesn’t help you with the following use cases, where you need source code for multiple platforms per package:
babel-preset-env affords us.There are two dimensions at play here:
The next section covers solutions for use case 1.
The following subsections explain properties in package.json that can be used to point to alternate versions of the same code.
When I use the term “native features”, it means: language features supported by the platforms you are targeting.
main: native features, CJS main is the standard mechanism for pointing to the module code inside a package if you want to override the default path, index.js. It is supported everywhere. This is an example:
{
"name": "the-package",
"version": "1.0.1",
"main": "dist/the-package.umd.js",
}
module: native features, ESM This property helps tools such as the tree-shaking module bundler Rollup that depend on the ESM format. Other than that, only native language features are supported. That is, module is just main with a different module format:
{
"name": "the-package",
"version": "1.0.1",
"main": "dist/the-package.umd.js",
"module": "dist/the-package.es2015.js"
}
Documentation:
es2015: ES6, ESM Angular v4 delivers each package in three formats:
mainmodulees2015This is what its package.json looks like:
{
"name": "@angular/core",
"main": "./bundles/core.umd.js",
"module": "./@angular/core.es5.js",
"es2015": "./@angular/core.js",
···
}
I like the idea of this property. But its name and semantics mean that it’ll age relatively quickly.
Documentation:
jsnext:main: the precursor of module The property jsnext:main is now deprecated. It was superseded by module.
browser: browser-specific code The idea of the property browser is that:
main provides Node.js codebrowser provides browser-specific codeThe simplest mode of browser is as an alternative to main:
{
"main": "dist/the-package.server.js",
"browser": "dist/the-package.client.js",
···
}
An advanced mode lets you replace specific files:
"browser": {
"module-a": "./shims/module-a.js",
"./server/only.js": "./shims/client-only.js"
}
Documentation:
main |
browser |
module |
es2015 |
|
|---|---|---|---|---|
| webpack | ✔ | ✔ | ✔ | – |
| Rollup | (✔) | (✔) | ✔ | – |
| jspm | ✔ | – | – | – |
Comments:
es2015 is simple.jspm and others).For webpack, you can configure where it searches for module code inside packages via the resolve.mainFields option:
module.exports = {
···
target: "web",
// the environment in which the bundle should run
resolve: {
// options for resolving module requests
mainFields: ···,
···
},
···
}
The default value of this property depends on the value of target.
If target is "web", "webworker" or unspecified then the default is:
mainFields: ["browser", "module", "main"]
If target has any other value (including "node") then the default is:
mainFields: ["module", "main"]
Documentation:
resolve.mainFields” in the webpack documentationSupport for multi-platform packages has come a long way. The main challenge ahead is to make sure transpiling external dependencies is as “auto-updating” and hassle-free as babel-preset-env.
babel-preset-env: a preset that configures Babel for you