From ab97a43f401674ded3ca0b42ddc746439ec5c128 Mon Sep 17 00:00:00 2001 From: guybedford Date: Thu, 29 Nov 2018 14:26:58 +0200 Subject: [PATCH 01/12] specify import file specifier resolution proposal --- doc/api/esm.md | 131 +++++++++++++++++++++++++++++++++++++++++-------- 1 file changed, 111 insertions(+), 20 deletions(-) diff --git a/doc/api/esm.md b/doc/api/esm.md index d2277044a94c6c..74978bc7f44fd1 100644 --- a/doc/api/esm.md +++ b/doc/api/esm.md @@ -55,10 +55,6 @@ property: ## Notable differences between `import` and `require` -### Only Support for .mjs - -ESM must have the `.mjs` extension. - ### Mandatory file extensions You must provide a file extension when using the `import` keyword. @@ -157,37 +153,132 @@ The resolver has the following properties: ### Resolver Algorithm -The algorithm to resolve an ES module specifier is provided through -_ESM_RESOLVE_: +The algorithm to load an ES module specifier is given through the +**ESM_RESOLVE** method below. It returns the resolved URL for a +module specifier relative to a parentURL, in addition to the unique module +format for that resolved URL given by the **ESM_FORMAT** routine. + +In the following algorithms, all subroutine errors are propogated as errors +of these top-level routines. -**ESM_RESOLVE**(_specifier_, _parentURL_) -> 1. Let _resolvedURL_ be _undefined_. -> 1. If _specifier_ is a valid URL then, +#### ESM_RESOLVE(_specifier_, _parentURL_) +> 1. Let _resolvedURL_ be *undefined*. +> 1. If _specifier_ is a valid URL, then > 1. Set _resolvedURL_ to the result of parsing and reserializing > _specifier_ as a URL. -> 1. Otherwise, if _specifier_ starts with _"/"_, _"./"_ or _"../"_ then, +> 1. Otherwise, if _specifier_ starts with _"/"_, then +> 1. Throw an _Invalid Specifier_ error. +> 1. Otherwise, if _specifier_ starts with _"./"_ or _"../"_, then > 1. Set _resolvedURL_ to the URL resolution of _specifier_ relative to > _parentURL_. > 1. Otherwise, > 1. Note: _specifier_ is now a bare specifier. > 1. Set _resolvedURL_ the result of > **PACKAGE_RESOLVE**(_specifier_, _parentURL_). -> 1. If the file at _resolvedURL_ does not exist then, +> 1. If the file at _resolvedURL_ does not exist, then > 1. Throw a _Module Not Found_ error. -> 1. Return _resolvedURL_. - -**PACKAGE_RESOLVE**(_packageSpecifier_, _parentURL_) -> 1. Assert: _packageSpecifier_ is a bare specifier. -> 1. If _packageSpecifier_ is a Node.js builtin module then, +> 1. Let _format_ be the result of **ESM_FORMAT**(_url_). +> 1. Return _{ resolvedURL, format }_. + +PACKAGE_RESOLVE(_packageSpecifier_, _parentURL_) +> 1. Let _packageName_ be *undefined*. +> 1. Let _packagePath_ be *undefined*. +> 1. If _packageSpecifier_ does not start with _"@"_, then +> 1. If _packageSpecifier_ is an empty string, then +> 1. Throw an _Invalid Package Name_ error. +> 1. Set _packageName_ to the substring of _packageSpecifier_ until the +> first _"/"_ separator or the end of the string. +> 1. If _packageSpecifier_ starts with _"@"_, then +> 1. If _packageSpecifier_ does not contain a _"/"_ separator, then +> 1. Throw an _Invalid Package Name_ error. +> 1. Set _packageName_ to the substring of _packageSpecifier_ +> until the second _"/"_ separator or the end of the string. +> 1. Let _packagePath_ be the substring of _packageSpecifier_ from the +> position at the length of _packageName_ plus one, if any. +> 1. Assert: _packageName_ is a valid package name or scoped package name. +> 1. Assert: _packagePath_ is either empty, or a path without a leading +> separator. +> 1. Note: Further package name validations can be added here. +> 1. If _packagePath_ is empty and _packageName_ is a Node.js builtin +> module, then > 1. Return the string _"node:"_ concatenated with _packageSpecifier_. -> 1. While _parentURL_ contains a non-empty _pathname_, +> 1. While _parentURL_ is not the file system root, > 1. Set _parentURL_ to the parent folder URL of _parentURL_. > 1. Let _packageURL_ be the URL resolution of the string concatenation of -> _parentURL_, _"/node_modules/"_ and _"packageSpecifier"_. -> 1. If the file at _packageURL_ exists then, -> 1. Return _packageURL_. +> _parentURL_, _"/node_modules/"_ and _packageSpecifier_. +> 1. If the folder at _packageURL_ does not exist, then +> 1. Note: This check can be optimized out where possible in +> implementation. +> 1. Set _parentURL_ to the parent URL path of _parentURL_. +> 1. Continue the next loop iteration. +> 1. If _packagePath_ is empty, then +> 1. Let _url_ be the result of **PACKAGE_MAIN_RESOLVE**(_packageURL_). +> 1. If _url_ is *null*, then +> 1. Throw a _Module Not Found_ error. +> 1. Return _url_. +> 1. Otherwise, +> 1. Return the URL resolution of _packagePath_ in _packageURL_. > 1. Throw a _Module Not Found_ error. +PACKAGE_MAIN_RESOLVE(_packageURL_) +> 1. Let _pjsonURL_ be the URL of the file _"package.json"_ within the parent + path _packageURL_. +> 1. If the file at _pjsonURL_ exists, then +> 1. Let _pjson_ be the result of **READ_JSON_FILE**(_pjsonURL_). +> 1. If **HAS_ESM_PROPERTIES**(_pjson_) is *true*, then +> 1. Let _mainURL_ be the result applying the legacy +> **LOAD_AS_DIRECTORY** CommonJS resolver to _packageURL_, returning +> *undefined* for no resolution. +> 1. If _mainURL_ is not *undefined* and **ESM_FORMAT**(_mainURL_) is not +> equal to _"cjs"_, then +> 1. Throw a _"Invalid Module Format"_ error. +> 1. Return _mainURL_. +> 1. _Note: ESM main yet to be implemented here._ +> 1. Return *null*. + +#### ESM_FORMAT(_url_) +> 1. Assert: _url_ corresponds to an existing file. +> 1. Let _pjsonURL_ be the result of **READ_PACKAGE_BOUNDARY**(_url_). +> 1. Let _pjson_ be *undefined*. +> 1. If _pjsonURL_ is not *null*, then +> 1. Set _pjson_ to the result of **READ_JSON_FILE**(_pjsonURL_). +> 1. If _pjsonURL_ is *null* or **HAS_ESM_PROPERTIES**(_pjson_) is *true*, then +> 1. If _url_ does not end in _".js"_ or _".mjs"_ then, +> 1. Throw an _Unkonwn Module Format_ error. +> 1. Return _"esm"_. +> 1. Otherwise, +> 1. If _url_ does not end in _".js"_ then, +> 1. Throw an _Unknown Module Format_ error. +> 1. Return _"cjs"_. + +> 1. If **HAS_ESM_PROPERTIES**(_pjson_) is *true*, then +> 1. Return _"esm"_. +> 1. Return _"cjs"_. + +READ_PACKAGE_BOUNDARY(_url_) +> 1. Let _boundaryURL_ be the URL resolution of _"package.json"_ relative to +> _url_. +> 1. While _boundaryURL_ is not the file system root, +> 1. If the file at _boundaryURL_ exists, then +> 1. Return _boundaryURL_. +> 1. Set _boundaryURL_ to the URL resolution of _"../package.json"_ relative +> to _boundaryURL_. +> 1. Return *null*. + +READ_JSON_FILE(_url_) +> 1. If the file at _url_ does not parse as valid JSON, then +> 1. Throw an _Invalid Package Configuration_ error. +> 1. Let _pjson_ be the parsed JSON source of the file at _url_. +> 1. Return _pjson_. + +HAS_ESM_PROPERTIES(_pjson_) +> 1. Note: To be specified. + +_ESM properties_ in a package.json file are yet to be specified. +The current possible specifications for this are the +[_"exports"_](https://github.com/jkrems/proposal-pkg-exports) +or [_"mode"_](https://github.com/nodejs/node/pull/18392) flag. + [Node.js EP for ES Modules]: https://github.com/nodejs/node-eps/blob/master/002-es-modules.md [`module.createRequireFromPath()`]: modules.html#modules_module_createrequirefrompath_filename [ESM Minimal Kernel]: https://github.com/nodejs/modules/blob/master/doc/plan-for-new-modules-implementation.md From 41d2d3af8d7cb03e25fea0de2b441da7b42cac98 Mon Sep 17 00:00:00 2001 From: guybedford Date: Wed, 5 Dec 2018 21:14:01 +0200 Subject: [PATCH 02/12] feedback from @SMotaal --- doc/api/esm.md | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/doc/api/esm.md b/doc/api/esm.md index 74978bc7f44fd1..4a5f5b925c6c41 100644 --- a/doc/api/esm.md +++ b/doc/api/esm.md @@ -183,12 +183,12 @@ of these top-level routines. PACKAGE_RESOLVE(_packageSpecifier_, _parentURL_) > 1. Let _packageName_ be *undefined*. > 1. Let _packagePath_ be *undefined*. +> 1. If _packageSpecifier_ is an empty string, then +> 1. Throw an _Invalid Package Name_ error. > 1. If _packageSpecifier_ does not start with _"@"_, then -> 1. If _packageSpecifier_ is an empty string, then -> 1. Throw an _Invalid Package Name_ error. > 1. Set _packageName_ to the substring of _packageSpecifier_ until the > first _"/"_ separator or the end of the string. -> 1. If _packageSpecifier_ starts with _"@"_, then +> 1. Otherwise, > 1. If _packageSpecifier_ does not contain a _"/"_ separator, then > 1. Throw an _Invalid Package Name_ error. > 1. Set _packageName_ to the substring of _packageSpecifier_ @@ -225,7 +225,7 @@ PACKAGE_MAIN_RESOLVE(_packageURL_) path _packageURL_. > 1. If the file at _pjsonURL_ exists, then > 1. Let _pjson_ be the result of **READ_JSON_FILE**(_pjsonURL_). -> 1. If **HAS_ESM_PROPERTIES**(_pjson_) is *true*, then +> 1. If **HAS_ESM_PROPERTIES**(_pjson_) is *false*, then > 1. Let _mainURL_ be the result applying the legacy > **LOAD_AS_DIRECTORY** CommonJS resolver to _packageURL_, returning > *undefined* for no resolution. @@ -250,7 +250,6 @@ PACKAGE_MAIN_RESOLVE(_packageURL_) > 1. If _url_ does not end in _".js"_ then, > 1. Throw an _Unknown Module Format_ error. > 1. Return _"cjs"_. - > 1. If **HAS_ESM_PROPERTIES**(_pjson_) is *true*, then > 1. Return _"esm"_. > 1. Return _"cjs"_. From 7ffa196553a09187670b3133246e9e6467d4e482 Mon Sep 17 00:00:00 2001 From: guybedford Date: Wed, 5 Dec 2018 21:50:07 +0200 Subject: [PATCH 03/12] read package boundary simplification --- doc/api/esm.md | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/doc/api/esm.md b/doc/api/esm.md index 4a5f5b925c6c41..c2732e0d76046c 100644 --- a/doc/api/esm.md +++ b/doc/api/esm.md @@ -238,11 +238,8 @@ PACKAGE_MAIN_RESOLVE(_packageURL_) #### ESM_FORMAT(_url_) > 1. Assert: _url_ corresponds to an existing file. -> 1. Let _pjsonURL_ be the result of **READ_PACKAGE_BOUNDARY**(_url_). -> 1. Let _pjson_ be *undefined*. -> 1. If _pjsonURL_ is not *null*, then -> 1. Set _pjson_ to the result of **READ_JSON_FILE**(_pjsonURL_). -> 1. If _pjsonURL_ is *null* or **HAS_ESM_PROPERTIES**(_pjson_) is *true*, then +> 1. Let _pjson_ be the result of **READ_PACKAGE_BOUNDARY**(_url_). +> 1. If _pjson_ is *null* or **HAS_ESM_PROPERTIES**(_pjson_) is *true*, then > 1. If _url_ does not end in _".js"_ or _".mjs"_ then, > 1. Throw an _Unkonwn Module Format_ error. > 1. Return _"esm"_. @@ -262,6 +259,8 @@ READ_PACKAGE_BOUNDARY(_url_) > 1. Return _boundaryURL_. > 1. Set _boundaryURL_ to the URL resolution of _"../package.json"_ relative > to _boundaryURL_. +> 1. Let _pjson_ be the result of **READ_JSON_FILE**(_boundaryURL_). +> 1. Return _pjson_. > 1. Return *null*. READ_JSON_FILE(_url_) From 7ce173a5294d974534c01822421500638572488e Mon Sep 17 00:00:00 2001 From: guybedford Date: Fri, 14 Dec 2018 15:50:03 +0200 Subject: [PATCH 04/12] fixes, formatting, isMain handling --- doc/api/esm.md | 53 ++++++++++++++++++++++++-------------------------- 1 file changed, 25 insertions(+), 28 deletions(-) diff --git a/doc/api/esm.md b/doc/api/esm.md index c2732e0d76046c..245ea123f79ff5 100644 --- a/doc/api/esm.md +++ b/doc/api/esm.md @@ -18,14 +18,10 @@ interoperability support, specifier resolution, and default behavior. -The `--experimental-modules` flag can be used to enable features for loading -ESM modules. - -Once this has been set, files ending with `.mjs` will be able to be loaded -as ES Modules. +Modules are enabled by default for files ending with `.mjs`: ```sh -node --experimental-modules my-app.mjs +node my-app.mjs ``` ## Features @@ -154,15 +150,22 @@ The resolver has the following properties: ### Resolver Algorithm The algorithm to load an ES module specifier is given through the -**ESM_RESOLVE** method below. It returns the resolved URL for a +**ESM_RESOLVE** method below. It returns the resolved URL for a module specifier relative to a parentURL, in addition to the unique module format for that resolved URL given by the **ESM_FORMAT** routine. +The _"esm"_ format is returned for an ECMAScript Module, while the +_"legacy"_ format is used to indicate loading through the legacy +CommonJS loader. Additional formats such as _"wasm"_ or _"addon"_ can be +extended in future updates. + In the following algorithms, all subroutine errors are propogated as errors of these top-level routines. -#### ESM_RESOLVE(_specifier_, _parentURL_) -> 1. Let _resolvedURL_ be *undefined*. +_isMain_ is **true** when resolving the Node.js application entry point. + +#### ESM_RESOLVE(_specifier_, _parentURL_, _isMain_) +> 1. Let _resolvedURL_ be **undefined**. > 1. If _specifier_ is a valid URL, then > 1. Set _resolvedURL_ to the result of parsing and reserializing > _specifier_ as a URL. @@ -177,8 +180,8 @@ of these top-level routines. > **PACKAGE_RESOLVE**(_specifier_, _parentURL_). > 1. If the file at _resolvedURL_ does not exist, then > 1. Throw a _Module Not Found_ error. -> 1. Let _format_ be the result of **ESM_FORMAT**(_url_). -> 1. Return _{ resolvedURL, format }_. +> 1. Let _format_ be the result of **ESM_FORMAT**(_url_, _isMain_). +> 1. Load _resolvedURL_ as module format, _format_. PACKAGE_RESOLVE(_packageSpecifier_, _parentURL_) > 1. Let _packageName_ be *undefined*. @@ -213,7 +216,7 @@ PACKAGE_RESOLVE(_packageSpecifier_, _parentURL_) > 1. Continue the next loop iteration. > 1. If _packagePath_ is empty, then > 1. Let _url_ be the result of **PACKAGE_MAIN_RESOLVE**(_packageURL_). -> 1. If _url_ is *null*, then +> 1. If _url_ is **null**, then > 1. Throw a _Module Not Found_ error. > 1. Return _url_. > 1. Otherwise, @@ -222,34 +225,28 @@ PACKAGE_RESOLVE(_packageSpecifier_, _parentURL_) PACKAGE_MAIN_RESOLVE(_packageURL_) > 1. Let _pjsonURL_ be the URL of the file _"package.json"_ within the parent - path _packageURL_. +> path _packageURL_. > 1. If the file at _pjsonURL_ exists, then > 1. Let _pjson_ be the result of **READ_JSON_FILE**(_pjsonURL_). -> 1. If **HAS_ESM_PROPERTIES**(_pjson_) is *false*, then +> 1. If **HAS_ESM_PROPERTIES**(_pjson_) is **false**, then > 1. Let _mainURL_ be the result applying the legacy > **LOAD_AS_DIRECTORY** CommonJS resolver to _packageURL_, returning > *undefined* for no resolution. -> 1. If _mainURL_ is not *undefined* and **ESM_FORMAT**(_mainURL_) is not -> equal to _"cjs"_, then -> 1. Throw a _"Invalid Module Format"_ error. > 1. Return _mainURL_. > 1. _Note: ESM main yet to be implemented here._ -> 1. Return *null*. +> 1. Return **null**. -#### ESM_FORMAT(_url_) +#### ESM_FORMAT(_url_, _isMain_) > 1. Assert: _url_ corresponds to an existing file. > 1. Let _pjson_ be the result of **READ_PACKAGE_BOUNDARY**(_url_). -> 1. If _pjson_ is *null* or **HAS_ESM_PROPERTIES**(_pjson_) is *true*, then +> 1. If _pjson_ is **null** or **HAS_ESM_PROPERTIES**(_pjson_) is **true**, then > 1. If _url_ does not end in _".js"_ or _".mjs"_ then, -> 1. Throw an _Unkonwn Module Format_ error. +> 1. Throw an _Unsupported File Extension_ error. > 1. Return _"esm"_. > 1. Otherwise, -> 1. If _url_ does not end in _".js"_ then, -> 1. Throw an _Unknown Module Format_ error. -> 1. Return _"cjs"_. -> 1. If **HAS_ESM_PROPERTIES**(_pjson_) is *true*, then -> 1. Return _"esm"_. -> 1. Return _"cjs"_. +> 1. If _url_ ends with _".mjs"_, then +> 1. Throw an _Unsupported File Extension_ error. +> 1. Return _"legacy"_. READ_PACKAGE_BOUNDARY(_url_) > 1. Let _boundaryURL_ be the URL resolution of _"package.json"_ relative to @@ -261,7 +258,7 @@ READ_PACKAGE_BOUNDARY(_url_) > to _boundaryURL_. > 1. Let _pjson_ be the result of **READ_JSON_FILE**(_boundaryURL_). > 1. Return _pjson_. -> 1. Return *null*. +> 1. Return **null**. READ_JSON_FILE(_url_) > 1. If the file at _url_ does not parse as valid JSON, then From 67287cb36dbce363f02fcbf207a607858ee13383 Mon Sep 17 00:00:00 2001 From: guybedford Date: Tue, 11 Dec 2018 18:27:13 +0200 Subject: [PATCH 05/12] refactoring --- doc/api/esm.md | 37 ++++++++++++++++++------------------- 1 file changed, 18 insertions(+), 19 deletions(-) diff --git a/doc/api/esm.md b/doc/api/esm.md index 245ea123f79ff5..ebdfebd78ff05f 100644 --- a/doc/api/esm.md +++ b/doc/api/esm.md @@ -214,8 +214,13 @@ PACKAGE_RESOLVE(_packageSpecifier_, _parentURL_) > implementation. > 1. Set _parentURL_ to the parent URL path of _parentURL_. > 1. Continue the next loop iteration. +> 1. Let _pjsonURL_ be the URL of the file _"package.json"_ within the parent +> path _packageURL_. +> 1. Let _pjson_ be **null**. +> 1. If the file at _pjsonURL_ exists, then +> 1. Set _pjson_ to the result of **READ_JSON_FILE**(_pjsonURL_). > 1. If _packagePath_ is empty, then -> 1. Let _url_ be the result of **PACKAGE_MAIN_RESOLVE**(_packageURL_). +> 1. Let _url_ be the result of **PACKAGE_MAIN_RESOLVE**(_packageURL_, _pjson_). > 1. If _url_ is **null**, then > 1. Throw a _Module Not Found_ error. > 1. Return _url_. @@ -223,17 +228,17 @@ PACKAGE_RESOLVE(_packageSpecifier_, _parentURL_) > 1. Return the URL resolution of _packagePath_ in _packageURL_. > 1. Throw a _Module Not Found_ error. -PACKAGE_MAIN_RESOLVE(_packageURL_) +PACKAGE_MAIN_RESOLVE(_packageURL_, _pjson_) +> 1. If _pjson_ is **null**, then +> 1. Return **null**. > 1. Let _pjsonURL_ be the URL of the file _"package.json"_ within the parent > path _packageURL_. -> 1. If the file at _pjsonURL_ exists, then -> 1. Let _pjson_ be the result of **READ_JSON_FILE**(_pjsonURL_). -> 1. If **HAS_ESM_PROPERTIES**(_pjson_) is **false**, then -> 1. Let _mainURL_ be the result applying the legacy -> **LOAD_AS_DIRECTORY** CommonJS resolver to _packageURL_, returning -> *undefined* for no resolution. -> 1. Return _mainURL_. -> 1. _Note: ESM main yet to be implemented here._ +> 1. If **HAS_ESM_PROPERTIES**(_pjson_) is **false**, then +> 1. Let _mainURL_ be the result applying the legacy +> **LOAD_AS_DIRECTORY** CommonJS resolver to _packageURL_, returning +> *undefined* for no resolution. +> 1. Return _mainURL_. +> 1. TODO: ESM main handling. > 1. Return **null**. #### ESM_FORMAT(_url_, _isMain_) @@ -253,11 +258,10 @@ READ_PACKAGE_BOUNDARY(_url_) > _url_. > 1. While _boundaryURL_ is not the file system root, > 1. If the file at _boundaryURL_ exists, then -> 1. Return _boundaryURL_. +> 1. Let _pjson_ be the result of **READ_JSON_FILE**(_boundaryURL_). +> 1. Return _pjson_. > 1. Set _boundaryURL_ to the URL resolution of _"../package.json"_ relative > to _boundaryURL_. -> 1. Let _pjson_ be the result of **READ_JSON_FILE**(_boundaryURL_). -> 1. Return _pjson_. > 1. Return **null**. READ_JSON_FILE(_url_) @@ -267,12 +271,7 @@ READ_JSON_FILE(_url_) > 1. Return _pjson_. HAS_ESM_PROPERTIES(_pjson_) -> 1. Note: To be specified. - -_ESM properties_ in a package.json file are yet to be specified. -The current possible specifications for this are the -[_"exports"_](https://github.com/jkrems/proposal-pkg-exports) -or [_"mode"_](https://github.com/nodejs/node/pull/18392) flag. +> Note: To be specified. [Node.js EP for ES Modules]: https://github.com/nodejs/node-eps/blob/master/002-es-modules.md [`module.createRequireFromPath()`]: modules.html#modules_module_createrequirefrompath_filename From 63887b76ef187b6a3528b0d5a83518c3079c3d8d Mon Sep 17 00:00:00 2001 From: guybedford Date: Wed, 19 Dec 2018 16:43:27 +0200 Subject: [PATCH 06/12] mjs, esm default updates --- doc/api/esm.md | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/doc/api/esm.md b/doc/api/esm.md index ebdfebd78ff05f..d00244cca4aedf 100644 --- a/doc/api/esm.md +++ b/doc/api/esm.md @@ -164,7 +164,7 @@ of these top-level routines. _isMain_ is **true** when resolving the Node.js application entry point. -#### ESM_RESOLVE(_specifier_, _parentURL_, _isMain_) +**ESM_RESOLVE(_specifier_, _parentURL_, _isMain_)** > 1. Let _resolvedURL_ be **undefined**. > 1. If _specifier_ is a valid URL, then > 1. Set _resolvedURL_ to the result of parsing and reserializing @@ -229,8 +229,6 @@ PACKAGE_RESOLVE(_packageSpecifier_, _parentURL_) > 1. Throw a _Module Not Found_ error. PACKAGE_MAIN_RESOLVE(_packageURL_, _pjson_) -> 1. If _pjson_ is **null**, then -> 1. Return **null**. > 1. Let _pjsonURL_ be the URL of the file _"package.json"_ within the parent > path _packageURL_. > 1. If **HAS_ESM_PROPERTIES**(_pjson_) is **false**, then @@ -241,17 +239,21 @@ PACKAGE_MAIN_RESOLVE(_packageURL_, _pjson_) > 1. TODO: ESM main handling. > 1. Return **null**. -#### ESM_FORMAT(_url_, _isMain_) +**ESM_FORMAT(_url_, _isMain_)** > 1. Assert: _url_ corresponds to an existing file. > 1. Let _pjson_ be the result of **READ_PACKAGE_BOUNDARY**(_url_). -> 1. If _pjson_ is **null** or **HAS_ESM_PROPERTIES**(_pjson_) is **true**, then -> 1. If _url_ does not end in _".js"_ or _".mjs"_ then, +> 1. If _pjson_ is **null** and _isMain_ is **true**, then +> 1. Note: This path is for backwards compatibility and may be deprecated. +> 1. Return _"legacy"_. +> 1. If **HAS_ESM_PROPERTIES**(_pjson_) is **true**, then +> 1. If _url_ does not end in _".js"_ or _".mjs"_, then > 1. Throw an _Unsupported File Extension_ error. > 1. Return _"esm"_. > 1. Otherwise, -> 1. If _url_ ends with _".mjs"_, then -> 1. Throw an _Unsupported File Extension_ error. -> 1. Return _"legacy"_. +> 1. If _url_ ends in _".mjs"_, then +> 1. Return _"esm"_. +> 1. Otherwise, +> 1. Return _"legacy"_. READ_PACKAGE_BOUNDARY(_url_) > 1. Let _boundaryURL_ be the URL resolution of _"package.json"_ relative to From 77cd8cf9b69d36b7732a67da82668040e185a2e7 Mon Sep 17 00:00:00 2001 From: guybedford Date: Wed, 19 Dec 2018 17:46:22 +0200 Subject: [PATCH 07/12] lint fix --- doc/api/esm.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/doc/api/esm.md b/doc/api/esm.md index d00244cca4aedf..3b3152d6917f41 100644 --- a/doc/api/esm.md +++ b/doc/api/esm.md @@ -220,7 +220,8 @@ PACKAGE_RESOLVE(_packageSpecifier_, _parentURL_) > 1. If the file at _pjsonURL_ exists, then > 1. Set _pjson_ to the result of **READ_JSON_FILE**(_pjsonURL_). > 1. If _packagePath_ is empty, then -> 1. Let _url_ be the result of **PACKAGE_MAIN_RESOLVE**(_packageURL_, _pjson_). +> 1. Let _url_ be the result of +> **PACKAGE_MAIN_RESOLVE**(_packageURL_, _pjson_). > 1. If _url_ is **null**, then > 1. Throw a _Module Not Found_ error. > 1. Return _url_. From 4b1bd2f90eea8e3a99817b0f7acdd8e086ae9576 Mon Sep 17 00:00:00 2001 From: Guy Bedford Date: Sun, 13 Jan 2019 21:57:15 +0200 Subject: [PATCH 08/12] some spec refactoring --- doc/api/esm.md | 62 ++++++++++++++++++++++---------------------------- 1 file changed, 27 insertions(+), 35 deletions(-) diff --git a/doc/api/esm.md b/doc/api/esm.md index 3b3152d6917f41..308bd2fd3a2178 100644 --- a/doc/api/esm.md +++ b/doc/api/esm.md @@ -185,24 +185,26 @@ _isMain_ is **true** when resolving the Node.js application entry point. PACKAGE_RESOLVE(_packageSpecifier_, _parentURL_) > 1. Let _packageName_ be *undefined*. -> 1. Let _packagePath_ be *undefined*. +> 1. Let _packageSubpath_ be *undefined*. > 1. If _packageSpecifier_ is an empty string, then -> 1. Throw an _Invalid Package Name_ error. +> 1. Throw an _Invalid Specifier_ error. > 1. If _packageSpecifier_ does not start with _"@"_, then > 1. Set _packageName_ to the substring of _packageSpecifier_ until the > first _"/"_ separator or the end of the string. > 1. Otherwise, > 1. If _packageSpecifier_ does not contain a _"/"_ separator, then -> 1. Throw an _Invalid Package Name_ error. +> 1. Throw an _Invalid Specifier_ error. > 1. Set _packageName_ to the substring of _packageSpecifier_ > until the second _"/"_ separator or the end of the string. -> 1. Let _packagePath_ be the substring of _packageSpecifier_ from the +> 1. Let _packageSubpath_ be the substring of _packageSpecifier_ from the > position at the length of _packageName_ plus one, if any. > 1. Assert: _packageName_ is a valid package name or scoped package name. -> 1. Assert: _packagePath_ is either empty, or a path without a leading +> 1. Assert: _packageSubpath_ is either empty, or a path without a leading > separator. -> 1. Note: Further package name validations can be added here. -> 1. If _packagePath_ is empty and _packageName_ is a Node.js builtin +> 1. If _packageSubpath_ contains any _"."_ or _".."_ segments or percent +> encoded strings for _"/"_ or _"\"_ then, +> 1. Throw an _Invalid Specifier_ error. +> 1. If _packageSubpath_ is empty and _packageName_ is a Node.js builtin > module, then > 1. Return the string _"node:"_ concatenated with _packageSpecifier_. > 1. While _parentURL_ is not the file system root, @@ -210,23 +212,14 @@ PACKAGE_RESOLVE(_packageSpecifier_, _parentURL_) > 1. Let _packageURL_ be the URL resolution of the string concatenation of > _parentURL_, _"/node_modules/"_ and _packageSpecifier_. > 1. If the folder at _packageURL_ does not exist, then -> 1. Note: This check can be optimized out where possible in -> implementation. > 1. Set _parentURL_ to the parent URL path of _parentURL_. > 1. Continue the next loop iteration. -> 1. Let _pjsonURL_ be the URL of the file _"package.json"_ within the parent -> path _packageURL_. -> 1. Let _pjson_ be **null**. -> 1. If the file at _pjsonURL_ exists, then -> 1. Set _pjson_ to the result of **READ_JSON_FILE**(_pjsonURL_). -> 1. If _packagePath_ is empty, then -> 1. Let _url_ be the result of -> **PACKAGE_MAIN_RESOLVE**(_packageURL_, _pjson_). -> 1. If _url_ is **null**, then -> 1. Throw a _Module Not Found_ error. -> 1. Return _url_. +> 1. Let _pjson_be the result of **READ_PACKAGE_JSON**(_packageURL_). +> 1. If _packageSubpath_ is empty, then +> 1. Return the result of **PACKAGE_MAIN_RESOLVE**(_packageURL_, +> _pjson_). > 1. Otherwise, -> 1. Return the URL resolution of _packagePath_ in _packageURL_. +> 1. Return the URL resolution of _packageSubpath_ in _packageURL_. > 1. Throw a _Module Not Found_ error. PACKAGE_MAIN_RESOLVE(_packageURL_, _pjson_) @@ -234,17 +227,16 @@ PACKAGE_MAIN_RESOLVE(_packageURL_, _pjson_) > path _packageURL_. > 1. If **HAS_ESM_PROPERTIES**(_pjson_) is **false**, then > 1. Let _mainURL_ be the result applying the legacy -> **LOAD_AS_DIRECTORY** CommonJS resolver to _packageURL_, returning -> *undefined* for no resolution. +> **LOAD_AS_DIRECTORY** CommonJS resolver to _packageURL_, throwing a +> _Module Not Found_ error for no resolution. > 1. Return _mainURL_. > 1. TODO: ESM main handling. -> 1. Return **null**. +> 1. Throw a _Module Not Found_ error. **ESM_FORMAT(_url_, _isMain_)** > 1. Assert: _url_ corresponds to an existing file. > 1. Let _pjson_ be the result of **READ_PACKAGE_BOUNDARY**(_url_). > 1. If _pjson_ is **null** and _isMain_ is **true**, then -> 1. Note: This path is for backwards compatibility and may be deprecated. > 1. Return _"legacy"_. > 1. If **HAS_ESM_PROPERTIES**(_pjson_) is **true**, then > 1. If _url_ does not end in _".js"_ or _".mjs"_, then @@ -257,21 +249,21 @@ PACKAGE_MAIN_RESOLVE(_packageURL_, _pjson_) > 1. Return _"legacy"_. READ_PACKAGE_BOUNDARY(_url_) -> 1. Let _boundaryURL_ be the URL resolution of _"package.json"_ relative to -> _url_. +> 1. Let _boundaryURL_ be _url_. > 1. While _boundaryURL_ is not the file system root, -> 1. If the file at _boundaryURL_ exists, then -> 1. Let _pjson_ be the result of **READ_JSON_FILE**(_boundaryURL_). +> 1. Let _pjson_ be the result of **READ_PACKAGE_JSON**(_boundaryURL_). +> 1. If _pjson_ is not **null**, then > 1. Return _pjson_. -> 1. Set _boundaryURL_ to the URL resolution of _"../package.json"_ relative -> to _boundaryURL_. +> 1. Set _boundaryURL_ to the parent URL of _boundaryURL_. > 1. Return **null**. -READ_JSON_FILE(_url_) -> 1. If the file at _url_ does not parse as valid JSON, then +READ_PACKAGE_JSON(_packageURL_) +> 1. Let _pjsonURL_ be the resolution of _"package.json"_ within _packageURL_. +> 1. If the file at _pjsonURL_ does not exist, then +> 1. Return **null**. +> 1. If the file at _packageURL_ does not parse as valid JSON, then > 1. Throw an _Invalid Package Configuration_ error. -> 1. Let _pjson_ be the parsed JSON source of the file at _url_. -> 1. Return _pjson_. +> 1. Return the parsed JSON source of the file at _url_. HAS_ESM_PROPERTIES(_pjson_) > Note: To be specified. From 73fb81d3f83ed6777e67a50677fe840d95561ace Mon Sep 17 00:00:00 2001 From: guybedford Date: Tue, 11 Dec 2018 18:27:13 +0200 Subject: [PATCH 09/12] exports spec --- doc/api/esm.md | 60 +++++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 57 insertions(+), 3 deletions(-) diff --git a/doc/api/esm.md b/doc/api/esm.md index 308bd2fd3a2178..cb3aa0ed3fe273 100644 --- a/doc/api/esm.md +++ b/doc/api/esm.md @@ -219,7 +219,10 @@ PACKAGE_RESOLVE(_packageSpecifier_, _parentURL_) > 1. Return the result of **PACKAGE_MAIN_RESOLVE**(_packageURL_, > _pjson_). > 1. Otherwise, -> 1. Return the URL resolution of _packageSubpath_ in _packageURL_. +> 1. If **HAS_ESM_PROPERTIES**(_pjson_) is *true*, then +> 1. Return **PACKAGE_EXPORTS_RESOLVE**(_packageURL_, _packagePath_, +> _pjson_). +> 1. Return the URL resolution of _packagePath_ in _packageURL_. > 1. Throw a _Module Not Found_ error. PACKAGE_MAIN_RESOLVE(_packageURL_, _pjson_) @@ -230,9 +233,58 @@ PACKAGE_MAIN_RESOLVE(_packageURL_, _pjson_) > **LOAD_AS_DIRECTORY** CommonJS resolver to _packageURL_, throwing a > _Module Not Found_ error for no resolution. > 1. Return _mainURL_. -> 1. TODO: ESM main handling. +> 1. If _pjson.exports_ is a String, then +> 1. Return the URL of _pjson.exports_ within the parent path _packageURL_. +> 1. Assert: _pjson.exports_ is an Object. +> 1. If _pjson.exports["."]_ is a String, then +> 1. Let _target_ be _pjson.exports["."]_. +> 1. If **IS_VALID_EXPORTS_TARGET**(_target_) is **false**, then +> 1. Emit an _"Invalid Exports Target"_ warning. +> 1. Otherwise, +> 1. Return the URL of _pjson.exports.default_ within the parent path +> _packageURL_. +> 1. Return **null**. + +PACKAGE_EXPORTS_RESOLVE(_packageURL_, _packagePath_, _pjson_) +> 1. Assert: _pjson_ is not **null**. +> 1. If _pjson.exports_ is a String, then +> 1. Throw a _Module Not Found_ error. +> 1. Assert: _pjson.exports_ is an Object. +> 1. Set _packagePath_ to _"./"_ concatenated with _packagePath_. +> 1. If _packagePath_ is a key of _pjson.exports_, then +> 1. Let _target_ be the value of _pjson.exports[packagePath]_. +> 1. If **IS_VALID_EXPORTS_TARGET**(_target_) is **false**, then +> 1. Emit an _"Invalid Exports Target"_ warning. +> 1. Throw a _Module Not Found_ error. +> 1. Return the URL resolution of the concatenation of _packageURL_ and +> _target_. +> 1. Let _directoryKeys_ be the list of keys of _pjson.exports_ ending in +> _"/"_, sorted by length descending. +> 1. For each key _directory_ in _directoryKeys_, do +> 1. If _packagePath_ starts with _directory_, then +> 1. Let _target_ be the value of _pjson.exports[directory]_. +> 1. If **IS_VALID_EXPORTS_TARGET**(_target_) is **false** or _target_ +> does not end in _"/"_, then +> 1. Emit an _"Invalid Exports Target"_ warning. +> 1. Continue the loop. +> 1. Let _subpath_ be the substring of _target_ starting at the index of +> the length of _directory_. +> 1. Return the URL resolution of the concatenation of _packageURL_, +> _target_ and _subpath_. > 1. Throw a _Module Not Found_ error. +IS_VALID_EXPORTS_TARGET(_target_) +> 1. If _target_ is not a valid String, then +> 1. Return **false**. +> 1. If _target_ does not start with _"./"_, then +> 1. Return **false**. +> 1. If _target_ contains any _".."_ or _"."_ path segments, then +> 1. Return **false**. +> 1. If _target_ contains any percent-encoded characters for _"/"_ or _"\"_, +> then +> 1. Return **false**. +> 1. Return **true**. + **ESM_FORMAT(_url_, _isMain_)** > 1. Assert: _url_ corresponds to an existing file. > 1. Let _pjson_ be the result of **READ_PACKAGE_BOUNDARY**(_url_). @@ -266,7 +318,9 @@ READ_PACKAGE_JSON(_packageURL_) > 1. Return the parsed JSON source of the file at _url_. HAS_ESM_PROPERTIES(_pjson_) -> Note: To be specified. +> 1. If _pjson_ is not **null** and _pjson.exports_ is a String or Object, then +> 1. Return *true*. +> 1. Return *false*. [Node.js EP for ES Modules]: https://github.com/nodejs/node-eps/blob/master/002-es-modules.md [`module.createRequireFromPath()`]: modules.html#modules_module_createrequirefrompath_filename From ae33d0712e1f9af88683a12f6387c5775fad3ed4 Mon Sep 17 00:00:00 2001 From: guybedford Date: Fri, 14 Dec 2018 15:28:30 +0200 Subject: [PATCH 10/12] file resolution specifier proposal, package exports proposal, unflagged --- doc/api/cli.md | 7 - doc/api/errors.md | 16 +- lib/internal/bootstrap/loaders.js | 2 +- lib/internal/bootstrap/node.js | 29 +- lib/internal/errors.js | 11 +- lib/internal/modules/cjs/loader.js | 91 ++-- lib/internal/modules/esm/default_resolve.js | 56 +-- lib/internal/process/esm_loader.js | 4 +- src/env.h | 28 +- src/module_wrap.cc | 452 ++++++++++++++++++-- src/module_wrap.h | 11 +- src/node_errors.h | 2 + src/node_options.cc | 4 - 13 files changed, 519 insertions(+), 194 deletions(-) diff --git a/doc/api/cli.md b/doc/api/cli.md index 46b5295ccb151a..9999713a8207ba 100644 --- a/doc/api/cli.md +++ b/doc/api/cli.md @@ -83,13 +83,6 @@ added: v6.0.0 Enable FIPS-compliant crypto at startup. (Requires Node.js to be built with `./configure --openssl-fips`.) -### `--experimental-modules` - - -Enable experimental ES module support and caching modules. - ### `--experimental-repl-await`