This post is part of a series of three:
The idea of babel-preset-env
is brilliant: write JavaScript with stage 4 features (or earlier stages, if you want to take that risk) and transpile it so that it is an exact fit for your target platform(s).
However, at the moment, preset-env
only works for your own app, but not for your dependencies, which are normally already transpiled.
This blog post shows how package authors and package users can use the package.json
property esnext
to work with untranspiled source code in npm packages. The code is available in the repository esnext-demo
on GitHub.
What about module
and es2015
? I wish there were no need for a new property, but, as I explain elsewhere, module
and es2015
do not work for this use case:
module
is restricted to what is currently supported by Node.js.es2015
is only for untranspiled ES6 code (and not ES2016+ code).In other words: both properties are close to what we want, but if we want to comply completely with their specifications then we can’t use them.
If your npm package has untranspiled source code, you point to it via esnext
:
{
"esnext": "./index-esnext.js",
"main": "./index.js"
}
index.js
is usually transpiled from index-esnext.js
. E.g., via babel-preset-env
and targets.node === 'current'
.
As a package user, you can work with esnext
by adapting webpack.config.js
in two locations.
First, you need to make sure that webpack finds source code pointed to via esnext
:
const PROPKEY_ESNEXT = 'esnext';
module.exports = {
resolve: {
// Help webpack find `PROPKEY_ESNEXT` source code
mainFields: [PROPKEY_ESNEXT, 'browser', 'module', 'main'],
},
···
};
Second, you need to tell Babel that it should transpile all of the app code, but only those .js
files in dependencies whose package.json
files have the property esnext
.
···
const dir_js = path.resolve(__dirname, 'js');
const dir_node_modules = path.resolve(__dirname, 'node_modules');
module.exports = {
···
module: {
rules: [
{
test: /\.m?js$/,
// Should Babel transpile the file at `filepath`?
include: (filepath) => {
return pathIsInside(filepath, dir_js) ||
(pathIsInside(filepath, dir_node_modules) &&
hasPkgEsnext(filepath));
},
use: [
{
loader: 'babel-loader',
options: {
presets: [
'env'
],
},
}
]
}
]
},
};
/**
* Find package.json for file at `filepath`.
* Return `true` if it has a property whose key is `PROPKEY_ESNEXT`.
*/
function hasPkgEsnext(filepath) {
const pkgRoot = findRoot(filepath);
const packageJsonPath = path.resolve(pkgRoot, 'package.json');
const packageJsonText = fs.readFileSync(packageJsonPath,
{encoding: 'utf-8'});
const packageJson = JSON.parse(packageJsonText);
return {}.hasOwnProperty.call(packageJson, PROPKEY_ESNEXT); // (A)
}
If you are a consumer of npm packages, you can adopt a preliminary solution until your dependencies support esnext
(or another solution becomes popular): transpile module
in the same manner. For example, by changing line A in the previous code to:
return {}.hasOwnProperty.call(packageJson, PROPKEY_ESNEXT)
|| {}.hasOwnProperty.call(packageJson, 'module');
Due to module
pointing to an ES module, it is safe to transpile. Transpiling CommonJS modules is more tricky: strict mode and possibly other transpilation hazards can break things.