Skip to content
This repository was archived by the owner on Apr 16, 2020. It is now read-only.

Commit 764724b

Browse files
guybedfordMylesBorinsjdalton
committed
esm: irp type implementation
Refs: https://github.com/GeoffreyBooth/node-import-file-specifier-resolution-proposal Refs: nodejs/modules#180 Refs: #6 Refs: #12 Refs: #28 Co-authored-by: Myles Borins <MylesBorins@google.com> Co-authored-by: John-David Dalton <john.david.dalton@gmail.com>
1 parent 969c63a commit 764724b

File tree

107 files changed

+1238
-520
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

107 files changed

+1238
-520
lines changed

.eslintrc.js

+2
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,8 @@ module.exports = {
3838
{
3939
files: [
4040
'doc/api/esm.md',
41+
'test/es-module/test-esm-type-flag.js',
42+
'test/es-module/test-esm-type-flag-alias.js',
4143
'*.mjs',
4244
'test/es-module/test-esm-example-loader.js',
4345
],

doc/api/cli.md

+13-1
Original file line numberDiff line numberDiff line change
@@ -511,6 +511,18 @@ added: v2.4.0
511511

512512
Track heap object allocations for heap snapshots.
513513

514+
### `-m`, `--type=type`
515+
516+
When using `--experimental-modules`, this informs the module resolution type
517+
to interpret the top-level entry into Node.js.
518+
519+
Works with stdin, `--eval`, `--print` as well as standard execution.
520+
521+
Valid values are `"commonjs"` and `"module"`, where the default is to infer
522+
from the file extension and package type boundary.
523+
524+
`-m` is an alias for `--type=module`.
525+
514526
### `--use-bundled-ca`, `--use-openssl-ca`
515527
<!-- YAML
516528
added: v6.11.0
@@ -904,7 +916,7 @@ greater than `4` (its current default value). For more information, see the
904916
[debugger]: debugger.html
905917
[debugging security implications]: https://nodejs.org/en/docs/guides/debugging-getting-started/#security-implications
906918
[emit_warning]: process.html#process_process_emitwarning_warning_type_code_ctor
907-
[experimental ECMAScript Module]: esm.html#esm_loader_hooks
919+
[experimental ECMAScript Module]: esm.html#esm_experimental_loader_hooks
908920
[libuv threadpool documentation]: http://docs.libuv.org/en/latest/threadpool.html
909921
[remote code execution]: https://www.owasp.org/index.php/Code_Injection
910922
[secureProtocol]: tls.html#tls_tls_createsecurecontext_options

doc/api/errors.md

+34-11
Original file line numberDiff line numberDiff line change
@@ -1267,6 +1267,11 @@ An invalid or unexpected value was passed in an options object.
12671267

12681268
An invalid or unknown file encoding was passed.
12691269

1270+
<a id="ERR_INVALID_PACKAGE_CONFIG"></a>
1271+
### ERR_INVALID_PACKAGE_CONFIG
1272+
1273+
An invalid `package.json` file was found which failed parsing.
1274+
12701275
<a id="ERR_INVALID_PERFORMANCE_MARK"></a>
12711276
### ERR_INVALID_PERFORMANCE_MARK
12721277

@@ -1449,26 +1454,19 @@ a `dynamicInstantiate` hook.
14491454
A `MessagePort` was found in the object passed to a `postMessage()` call,
14501455
but not provided in the `transferList` for that call.
14511456

1452-
<a id="ERR_MISSING_MODULE"></a>
1453-
### ERR_MISSING_MODULE
1454-
1455-
> Stability: 1 - Experimental
1456-
1457-
An [ES6 module][] could not be resolved.
1458-
14591457
<a id="ERR_MISSING_PLATFORM_FOR_WORKER"></a>
14601458
### ERR_MISSING_PLATFORM_FOR_WORKER
14611459

14621460
The V8 platform used by this instance of Node.js does not support creating
14631461
Workers. This is caused by lack of embedder support for Workers. In particular,
14641462
this error will not occur with standard builds of Node.js.
14651463

1466-
<a id="ERR_MODULE_RESOLUTION_LEGACY"></a>
1467-
### ERR_MODULE_RESOLUTION_LEGACY
1464+
<a id="ERR_MODULE_NOT_FOUND"></a>
1465+
### ERR_MODULE_NOT_FOUND
14681466

14691467
> Stability: 1 - Experimental
14701468
1471-
A failure occurred resolving imports in an [ES6 module][].
1469+
An [ESM module][] could not be resolved.
14721470

14731471
<a id="ERR_MULTIPLE_CALLBACK"></a>
14741472
### ERR_MULTIPLE_CALLBACK
@@ -2220,6 +2218,32 @@ A non-specific HTTP/2 error has occurred.
22202218
Used in the `repl` in case the old history file is used and an error occurred
22212219
while trying to read and parse it.
22222220

2221+
<a id="ERR_INVALID_REPL_TYPE"></a>
2222+
### ERR_INVALID_REPL_TYPE
2223+
2224+
> Stability: 1 - Experimental
2225+
2226+
The `--type=...` flag is not compatible with the Node.js REPL.
2227+
2228+
<a id="ERR_TYPE_MISMATCH"></a>
2229+
### ERR_TYPE_MISMATCH
2230+
2231+
> Stability: 1 - Experimental
2232+
2233+
The `--type=commonjs` flag was used to attempt to execute an `.mjs` file or
2234+
a `.js` file where the nearest parent `package.json` contains
2235+
`"type": "module"`; or
2236+
the `--type=module` flag was used to attempt to execute a `.cjs` file or
2237+
a `.js` file where the nearest parent `package.json` either lacks a `"type"`
2238+
field or contains `"type": "commonjs"`.
2239+
2240+
<a id="ERR_INVALID_TYPE_FLAG"></a>
2241+
### ERR_INVALID_TYPE_FLAG
2242+
2243+
> Stability: 1 - Experimental
2244+
2245+
An invalid `--type=...` flag value was provided.
2246+
22232247
<a id="ERR_MISSING_DYNAMIC_INSTANTIATE_HOOK"></a>
22242248
#### ERR_MISSING_DYNAMIC_INSTANTIATE_HOOK
22252249

@@ -2250,7 +2274,6 @@ size.
22502274
This `Error` is thrown when a read is attempted on a TTY `WriteStream`,
22512275
such as `process.stdout.on('data')`.
22522276

2253-
22542277
[`'uncaughtException'`]: process.html#process_event_uncaughtexception
22552278
[`--force-fips`]: cli.html#cli_force_fips
22562279
[`Class: assert.AssertionError`]: assert.html#assert_class_assert_assertionerror

doc/api/esm.md

+182-24
Original file line numberDiff line numberDiff line change
@@ -8,10 +8,11 @@
88
<!--name=esm-->
99

1010
Node.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
7883
them 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

8590
For 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+
106112
Builtin modules will provide named exports of their public API, as well as a
107113
default export which can be used for, among other things, modifying the named
108114
exports. Named exports of builtin modules are updated when the corresponding
@@ -132,7 +138,160 @@ fs.readFileSync = () => Buffer.from('Hello, ESM');
132138
fs.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
183340
For 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
254411
in 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

doc/node.1

+3
Original file line numberDiff line numberDiff line change
@@ -270,6 +270,9 @@ Print stack traces for process warnings (including deprecations).
270270
.It Fl -track-heap-objects
271271
Track heap object allocations for heap snapshots.
272272
.
273+
.It Fl -type Ns = Ns Ar type
274+
Set the top-level module resolution type.
275+
.
273276
.It Fl -use-bundled-ca , Fl -use-openssl-ca
274277
Use bundled Mozilla CA store as supplied by current Node.js version or use OpenSSL's default CA store.
275278
The default store is selectable at build-time.

0 commit comments

Comments
 (0)