88<!-- name=esm-->
99
1010Node.js contains support for ES Modules based upon the
11- [ Node.js EP for ES Modules] [ ] .
11+ [ Node.js EP for ES Modules] [ ] and the [ ESM Minimal Kernel ] [ ] .
1212
13- Not all features of the EP are complete and will be landing as both VM support
14- and implementation is ready. Error messages are still being polished.
13+ The minimal feature set is designed to be compatible with all potential
14+ future implementations. Expect major changes in the implementation including
15+ interoperability support, specifier resolution, and default behavior.
1516
1617## Enabling
1718
@@ -54,6 +55,10 @@ property:
5455
5556## Notable differences between ` import ` and ` require `
5657
58+ ### Mandatory file extensions
59+
60+ You must provide a file extension when using the ` import ` keyword.
61+
5762### No NODE_PATH
5863
5964` NODE_PATH ` is not part of resolving ` import ` specifiers. Please use symlinks
@@ -78,31 +83,32 @@ Modules will be loaded multiple times if the `import` specifier used to resolve
7883them have a different query or fragment.
7984
8085``` js
81- import ' ./foo?query=1' ; // loads ./foo with query of "?query=1"
82- import ' ./foo?query=2' ; // loads ./foo with query of "?query=2"
86+ import ' ./foo.mjs ?query=1' ; // loads ./foo.mjs with query of "?query=1"
87+ import ' ./foo.mjs ?query=2' ; // loads ./foo.mjs with query of "?query=2"
8388```
8489
8590For now, only modules using the ` file: ` protocol can be loaded.
8691
87- ## Interop with existing modules
92+ ## CommonJS, JSON, and Native Modules
8893
89- All CommonJS, JSON, and C++ modules can be used with ` import ` .
94+ CommonJS, JSON, and Native modules can be used with [ ` module.createRequireFromPath() ` ] [ ] .
9095
91- Modules loaded this way will only be loaded once, even if their query
92- or fragment string differs between ` import ` statements.
96+ ``` js
97+ // cjs.js
98+ module .exports = ' cjs' ;
9399
94- When loaded via ` import ` these modules will provide a single ` default ` export
95- representing the value of ` module.exports ` at the time they finished evaluating.
100+ // esm.mjs
101+ import { createRequireFromPath as createRequire } from ' module' ;
102+ import { fileURLToPath as fromPath } from ' url' ;
96103
97- ``` js
98- // foo.js
99- module .exports = { one: 1 };
104+ const require = createRequire (fromPath (import .meta.url));
100105
101- // bar.mjs
102- import foo from ' ./foo.js' ;
103- foo .one === 1 ; // true
106+ const cjs = require (' ./cjs' );
107+ cjs === ' cjs' ; // true
104108` ` `
105109
110+ ## Builtin modules
111+
106112Builtin modules will provide named exports of their public API, as well as a
107113default export which can be used for, among other things, modifying the named
108114exports. Named exports of builtin modules are updated when the corresponding
@@ -132,7 +138,160 @@ fs.readFileSync = () => Buffer.from('Hello, ESM');
132138fs .readFileSync === readFileSync;
133139` ` `
134140
135- ## Loader hooks
141+ ## Resolution Algorithm
142+
143+ ### Features
144+
145+ The resolver has the following properties:
146+
147+ * FileURL-based resolution as is used by ES modules
148+ * Support for builtin module loading
149+ * Relative and absolute URL resolution
150+ * No default extensions
151+ * No folder mains
152+ * Bare specifier package resolution lookup through node_modules
153+
154+ ### Resolver Algorithm
155+
156+ The algorithm to load an ES module specifier is given through the
157+ **ESM_RESOLVE** method below. It returns the resolved URL for a
158+ module specifier relative to a parentURL, in addition to the unique module
159+ format for that resolved URL given by the **ESM_FORMAT** routine.
160+
161+ The _"module"_ format is returned for an ECMAScript Module, while the
162+ _"commonjs"_ format is used to indicate loading through the legacy
163+ CommonJS loader. Additional formats such as _"wasm"_ or _"addon"_ can be
164+ extended in future updates.
165+
166+ In the following algorithms, all subroutine errors are propogated as errors
167+ of these top-level routines.
168+
169+ _isMain_ is **true** when resolving the Node.js application entry point.
170+
171+ If the top-level ` -- type` is _"commonjs"_, then the ESM resolver is skipped
172+ entirely for the CommonJS loader.
173+
174+ If the top-level ` -- type` is _"module"_, then the ESM resolver is used
175+ as described here, with the conditional ` -- type` check in **ESM_FORMAT**.
176+
177+ **ESM_RESOLVE(_specifier_, _parentURL_, _isMain_)**
178+ > 1. Let _resolvedURL_ be **undefined**.
179+ > 1. If _specifier_ is a valid URL, then
180+ > 1. Set _resolvedURL_ to the result of parsing and reserializing
181+ > _specifier_ as a URL.
182+ > 1. Otherwise, if _specifier_ starts with _"/"_, then
183+ > 1. Throw an _Invalid Specifier_ error.
184+ > 1. Otherwise, if _specifier_ starts with _"./"_ or _"../"_, then
185+ > 1. Set _resolvedURL_ to the URL resolution of _specifier_ relative to
186+ > _parentURL_.
187+ > 1. Otherwise,
188+ > 1. Note: _specifier_ is now a bare specifier.
189+ > 1. Set _resolvedURL_ the result of
190+ > **PACKAGE_RESOLVE**(_specifier_, _parentURL_).
191+ > 1. If the file at _resolvedURL_ does not exist, then
192+ > 1. Throw a _Module Not Found_ error.
193+ > 1. Set _resolvedURL_ to the real path of _resolvedURL_.
194+ > 1. Let _format_ be the result of **ESM_FORMAT**(_resolvedURL_, _isMain_).
195+ > 1. Load _resolvedURL_ as module format, _format_.
196+
197+ PACKAGE_RESOLVE(_packageSpecifier_, _parentURL_)
198+ > 1. Let _packageName_ be *undefined*.
199+ > 1. Let _packageSubpath_ be *undefined*.
200+ > 1. If _packageSpecifier_ is an empty string, then
201+ > 1. Throw an _Invalid Specifier_ error.
202+ > 1. If _packageSpecifier_ does not start with _"@"_, then
203+ > 1. Set _packageName_ to the substring of _packageSpecifier_ until the
204+ > first _"/"_ separator or the end of the string.
205+ > 1. Otherwise,
206+ > 1. If _packageSpecifier_ does not contain a _"/"_ separator, then
207+ > 1. Throw an _Invalid Specifier_ error.
208+ > 1. Set _packageName_ to the substring of _packageSpecifier_
209+ > until the second _"/"_ separator or the end of the string.
210+ > 1. Let _packageSubpath_ be the substring of _packageSpecifier_ from the
211+ > position at the length of _packageName_ plus one, if any.
212+ > 1. Assert: _packageName_ is a valid package name or scoped package name.
213+ > 1. Assert: _packageSubpath_ is either empty, or a path without a leading
214+ > separator.
215+ > 1. If _packageSubpath_ contains any _"."_ or _".."_ segments or percent
216+ > encoded strings for _"/"_ or _"\" _ then,
217+ > 1. Throw an _Invalid Specifier_ error.
218+ > 1. If _packageSubpath_ is empty and _packageName_ is a Node.js builtin
219+ > module, then
220+ > 1. Return the string _"node:"_ concatenated with _packageSpecifier_.
221+ > 1. While _parentURL_ is not the file system root,
222+ > 1. Set _parentURL_ to the parent folder URL of _parentURL_.
223+ > 1. Let _packageURL_ be the URL resolution of the string concatenation of
224+ > _parentURL_, _"/node_modules/"_ and _packageSpecifier_.
225+ > 1. If the folder at _packageURL_ does not exist, then
226+ > 1. Set _parentURL_ to the parent URL path of _parentURL_.
227+ > 1. Continue the next loop iteration.
228+ > 1. Let _pjson_ be the result of **READ_PACKAGE_JSON**(_packageURL_).
229+ > 1. If _packageSubpath_ is empty, then
230+ > 1. Return the result of **PACKAGE_MAIN_RESOLVE**(_packageURL_,
231+ > _pjson_).
232+ > 1. Otherwise,
233+ > 1. Return the URL resolution of _packageSubpath_ in _packageURL_.
234+ > 1. Throw a _Module Not Found_ error.
235+
236+ PACKAGE_MAIN_RESOLVE(_packageURL_, _pjson_)
237+ > 1. If _pjson_ is **null**, then
238+ > 1. Throw a _Module Not Found_ error.
239+ > 1. If _pjson.main_ is a String, then
240+ > 1. Let _resolvedMain_ be the concatenation of _packageURL_, "/", and
241+ > _pjson.main_.
242+ > 1. If the file at _resolvedMain_ exists, then
243+ > 1. Return _resolvedMain_.
244+ > 1. If _pjson.type_ is equal to _"module"_, then
245+ > 1. Throw a _Module Not Found_ error.
246+ > 1. Let _legacyMainURL_ be the result applying the legacy
247+ > **LOAD_AS_DIRECTORY** CommonJS resolver to _packageURL_, throwing a
248+ > _Module Not Found_ error for no resolution.
249+ > 1. If _legacyMainURL_ does not end in _".js"_ then,
250+ > 1. Throw an _Unsupported File Extension_ error.
251+ > 1. Return _legacyMainURL_.
252+
253+ **ESM_FORMAT(_url_, _isMain_)**
254+ > 1. Assert: _url_ corresponds to an existing file.
255+ > 1. If _isMain_ is **true** and the ` -- type` flag is _"module"_, then
256+ > 1. If _url_ ends with _".cjs"_, then
257+ > 1. Throw a _Type Mismatch_ error.
258+ > 1. Return _"module"_.
259+ > 1. Let _pjson_ be the result of **READ_PACKAGE_BOUNDARY**(_url_).
260+ > 1. If _pjson_ is **null** and _isMain_ is **true**, then
261+ > 1. If _url_ ends in _".mjs"_, then
262+ > 1. Return _"module"_.
263+ > 1. Return _"commonjs"_.
264+ > 1. If _pjson.type_ exists and is _"module"_, then
265+ > 1. If _url_ ends in _".cjs"_, then
266+ > 1. Return _"commonjs"_.
267+ > 1. Return _"module"_.
268+ > 1. Otherwise,
269+ > 1. If _url_ ends in _".mjs"_, then
270+ > 1. Return _"module"_.
271+ > 1. If _url_ does not end in _".js"_, then
272+ > 1. Throw an _Unsupported File Extension_ error.
273+ > 1. Return _"commonjs"_.
274+
275+ READ_PACKAGE_BOUNDARY(_url_)
276+ > 1. Let _boundaryURL_ be _url_.
277+ > 1. While _boundaryURL_ is not the file system root,
278+ > 1. Let _pjson_ be the result of **READ_PACKAGE_JSON**(_boundaryURL_).
279+ > 1. If _pjson_ is not **null**, then
280+ > 1. Return _pjson_.
281+ > 1. Set _boundaryURL_ to the parent URL of _boundaryURL_.
282+ > 1. Return **null**.
283+
284+ READ_PACKAGE_JSON(_packageURL_)
285+ > 1. Let _pjsonURL_ be the resolution of _"package.json"_ within _packageURL_.
286+ > 1. If the file at _pjsonURL_ does not exist, then
287+ > 1. Return **null**.
288+ > 1. If the file at _packageURL_ does not parse as valid JSON, then
289+ > 1. Throw an _Invalid Package Configuration_ error.
290+ > 1. Return the parsed JSON source of the file at _pjsonURL_.
291+
292+ ## Experimental Loader hooks
293+
294+ **Note: This API is currently being redesigned and will still change.**.
136295
137296<!-- type=misc -->
138297
@@ -173,11 +332,9 @@ module. This can be one of the following:
173332
174333| ` format` | Description |
175334| --- | --- |
176- | ` 'esm' ` | Load a standard JavaScript module |
177- | ` 'cjs' ` | Load a node-style CommonJS module |
178- | ` 'builtin' ` | Load a node builtin CommonJS module |
179- | ` 'json' ` | Load a JSON file |
180- | ` 'addon' ` | Load a [ C++ Addon] [ addons ] |
335+ | ` ' module' ` | Load a standard JavaScript module |
336+ | ` ' commonjs' ` | Load a Node.js CommonJS module |
337+ | ` ' builtin' ` | Load a Node.js builtin module |
181338| ` ' dynamic' ` | Use a [dynamic instantiate hook][] |
182339
183340For example, a dummy loader to load JavaScript restricted to browser resolution
@@ -254,5 +411,6 @@ then be called at the exact point of module evaluation order for that module
254411in the import tree.
255412
256413[Node.js EP for ES Modules]: https://github.com/nodejs/node-eps/blob/master/002-es-modules.md
257- [ addons ] : addons.html
258414[dynamic instantiate hook]: #esm_dynamic_instantiate_hook
415+ [` module .createRequireFromPath ()` ]: modules.html#modules_module_createrequirefrompath_filename
416+ [ESM Minimal Kernel]: https://github.com/nodejs/modules/blob/master/doc/plan-for-new-modules-implementation.md
0 commit comments