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:
main
module
es2015
This 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