-
Notifications
You must be signed in to change notification settings - Fork 0
Description
note: jsnext:main has been superseded by pkg.module, which indicates the location of a file with import/export declarations.
Typically, the pkg.module file will not have other ES2015+ features unless the package explicitly states that it doesn't support older environments — in other words, best practice is still to transpile ES2015+ features other than import/export. More info here.
Original issue follows.
This is a follow-up to this Twitter conversation:
Exposition
In CommonJS packages, it's common to have a main field in your package.json that tells Node.js or Browserify (etc) where to find the code:
{
"name": "some-package",
"main": "lib/some-package.js"
}Nowadays, thanks to Babel, it's increasingly common to author in ES6/7 and use a prepublish hook to generate distributable files:
{
"name": "some-package",
"main": "dist/some-package.js",
"scripts": {
"build": "babel src --out-dir dist",
"prepublish": "npm run build"
}
}So far so good. But the dist file must be CommonJS or UMD, otherwise nothing can use it. That means we're missing an opportunity, because ES6 modules are better in lots of important ways, particularly when it comes to making efficient bundles for use in the browser. (See The Importance of import and export by @benjamn if you need persuading.)
Unfortunately, we can't just create a better UMD block that incorporates ES6 imports and exports, because in engines that don't support ES6 modules (all of them), that's a syntax error. So package authors can't distribute ES6 modules without making their packages incompatible with everything.
Enter jsnext:main
There is a proposed solution to this problem – jsnext:main – which, like main, indicates the package's entry point, except that it uses export instead of module.exports. This is great, because it means that CommonJS/UMD distributable files can co-exist with ES6 distributable files. In the future, once everyone supports ES6 imports and exports, we can simply ditch the CommonJS/UMD stuff.
But does jsnext:main mean 'entry point written with potentially unsupported ES6/7 features', or 'entry point that runs in existing engines, except for the import/export statements'? It's unclear. It sounds as though you can use ES6/7 features, the assumption being that it points to your source code, and that a consumer (such as a bundler) takes responsibility for transpiling it (e.g. with Babel):
{
"main": "dist/some-package.js",
"jsnext:main": "src/index.js"
}But that's problematic. What if src/index.js uses stage 0 features? some-package might have a build process that transpiles those features, but does that mean that all consumers of some-package have to be similarly equipped? (Yes, .babelrc makes that possible, but it's still a weird and brittle process, and it may become rather more complex with Babel 6.)
A better solution: jsnext:main could instead refer to an ES6-module entry point that is otherwise ready to use:
{
"main": "dist/some-package.cjs.js",
"jsnext:main": "dist/some-package.es6.js"
}Prepublish hooks make this stuff very straightforward to set up, and it means that everyone can use your package, and no-one needs to worry about your source code – they're only ever dealing with code that is ready to distribute. It's a very simple solution to the problem of serving CommonJS/UMD and ES6 builds simultaneously.
But the 'jsnext' part of jsnext:main is confusing. I'd suggested changing it to something less ambiguous, but would that just confuse people even more?
Does it go far enough?
@RReverser thinks not, and argues that engines/bundlers/whatever should be able to select different files based on feature detection. I have no idea exactly how that would work but I'm eager to hear more.
In summary
- We badly need a way for package authors to make their packages available as both CommonJS/UMD and ES6. Unless there's a way to transition from one to the other, we'll never be able to move past the legacy module formats, even when most widely-used engines support ES6 modules
jsnext:mainis one possible solution, though it's ambiguous. Personally I think that if we use it, it should be used to point to ready-to-use (other thanimport/export) distributable code, not source code that still needs to be run through Babel- We could go even further and have multiple entry points based on feature detection
All thoughts welcome. Thanks