This document outlines the plan for building a new implementation to support ECMAScript modules in Node.js. The effort is split up into phases:
-
Phase 0 branches off of current Node but removes much of the Node 8.5.0+
--experimental-modules
implementation so that a new implementation could be built in its place. -
Phase 1 adds the “minimal kernel,” features that the modules working group felt would likely appear in any potential new ES modules implementation.
-
Phase 2 fleshes out the implementation with enough functionality that it should be useful to average users as a minimum viable product.
- At the completion of Phase 2, the old
--experimental-modules
implementation was replaced with this new one (still behind the--experimental-modules
flag). It was released as part of Node 12 on 2019-04-23.
- At the completion of Phase 2, the old
-
Phase 3 improves user experience and extends the MVP.
- At the completion of Phase 3, the new implementation’s experimental flag will be dropped. The goal is to “release” (drop the
--experimental-modules
flag) by when Node 12 starts LTS in October 2019.
- At the completion of Phase 3, the new implementation’s experimental flag will be dropped. The goal is to “release” (drop the
The effort is currently in Phase 3.
At every phase, the following standards must be maintained:
- Spec compliance (#132): We must always follow the ES spec.
- Browser equivalence (#133): There’s room for debate in specific cases, but in general if Node is doing something that browsers also do, Node should do it in the same way. Alternatively, code that executes in both environments should produce identical results.
- Don’t break CommonJS (#112): We cannot cause breaking changes with regards to CommonJS.
See also the features list in the README.
From current shipping Node, the following changes were made to strip out most of the Node 8.5.0+ --experimental-modules
implementation so that a new implementation could be built in its place:
-
Remove support in the
import
statement of formats other than ESM:- No CommonJS.
- No JSON.
- No native modules.
-
Remove dynamic path searching:
- No extension adding.
- No directory resolution, including no support for
index.js
orindex.mjs
. - No support for
main
field for ESM.
-
Remove current VM implementation
-
Remove current Loader implementation
These changes were implemented in nodejs/ecmascript-modules#6.
The “minimal kernel” consists of features that the @nodejs/modules group have agreed will be necessary for all potential iterations of our ESM implementation. Phase 1 does not include features that preclude other potential features or implementation approaches; and Phase 1 also does not include some features that should naturally be built in a later phase of development, for example because those features depend on features planned for Phase 1.
-
module.createRequireFromPath
(nodejs/node#19360) is the only way to import CommonJS into an ES module, for now.import.meta.require
fails at runtime as opposed to import time. This is not desireable to all committee members.- Hold off on
import
statements for CommonJS until more progress is made on the dynamic modules spec. - Landed in core in https://github.com/nodejs/node/commit/246f6332e5a5f395d1e39a3594ee5d6fe869d622
-
import
statements will only support files with an.mjs
extension, and will import only ES modules, for now.- No JSON or native modules;
createRequireFromPath
can be used to get these.
- No JSON or native modules;
-
import.meta.url
.- Already in the existing implementation.
-
Dynamic
import()
.- Already in the existing implementation.
-
Support for built-in modules with named exports
- Already in the existing implementation.
Phase 2 fleshes out the implementation with enough functionality that it should be useful to average users as a minimum viable product. At the completion of Phase 2, the old --experimental-modules
implementation was replaced with this new one (still behind the --experimental-modules
flag).
-
Define semantics for importing a package entry point, e.g.
import _ from 'lodash'
- Proposal: “File Specifier Resolution” proposal covers bare module specifier resolution of CommonJS packages.
- Landed in nodejs/ecmascript-modules#28.
-
Define semantics for determining when to load sources as CommonJS or ES module for both the top-level main (
node x.js
) and dependency loading.- Proposal: “File Specifier Resolution” proposal covers
import
statements of ESM files; and CommonJS files, package entry point and package deep imports. - Landed in nodejs/ecmascript-modules#28.
- Proposal: “File Specifier Resolution” proposal covers
-
Define semantics for enabling ESM treatment of source code loaded via
--eval
, STDIN, and extensionless files (both with and without shebang lines).- Proposal: “Entry Points Proposal” covers non-file forms of input as well as adding
--type
flag for controlling file-based input. - Landed in nodejs/ecmascript-modules#32.
- Renamed to
--entry-type
as part of upstream PR to Node.js core. - Renamed to
--intry-type
and limited to--eval
,--print
andSTDIN
as part of follow-up PR to Node.js core.
- Proposal: “Entry Points Proposal” covers non-file forms of input as well as adding
-
File extension and directory index searching in ESM, behind its own flag,
--es-module-specifier-resolution
.- See nodejs#268.
- Landed in nodejs/ecmascript-modules#48.
The work through the end of Phase 2 landed in Node.js master
as part of nodejs/node#26745 and was released in Node 12.0.0.
Phase 3 improves user experience and extends the MVP. Phase 3 is malleable based on how things proceed while working on this phase. At the end of this phase, the --experimental-modules
flag is dropped.
-
A loaders solution that supports all items in the features list in our README.
- Should loaders be per package, per application or either?
- Status: In development.
-
"exports"
field: for consumers of a package, map the paths of deep imports to provide encapsulation (an explicit public API); pretty specifiers (no file exensions or paths that include things likedist/
) and flexibility for future package versions renaming or moving files without affecting the package’s public API. Applies to both ESM and CommonJS.- Proposal: Package Exports Proposal.
- Partial upstream PR: nodejs/node#28568.
- Status: Upstream PR submitted.
-
Reference package root via the package’s name.
- Proposal: Package
"name"
Resolution Proposal. - Discussion: nodejs#306.
- Status: Seeking consensus.
- Proposal: Package
-
Limited module type detection.
- PR: nodejs/ecmascript-modules#69.
- Upstream PR: nodejs/node#27808.
- Status: Upstream PR submitted.
-
Dual CommonJS/ESM packages: Either support packages with both CommonJS and ESM sources that can be used in either environment; or decide to specifically not support dual CommonJS/ESM packages.
- Status quo is current
--experimental-modules
behavior:"main"
points to exactly one file, and all file extensions are mandatory (by default), so there is no possibility of animport
specifier pointing to different files in ESM versus CommonJS. Recommended practice for dual packages is to have"main"
point to the CommonJS entry point and have users use a deep import, e.g./module.mjs
, to access ESM entry point. - PR to document status quo: nodejs/node#27957.
- Proposal for new
package.json
field for ESM entry point: nodejs#273. - PR for new
package.json
field: nodejs/ecmascript-modules#41. - Status summary: nodejs#273 (comment).
- Proposal for
require
of ESM: https://github.com/nodejs/modules/issues/299. - Status: Status quo has consensus and will ship absent any other proposals achieving consensus. “New field” proposal lacks consensus. “
require
of ESM” proposal awaiting implementation and consensus.
- Status quo is current
-
Finalize behavior of
import
of CommonJS files and packages.- Overview: nodejs#264.
- Status quo is current
--experimental-modules
behavior:import
only the CommonJS default export, soimport _ from 'cjs-pkg'
but notimport { shuffle } from 'cjs-pkg'
. - If the spec changes to allow it, we want to implement dynamic modules to enable named exports from CommonJS: nodejs#252.
- Another option is to specify CommonJS named exports in
package.json
: nodejs#324. - Status: Status quo has consensus and will ship absent any other proposals achieving consensus. Dynamic modules is stalled due to upstream concerns from TC39. Named exports in
package.json
seeks consensus.
- Better mechanism for creating
require
function:createRequire
.- Landed in nodejs/node#27405 and shipped in 12.2.0.
- Provide a way to make ESM the default instead of CommonJS.
- Discussion: nodejs#318.
- Proposal: nodejs#335.
- Status: Currently one can add
--input-type=module
toNODE_OPTIONS
to flip the default for--input-type
; at the moment the group is deciding not to pursue providing an ability to flip the default for thepackage.json
type
field. We instead want to encourage all packages, both ESM and CommonJS, to include an explicittype
field; leaving it out and relying on a default is something we want to discourage.