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` -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 @@ -55,10 +51,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 +149,179 @@ 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. + +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. -**ESM_RESOLVE**(_specifier_, _parentURL_) -> 1. Let _resolvedURL_ be _undefined_. -> 1. If _specifier_ is a valid URL then, +In the following algorithms, all subroutine errors are propogated as errors +of these top-level routines. + +_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. -> 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_, _isMain_). +> 1. Load _resolvedURL_ as module format, _format_. + +PACKAGE_RESOLVE(_packageSpecifier_, _parentURL_) +> 1. Let _packageName_ be *undefined*. +> 1. Let _packageSubpath_ be *undefined*. +> 1. If _packageSpecifier_ is an empty string, then +> 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 Specifier_ error. +> 1. Set _packageName_ to the substring of _packageSpecifier_ +> until the second _"/"_ separator or the end of the string. +> 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: _packageSubpath_ is either empty, or a path without a leading +> separator. +> 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_ 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. Set _parentURL_ to the parent URL path of _parentURL_. +> 1. Continue the next loop iteration. +> 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. 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_) +> 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 +> 1. Let _mainURL_ be the result applying the legacy +> **LOAD_AS_DIRECTORY** CommonJS resolver to _packageURL_, throwing a +> _Module Not Found_ error for no resolution. +> 1. Return _mainURL_. +> 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_). +> 1. If _pjson_ is **null** and _isMain_ is **true**, then +> 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 in _".mjs"_, then +> 1. Return _"esm"_. +> 1. Otherwise, +> 1. Return _"legacy"_. + +READ_PACKAGE_BOUNDARY(_url_) +> 1. Let _boundaryURL_ be _url_. +> 1. While _boundaryURL_ is not the file system root, +> 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 parent URL of _boundaryURL_. +> 1. Return **null**. + +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. Return the parsed JSON source of the file at _url_. + +HAS_ESM_PROPERTIES(_pjson_) +> 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 [ESM Minimal Kernel]: https://github.com/nodejs/modules/blob/master/doc/plan-for-new-modules-implementation.md diff --git a/lib/internal/bootstrap/loaders.js b/lib/internal/bootstrap/loaders.js index 300a362abb5f56..06e181fc66566e 100644 --- a/lib/internal/bootstrap/loaders.js +++ b/lib/internal/bootstrap/loaders.js @@ -316,7 +316,7 @@ NativeModule.prototype.compile = function() { const fn = compileFunction(id); fn(this.exports, requireFn, this, process, internalBinding); - if (config.experimentalModules && this.canBeRequiredByUsers) { + if (this.canBeRequiredByUsers) { this.proxifyExports(); } diff --git a/lib/internal/bootstrap/node.js b/lib/internal/bootstrap/node.js index 840b0c73f049b1..cdacbc53152436 100644 --- a/lib/internal/bootstrap/node.js +++ b/lib/internal/bootstrap/node.js @@ -217,15 +217,7 @@ function startup() { 'DeprecationWarning', 'DEP0062', startup, true); } - const experimentalModules = getOptionValue('--experimental-modules'); - if (experimentalModules) { - if (experimentalModules) { - process.emitWarning( - 'The ESM module loader is experimental.', - 'ExperimentalWarning', undefined); - } - NativeModule.require('internal/process/esm_loader').setup(); - } + NativeModule.require('internal/process/esm_loader').setup(); const { deprecate } = NativeModule.require('internal/util'); { @@ -425,11 +417,20 @@ function executeUserCode() { process.exit(0); } - // Note: this actually tries to run the module as a ESM first if - // --experimental-modules is on. - // TODO(joyeecheung): can we move that logic to here? Note that this - // is an undocumented method available via `require('module').runMain` - CJSModule.runMain(); + // Load the main module--the command line argument. + const { pathToFileURL } = NativeModule.require('url'); + const asyncESM = NativeModule.require('internal/process/esm_loader'); + const decorateErrorStack = + NativeModule.require('internal/util').decorateErrorStack; + asyncESM.loaderPromise.then((loader) => + loader.import(pathToFileURL(process.argv[1]).pathname) + ) + .catch((e) => { + decorateErrorStack(e); + console.error(e); + process.exit(1); + }); + process._tickCallback(); return; } diff --git a/lib/internal/errors.js b/lib/internal/errors.js index dcbc439950e8ca..e2332e9c0cece3 100644 --- a/lib/internal/errors.js +++ b/lib/internal/errors.js @@ -842,13 +842,6 @@ E('ERR_MISSING_ARGS', E('ERR_MISSING_DYNAMIC_INSTANTIATE_HOOK', 'The ES Module loader may not return a format of \'dynamic\' when no ' + 'dynamicInstantiate function was provided', Error); -E('ERR_MODULE_NOT_FOUND', (module, base, legacyResolution) => { - let msg = `Cannot find module '${module}' imported from ${base}.`; - if (legacyResolution) - msg += ' Legacy behavior in require() would have found it at ' + - legacyResolution; - return msg; -}, Error); E('ERR_MULTIPLE_CALLBACK', 'Callback called multiple times', Error); E('ERR_NAPI_CONS_FUNCTION', 'Constructor must be a function', TypeError); E('ERR_NAPI_INVALID_DATAVIEW_ARGS', @@ -947,10 +940,10 @@ E('ERR_UNKNOWN_CREDENTIAL', '%s identifier does not exist: %s', Error); E('ERR_UNKNOWN_ENCODING', 'Unknown encoding: %s', TypeError); // This should probably be a `TypeError`. -E('ERR_UNKNOWN_FILE_EXTENSION', 'Unknown file extension: \'%s\' imported ' + - 'from %s', Error); E('ERR_UNKNOWN_MODULE_FORMAT', 'Unknown module format: %s', RangeError); E('ERR_UNKNOWN_SIGNAL', 'Unknown signal: %s', TypeError); +E('ERR_UNSUPPORTED_FILE_EXTENSION', 'Unsupported file extension: \'%s\' ' + + 'imported from %s', Error); E('ERR_V8BREAKITERATOR', 'Full ICU data not installed. See https://github.com/nodejs/node/wiki/Intl', diff --git a/lib/internal/modules/cjs/loader.js b/lib/internal/modules/cjs/loader.js index 5e91da5c3dbfe7..f22dd6cca88fb8 100644 --- a/lib/internal/modules/cjs/loader.js +++ b/lib/internal/modules/cjs/loader.js @@ -44,7 +44,6 @@ const { const { getOptionValue } = require('internal/options'); const preserveSymlinks = getOptionValue('--preserve-symlinks'); const preserveSymlinksMain = getOptionValue('--preserve-symlinks-main'); -const experimentalModules = getOptionValue('--experimental-modules'); const { ERR_INVALID_ARG_VALUE, @@ -54,18 +53,10 @@ const { validateString } = require('internal/validators'); module.exports = Module; -let asyncESM; -let ModuleJob; -let createDynamicModule; -let decorateErrorStack; - -function lazyLoadESM() { - asyncESM = require('internal/process/esm_loader'); - ModuleJob = require('internal/modules/esm/module_job'); - createDynamicModule = require( - 'internal/modules/esm/create_dynamic_module'); - decorateErrorStack = require('internal/util').decorateErrorStack; -} +const asyncESM = require('internal/process/esm_loader'); +const ModuleJob = require('internal/modules/esm/module_job'); +const createDynamicModule = require( + 'internal/modules/esm/create_dynamic_module'); const { CHAR_UPPERCASE_A, @@ -621,28 +612,26 @@ Module.prototype.load = function(filename) { Module._extensions[extension](this, filename); this.loaded = true; - if (experimentalModules) { - if (asyncESM === undefined) lazyLoadESM(); - const ESMLoader = asyncESM.ESMLoader; - const url = `${pathToFileURL(filename)}`; - const module = ESMLoader.moduleMap.get(url); - // Create module entry at load time to snapshot exports correctly - const exports = this.exports; - if (module !== undefined) { // called from cjs translator - module.reflect.onReady((reflect) => { - reflect.exports.default.set(exports); - }); - } else { // preemptively cache - ESMLoader.moduleMap.set( - url, - new ModuleJob(ESMLoader, url, async () => { - return createDynamicModule( - ['default'], url, (reflect) => { - reflect.exports.default.set(exports); - }); - }) - ); - } + const ESMLoader = asyncESM.ESMLoader; + const url = `${pathToFileURL(filename)}`; + const module = ESMLoader.moduleMap.get(url); + // Create module entry at load time to snapshot exports correctly. + const exports = this.exports; + if (module !== undefined) { // called from cjs translator + module.reflect.onReady((reflect) => { + reflect.exports.default.set(exports); + }); + // Preemptively cache for ESM loader. + } else { + ESMLoader.cjsCache.set( + url, + new ModuleJob(ESMLoader, url, async () => { + return createDynamicModule( + ['default'], url, (reflect) => { + reflect.exports.default.set(exports); + }); + }) + ); } }; @@ -686,11 +675,10 @@ Module.prototype._compile = function(content, filename) { filename: filename, lineOffset: 0, displayErrors: true, - importModuleDynamically: experimentalModules ? async (specifier) => { - if (asyncESM === undefined) lazyLoadESM(); + importModuleDynamically: async (specifier) => { const loader = await asyncESM.loaderPromise; return loader.import(specifier, normalizeReferrerURL(filename)); - } : undefined, + }, }); var inspectorWrapper = null; @@ -751,33 +739,13 @@ Module._extensions['.node'] = function(module, filename) { return process.dlopen(module, path.toNamespacedPath(filename)); }; -if (experimentalModules) { - if (asyncESM === undefined) lazyLoadESM(); - Module._extensions['.mjs'] = function(module, filename) { - throw new ERR_REQUIRE_ESM(filename); - }; -} +Module._extensions['.mjs'] = function(module, filename) { + throw new ERR_REQUIRE_ESM(filename); +}; // bootstrap main module. Module.runMain = function() { - // Load the main module--the command line argument. - const base = path.basename(process.argv[1]); - const ext = path.extname(base); - const isESM = ext === '.mjs'; - - if (experimentalModules && isESM) { - if (asyncESM === undefined) lazyLoadESM(); - asyncESM.loaderPromise.then((loader) => { - return loader.import(pathToFileURL(process.argv[1]).pathname); - }) - .catch((e) => { - decorateErrorStack(e); - console.error(e); - process.exit(1); - }); - } else { - Module._load(process.argv[1], null, true); - } + Module._load(process.argv[1], null, true); // Handle any nextTicks added in the first tick of the program process._tickCallback(); }; diff --git a/lib/internal/modules/esm/default_resolve.js b/lib/internal/modules/esm/default_resolve.js index 188313f3042eb8..1e784e710a566e 100644 --- a/lib/internal/modules/esm/default_resolve.js +++ b/lib/internal/modules/esm/default_resolve.js @@ -1,7 +1,5 @@ 'use strict'; -const { URL } = require('url'); -const CJSmodule = require('internal/modules/cjs/loader'); const internalFS = require('internal/fs/utils'); const { NativeModule } = require('internal/bootstrap/loaders'); const { extname } = require('path'); @@ -9,38 +7,24 @@ const { realpathSync } = require('fs'); const { getOptionValue } = require('internal/options'); const preserveSymlinks = getOptionValue('--preserve-symlinks'); const preserveSymlinksMain = getOptionValue('--preserve-symlinks-main'); -const { - ERR_MODULE_NOT_FOUND, - ERR_UNKNOWN_FILE_EXTENSION -} = require('internal/errors').codes; +const { ERR_UNSUPPORTED_FILE_EXTENSION } = require('internal/errors').codes; const { resolve: moduleWrapResolve } = internalBinding('module_wrap'); const { pathToFileURL, fileURLToPath } = require('internal/url'); const realpathCache = new Map(); -function search(target, base) { - try { - return moduleWrapResolve(target, base); - } catch (e) { - e.stack; // cause V8 to generate stack before rethrow - let error = e; - try { - const questionedBase = new URL(base); - const tmpMod = new CJSmodule(questionedBase.pathname, null); - tmpMod.paths = CJSmodule._nodeModulePaths( - new URL('./', questionedBase).pathname); - const found = CJSmodule._resolveFilename(target, tmpMod); - error = new ERR_MODULE_NOT_FOUND(target, fileURLToPath(base), found); - } catch { - // ignore - } - throw error; - } -} - const extensionFormatMap = { '__proto__': null, - '.mjs': 'esm' + '.mjs': 'esm', + '.js': 'esm' +}; + +const legacyExtensionFormatMap = { + '__proto__': null, + '.js': 'cjs', + '.json': 'cjs', + '.mjs': 'esm', + '.node': 'cjs' }; function resolve(specifier, parentURL) { @@ -51,10 +35,14 @@ function resolve(specifier, parentURL) { }; } - let url = search(specifier, - parentURL || pathToFileURL(`${process.cwd()}/`).href); - const isMain = parentURL === undefined; + if (isMain) + parentURL = pathToFileURL(`${process.cwd()}/`).href; + + const resolved = moduleWrapResolve(specifier, parentURL, isMain); + + let url = resolved.url; + const legacy = resolved.legacy; if (isMain ? !preserveSymlinksMain : !preserveSymlinks) { const real = realpathSync(fileURLToPath(url), { @@ -67,14 +55,14 @@ function resolve(specifier, parentURL) { } const ext = extname(url.pathname); + let format = (legacy ? legacyExtensionFormatMap : extensionFormatMap)[ext]; - let format = extensionFormatMap[ext]; if (!format) { if (isMain) - format = 'cjs'; + format = legacy ? 'cjs' : 'esm'; else - throw new ERR_UNKNOWN_FILE_EXTENSION(url.pathname, - fileURLToPath(parentURL)); + throw new ERR_UNSUPPORTED_FILE_EXTENSION(fileURLToPath(url), + fileURLToPath(parentURL)); } return { url: `${url}`, format }; diff --git a/lib/internal/modules/esm/loader.js b/lib/internal/modules/esm/loader.js index a1a16219090e90..1776d3da483f0e 100644 --- a/lib/internal/modules/esm/loader.js +++ b/lib/internal/modules/esm/loader.js @@ -14,7 +14,7 @@ const ModuleJob = require('internal/modules/esm/module_job'); const defaultResolve = require('internal/modules/esm/default_resolve'); const createDynamicModule = require( 'internal/modules/esm/create_dynamic_module'); -const translators = require('internal/modules/esm/translators'); +const { translators } = require('internal/modules/esm/translators'); const FunctionBind = Function.call.bind(Function.prototype.bind); @@ -32,6 +32,9 @@ class Loader { // Registry of loaded modules, akin to `require.cache` this.moduleMap = new ModuleMap(); + // map of already-loaded CJS modules to use + this.cjsCache = new Map(); + // The resolver has the signature // (specifier : string, parentURL : string, defaultResolve) // -> Promise<{ url : string, format: string }> diff --git a/lib/internal/modules/esm/module_job.js b/lib/internal/modules/esm/module_job.js index fbb29aef783006..50cc7a9b4640f9 100644 --- a/lib/internal/modules/esm/module_job.js +++ b/lib/internal/modules/esm/module_job.js @@ -17,7 +17,7 @@ class ModuleJob { // This is a Promise<{ module, reflect }>, whose fields will be copied // onto `this` by `link()` below once it has been resolved. - this.modulePromise = moduleProvider(url, isMain); + this.modulePromise = moduleProvider.call(loader, url, isMain); this.module = undefined; this.reflect = undefined; diff --git a/lib/internal/modules/esm/translators.js b/lib/internal/modules/esm/translators.js index 9e0b878838b1b2..71b613a6ff2dd4 100644 --- a/lib/internal/modules/esm/translators.js +++ b/lib/internal/modules/esm/translators.js @@ -21,7 +21,7 @@ const StringReplace = Function.call.bind(String.prototype.replace); const debug = debuglog('esm'); const translators = new SafeMap(); -module.exports = translators; +exports.translators = translators; function initializeImportMeta(meta, { url }) { meta.url = url; @@ -33,7 +33,7 @@ async function importModuleDynamically(specifier, { url }) { } // Strategy for loading a standard JavaScript module -translators.set('esm', async (url) => { +translators.set('esm', async function(url) { const source = `${await readFileAsync(new URL(url))}`; debug(`Translating StandardModule ${url}`); const module = new ModuleWrap(stripShebang(source), url); @@ -50,9 +50,14 @@ translators.set('esm', async (url) => { // Strategy for loading a node-style CommonJS module const isWindows = process.platform === 'win32'; const winSepRegEx = /\//g; -translators.set('cjs', async (url, isMain) => { +translators.set('cjs', async function(url, isMain) { debug(`Translating CJSModule ${url}`); const pathname = internalURLModule.fileURLToPath(new URL(url)); + const cached = this.cjsCache.get(url); + if (cached) { + this.cjsCache.delete(url); + return cached; + } const module = CJSModule._cache[ isWindows ? StringReplace(pathname, winSepRegEx, '\\') : pathname]; if (module && module.loaded) { @@ -72,7 +77,7 @@ translators.set('cjs', async (url, isMain) => { // Strategy for loading a node builtin CommonJS module that isn't // through normal resolution -translators.set('builtin', async (url) => { +translators.set('builtin', async function(url) { debug(`Translating BuiltinModule ${url}`); // slice 'node:' scheme const id = url.slice(5); diff --git a/lib/internal/process/esm_loader.js b/lib/internal/process/esm_loader.js index 9d8df07c055c07..20e6bceba967e0 100644 --- a/lib/internal/process/esm_loader.js +++ b/lib/internal/process/esm_loader.js @@ -38,9 +38,7 @@ setInitializeImportMetaObjectCallback(initializeImportMetaObject); setImportModuleDynamicallyCallback(importModuleDynamicallyCallback); let loaderResolve; -exports.loaderPromise = new Promise((resolve, reject) => { - loaderResolve = resolve; -}); +exports.loaderPromise = new Promise((resolve) => loaderResolve = resolve); exports.ESMLoader = undefined; diff --git a/src/env.h b/src/env.h index fdd5c2382cb7c1..21f2a5d7966aa0 100644 --- a/src/env.h +++ b/src/env.h @@ -73,14 +73,24 @@ namespace loader { class ModuleWrap; struct PackageConfig { - enum class Exists { Yes, No }; - enum class IsValid { Yes, No }; - enum class HasMain { Yes, No }; - - Exists exists; - IsValid is_valid; - HasMain has_main; - std::string main; + struct Exists { + enum Bool { No, Yes }; + }; + struct IsValid { + enum Bool { No, Yes }; + }; + struct HasMain { + enum Bool { No, Yes }; + }; + struct IsESM { + enum Bool { No, Yes }; + }; + const Exists::Bool exists; + const IsValid::Bool is_valid; + const HasMain::Bool has_main; + const std::string main; + const v8::CopyablePersistentTraits::CopyablePersistent exports; + const IsESM::Bool esm; }; } // namespace loader @@ -168,6 +178,7 @@ constexpr size_t kFsStatsBufferLength = kFsStatsFieldsNumber * 2; V(env_var_settings_string, "envVarSettings") \ V(errno_string, "errno") \ V(error_string, "error") \ + V(esm_string, "esm") \ V(exchange_string, "exchange") \ V(exit_code_string, "exitCode") \ V(expire_string, "expire") \ @@ -206,6 +217,7 @@ constexpr size_t kFsStatsBufferLength = kFsStatsFieldsNumber * 2; V(kill_signal_string, "killSignal") \ V(kind_string, "kind") \ V(library_string, "library") \ + V(legacy_string, "legacy") \ V(mac_string, "mac") \ V(main_string, "main") \ V(max_buffer_string, "maxBuffer") \ diff --git a/src/module_wrap.cc b/src/module_wrap.cc index 5a0fc9c3d99ab3..11a78d5eb8d549 100644 --- a/src/module_wrap.cc +++ b/src/module_wrap.cc @@ -44,8 +44,6 @@ using v8::String; using v8::Undefined; using v8::Value; -static const char* const EXTENSIONS[] = {".mjs"}; - ModuleWrap::ModuleWrap(Environment* env, Local object, Local module, @@ -468,14 +466,30 @@ std::string ReadFile(uv_file file) { } enum DescriptorType { - NONE, FILE, - DIRECTORY + DIRECTORY, + NONE }; -DescriptorType CheckDescriptor(const std::string& path) { +// When DescriptorType cache is added, this can also return +// Nothing for the "null" cache entries. +inline Maybe OpenDescriptor(const std::string& path) { uv_fs_t fs_req; - int rc = uv_fs_stat(nullptr, &fs_req, path.c_str(), nullptr); + uv_file fd = uv_fs_open(nullptr, &fs_req, path.c_str(), O_RDONLY, 0, nullptr); + uv_fs_req_cleanup(&fs_req); + if (fd < 0) return Nothing(); + return Just(fd); +} + +inline void CloseDescriptor(uv_file fd) { + uv_fs_t fs_req; + uv_fs_close(nullptr, &fs_req, fd, nullptr); + uv_fs_req_cleanup(&fs_req); +} + +inline DescriptorType CheckDescriptorAtFile(uv_file fd) { + uv_fs_t fs_req; + int rc = uv_fs_fstat(nullptr, &fs_req, fd, nullptr); if (rc == 0) { uint64_t is_directory = fs_req.statbuf.st_mode & S_IFDIR; uv_fs_req_cleanup(&fs_req); @@ -485,27 +499,382 @@ DescriptorType CheckDescriptor(const std::string& path) { return NONE; } -Maybe PackageResolve(Environment* env, +// TODO(@guybedford): Add a DescriptorType cache layer here. +// Should be directory based -> if path/to/dir doesn't exist +// then the cache should early-fail any path/to/dir/file check. +DescriptorType CheckDescriptorAtPath(const std::string& path) { + Maybe fd = OpenDescriptor(path); + if (fd.IsNothing()) return NONE; + DescriptorType type = CheckDescriptorAtFile(fd.FromJust()); + CloseDescriptor(fd.FromJust()); + return type; +} + +Maybe ReadIfFile(const std::string& path) { + Maybe fd = OpenDescriptor(path); + if (fd.IsNothing()) return Nothing(); + DescriptorType type = CheckDescriptorAtFile(fd.FromJust()); + if (type != FILE) return Nothing(); + std::string source = ReadFile(fd.FromJust()); + CloseDescriptor(fd.FromJust()); + return Just(source); +} + +using Exists = PackageConfig::Exists; +using IsValid = PackageConfig::IsValid; +using HasMain = PackageConfig::HasMain; +using IsESM = PackageConfig::IsESM; + +Maybe GetPackageConfig(Environment* env, + const std::string& path, + const URL& base) { + auto existing = env->package_json_cache.find(path); + if (existing != env->package_json_cache.end()) { + return Just(&existing->second); + } + + Maybe source = ReadIfFile(path); + + if (source.IsNothing()) { + auto entry = env->package_json_cache.emplace(path, + PackageConfig { Exists::No, IsValid::Yes, HasMain::No, "", + Persistent(), IsESM::No }); + return Just(&entry.first->second); + } + + std::string pkg_src = source.FromJust(); + + Isolate* isolate = env->isolate(); + v8::HandleScope handle_scope(isolate); + + bool parsed = false; + Local pkg_json; + { + Local src; + Local pkg_json_v; + if (String::NewFromUtf8(isolate, + pkg_src.c_str(), + v8::NewStringType::kNormal, + pkg_src.length()).ToLocal(&src) && + v8::JSON::Parse(env->context(), src).ToLocal(&pkg_json_v) && + pkg_json_v->ToObject(env->context()).ToLocal(&pkg_json)) { + parsed = true; + } + } + + if (!parsed) { + (void)env->package_json_cache.emplace(path, + PackageConfig { Exists::Yes, IsValid::No, HasMain::No, "", + Persistent(), IsESM::No }); + std::string msg = "Invalid JSON in '" + path + + "' imported from " + base.ToFilePath(); + node::THROW_ERR_INVALID_PACKAGE_CONFIG(env, msg.c_str()); + return Nothing(); + } + + Local pkg_main; + HasMain::Bool has_main = HasMain::No; + std::string main_std; + if (pkg_json->Get(env->context(), env->main_string()).ToLocal(&pkg_main)) { + if (pkg_main->IsString()) { + has_main = HasMain::Yes; + } + Utf8Value main_utf8(isolate, pkg_main); + main_std.assign(std::string(*main_utf8, main_utf8.length())); + } + IsESM::Bool esm = IsESM::No; + + Local exports_v; + if (pkg_json->Get(env->context(), + env->exports_string()).ToLocal(&exports_v) && + (exports_v->IsObject() || exports_v->IsString())) { + esm = IsESM::Yes; + Persistent exports; + exports.Reset(env->isolate(), exports_v); + + auto entry = env->package_json_cache.emplace(path, + PackageConfig { Exists::Yes, IsValid::Yes, has_main, main_std, + exports, esm }); + return Just(&entry.first->second); + } + + auto entry = env->package_json_cache.emplace(path, + PackageConfig { Exists::Yes, IsValid::Yes, has_main, main_std, + Persistent(), esm }); + return Just(&entry.first->second); +} + +Maybe GetPackageBoundaryConfig(Environment* env, + const URL& search, + const URL& base) { + URL pjson_url("package.json", &search); + while (true) { + Maybe pkg_cfg = + GetPackageConfig(env, pjson_url.ToFilePath(), base); + if (pkg_cfg.IsNothing()) return pkg_cfg; + if (pkg_cfg.FromJust()->exists == Exists::Yes) return pkg_cfg; + + URL last_pjson_url = pjson_url; + pjson_url = URL("../package.json", pjson_url); + + // Terminates at root where ../package.json equals ../../package.json + // (can't just check "/package.json" for Windows support). + if (pjson_url.path() == last_pjson_url.path()) { + auto entry = env->package_json_cache.emplace(pjson_url.ToFilePath(), + PackageConfig { Exists::No, IsValid::Yes, HasMain::No, "", + Persistent(), IsESM::No }); + return Just(&entry.first->second); + } + } +} + +/* + * Legacy CommonJS main resolution: + * 1. let M = pkg_url + (json main field) + * 2. TRY(M, M.js, M.json, M.node) + * 3. TRY(M/index.js, M/index.json, M/index.node) + * 4. TRY(pkg_url/index.js, pkg_url/index.json, pkg_url/index.node) + * 5. NOT_FOUND + */ +inline bool FileExists(const URL& url) { + return CheckDescriptorAtPath(url.ToFilePath()) == FILE; +} +Maybe LegacyMainResolve(const URL& pjson_url, + const PackageConfig& pcfg) { + URL guess; + if (pcfg.has_main == HasMain::Yes) { + // Note: fs check redundances will be handled by Descriptor cache here. + if (FileExists(guess = URL("./" + pcfg.main, pjson_url))) { + return Just(guess); + } + if (FileExists(guess = URL("./" + pcfg.main + ".js", pjson_url))) { + return Just(guess); + } + if (FileExists(guess = URL("./" + pcfg.main + ".json", pjson_url))) { + return Just(guess); + } + if (FileExists(guess = URL("./" + pcfg.main + ".node", pjson_url))) { + return Just(guess); + } + if (FileExists(guess = URL("./" + pcfg.main + "/index.js", pjson_url))) { + return Just(guess); + } + // Such stat. + if (FileExists(guess = URL("./" + pcfg.main + "/index.json", pjson_url))) { + return Just(guess); + } + if (FileExists(guess = URL("./" + pcfg.main + "/index.node", pjson_url))) { + return Just(guess); + } + // Fallthrough. + } + if (FileExists(guess = URL("./index.js", pjson_url))) { + return Just(guess); + } + // So fs. + if (FileExists(guess = URL("./index.json", pjson_url))) { + return Just(guess); + } + if (FileExists(guess = URL("./index.node", pjson_url))) { + return Just(guess); + } + // Not found. + return Nothing(); +} + +Maybe FinalizeResolution(Environment* env, + const URL& resolved, + const URL& base, + bool check_exists, + bool is_main) { + const std::string& path = resolved.ToFilePath(); + + if (check_exists && CheckDescriptorAtPath(path) != FILE) { + std::string msg = "Cannot find module '" + path + + "' imported from " + base.ToFilePath(); + node::THROW_ERR_MODULE_NOT_FOUND(env, msg.c_str()); + return Nothing(); + } + + Maybe pcfg = + GetPackageBoundaryConfig(env, resolved, base); + if (pcfg.IsNothing()) return Nothing(); + + if (pcfg.FromJust()->exists == Exists::No) { + return Just(ModuleResolution { resolved, true }); + } + + return Just(ModuleResolution { + resolved, pcfg.FromJust()->esm == IsESM::No }); +} + +Maybe PackageMainResolve(Environment* env, + const URL& pjson_url, + const PackageConfig& pcfg, + const URL& base) { + if (pcfg.esm == IsESM::No) { + Maybe resolved = LegacyMainResolve(pjson_url, pcfg); + if (!resolved.IsNothing()) { + return FinalizeResolution(env, resolved.FromJust(), base, false, false); + } + } else { + Isolate* isolate = env->isolate(); + Local exports = pcfg.exports.Get(isolate); + if (exports->IsString()) { + Utf8Value main_utf8(isolate, exports.As()); + std::string main(*main_utf8, main_utf8.length()); + URL main_url("./" + main, pjson_url); + return FinalizeResolution(env, main_url, base, true, false); + } else if (exports->IsObject()) { + Local exports_obj = exports.As(); + Local dot_string = String::NewFromUtf8(isolate, ".", + v8::NewStringType::kNormal).ToLocalChecked(); + auto dot_main = + exports_obj->Get(env->context(), dot_string).ToLocalChecked(); + // TODO(@guybedford): Target validation + if (dot_main->IsString()) { + Utf8Value main_utf8(isolate, dot_main.As()); + std::string main(*main_utf8, main_utf8.length()); + URL main_url("./" + main, pjson_url); + return FinalizeResolution(env, main_url, base, true, false); + } + } + } + std::string msg = "Cannot find main entry point for '" + + URL(".", pjson_url).ToFilePath() + "' imported from " + + base.ToFilePath(); + node::THROW_ERR_MODULE_NOT_FOUND(env, msg.c_str()); + return Nothing(); +} + +Maybe PackageExportsResolve(Environment* env, + const URL& pjson_url, + const std::string& pkg_subpath, + const PackageConfig& pcfg, + const URL& base) { + Isolate* isolate = env->isolate(); + Local context = env->context(); + Local exports = pcfg.exports.Get(isolate); + if (exports->IsObject()) { + Local exports_obj = exports.As(); + Local subpath = String::NewFromUtf8(isolate, + pkg_subpath.c_str(), v8::NewStringType::kNormal).ToLocalChecked(); + + auto target = exports_obj->Get(context, subpath).ToLocalChecked(); + // TODO(@guybedford): Target validation + if (target->IsString()) { + Utf8Value target_utf8(isolate, target.As()); + std::string target(*target_utf8, target_utf8.length()); + if (target.substr(0, 2) == "./") { + URL target_url(target, pjson_url); + return FinalizeResolution(env, target_url, base, true, false); + } + } + + Local best_match; + std::string best_match_str = ""; + Local keys = + exports_obj->GetOwnPropertyNames(context).ToLocalChecked(); + for (uint32_t i = 0; i < keys->Length(); ++i) { + Local key = keys->Get(context, i).ToLocalChecked().As(); + Utf8Value key_utf8(isolate, key); + std::string key_str(*key_utf8, key_utf8.length()); + if (key_str.back() != '/') continue; + if (pkg_subpath.substr(0, key_str.length()) == key_str && + key_str.length() > best_match_str.length()) { + best_match = key; + best_match_str = key_str; + } + } + + if (best_match_str.length() > 0) { + auto target = exports_obj->Get(context, best_match).ToLocalChecked(); + if (target->IsString()) { + Utf8Value target_utf8(isolate, target.As()); + std::string target(*target_utf8, target_utf8.length()); + if (target.back() == '/' && target.substr(0, 2) == "./") { + std::string subpath = pkg_subpath.substr(best_match_str.length()); + URL target_url(target + subpath, pjson_url); + return FinalizeResolution(env, target_url, base, true, false); + } + } + } + } + std::string msg = "Package exports for '" + + URL(".", pjson_url).ToFilePath() + "' do not define a '" + pkg_subpath + + "' subpath, imported from " + base.ToFilePath(); + node::THROW_ERR_MODULE_NOT_FOUND(env, msg.c_str()); + return Nothing(); +} + +Maybe PackageResolve(Environment* env, const std::string& specifier, const URL& base) { - URL parent(".", base); + size_t sep_index = specifier.find('/'); + if (specifier[0] == '@' && (sep_index == std::string::npos || + specifier.length() == 0)) { + std::string msg = "Invalid package name '" + specifier + + "' imported from " + base.ToFilePath(); + node::THROW_ERR_INVALID_MODULE_SPECIFIER(env, msg.c_str()); + return Nothing(); + } + if (specifier[0] == '@') { + sep_index = specifier.find('/', sep_index + 1); + } + std::string pkg_name = specifier.substr(0, + sep_index == std::string::npos ? std::string::npos : sep_index); + std::string pkg_subpath; + if ((sep_index == std::string::npos || + sep_index == specifier.length() - 1)) { + pkg_subpath = ""; + } else { + pkg_subpath = "." + specifier.substr(sep_index); + } + URL pjson_url("./node_modules/" + pkg_name + "/package.json", &base); + std::string pjson_path = pjson_url.ToFilePath(); std::string last_path; do { - URL pkg_url("./node_modules/" + specifier, &parent); - DescriptorType check = CheckDescriptor(pkg_url.ToFilePath()); - if (check == FILE) return Just(pkg_url); - last_path = parent.path(); - parent = URL("..", &parent); - // cross-platform root check - } while (parent.path() != last_path); - return Nothing(); + DescriptorType check = + CheckDescriptorAtPath(pjson_path.substr(0, pjson_path.length() - 13)); + if (check != DIRECTORY) { + last_path = pjson_path; + pjson_url = + URL("../node_modules/" + pkg_name + "/package.json", &pjson_url); + pjson_path = pjson_url.ToFilePath(); + continue; + } + + // Package match. + Maybe pcfg = GetPackageConfig(env, pjson_path, base); + // Invalid package configuration error. + if (pcfg.IsNothing()) return Nothing(); + if (!pkg_subpath.length()) { + return PackageMainResolve(env, pjson_url, *pcfg.FromJust(), base); + } else { + if (pcfg.FromJust()->esm == IsESM::Yes) { + return PackageExportsResolve(env, pjson_url, pkg_subpath, + *pcfg.FromJust(), base); + } else { + return FinalizeResolution(env, URL(pkg_subpath, pjson_url), + base, true, false); + } + } + CHECK(false); + // Cross-platform root check. + } while (pjson_url.path().length() != last_path.length()); + + std::string msg = "Cannot find package '" + pkg_name + + "' imported from " + base.ToFilePath(); + node::THROW_ERR_MODULE_NOT_FOUND(env, msg.c_str()); + return Nothing(); } } // anonymous namespace -Maybe Resolve(Environment* env, - const std::string& specifier, - const URL& base) { +Maybe Resolve(Environment* env, + const std::string& specifier, + const URL& base, + bool is_main) { // Order swapped from spec for minor perf gain. // Ok since relative URLs cannot parse as URLs. URL resolved; @@ -519,21 +888,14 @@ Maybe Resolve(Environment* env, return PackageResolve(env, specifier, base); } } - DescriptorType check = CheckDescriptor(resolved.ToFilePath()); - if (check != FILE) { - std::string msg = "Cannot find module '" + resolved.ToFilePath() + - "' imported from " + base.ToFilePath(); - node::THROW_ERR_MODULE_NOT_FOUND(env, msg.c_str()); - return Nothing(); - } - return Just(resolved); + return FinalizeResolution(env, resolved, base, true, is_main); } void ModuleWrap::Resolve(const FunctionCallbackInfo& args) { Environment* env = Environment::GetCurrent(args); - // module.resolve(specifier, url) - CHECK_EQ(args.Length(), 2); + // module.resolve(specifier, url, is_main) + CHECK_EQ(args.Length(), 3); CHECK(args[0]->IsString()); Utf8Value specifier_utf8(env->isolate(), args[0]); @@ -543,28 +905,41 @@ void ModuleWrap::Resolve(const FunctionCallbackInfo& args) { Utf8Value url_utf8(env->isolate(), args[1]); URL url(*url_utf8, url_utf8.length()); + CHECK(args[2]->IsBoolean()); + if (url.flags() & URL_FLAGS_FAILED) { return node::THROW_ERR_INVALID_ARG_TYPE( env, "second argument is not a URL string"); } TryCatchScope try_catch(env); - Maybe result = node::loader::Resolve(env, specifier_std, url); - if (try_catch.HasCaught()) { - try_catch.ReThrow(); - return; - } else if (result.IsNothing() || - (result.FromJust().flags() & URL_FLAGS_FAILED)) { - std::string msg = "Cannot find module '" + specifier_std + - "' imported from " + url.ToFilePath(); - node::THROW_ERR_MODULE_NOT_FOUND(env, msg.c_str()); + Maybe result = + node::loader::Resolve(env, specifier_std, url, args[2]->IsTrue()); + if (result.IsNothing()) { + CHECK(try_catch.HasCaught()); try_catch.ReThrow(); return; } + CHECK(!try_catch.HasCaught()); + + ModuleResolution resolution = result.FromJust(); + CHECK(!(resolution.url.flags() & URL_FLAGS_FAILED)); + + Local resolved = Object::New(env->isolate()); + + resolved->DefineOwnProperty( + env->context(), + env->url_string(), + resolution.url.ToObject(env).ToLocalChecked(), + v8::ReadOnly).FromJust(); + + resolved->DefineOwnProperty( + env->context(), + env->legacy_string(), + v8::Boolean::New(env->isolate(), resolution.legacy), + v8::ReadOnly).FromJust(); - MaybeLocal obj = result.FromJust().ToObject(env); - if (!obj.IsEmpty()) - args.GetReturnValue().Set(obj.ToLocalChecked()); + args.GetReturnValue().Set(resolved); } static MaybeLocal ImportModuleDynamically( diff --git a/src/module_wrap.h b/src/module_wrap.h index 8a5592d3f231a0..f5e6eef94ecd35 100644 --- a/src/module_wrap.h +++ b/src/module_wrap.h @@ -23,9 +23,14 @@ enum HostDefinedOptions : int { kLength = 10, }; -v8::Maybe Resolve(Environment* env, - const std::string& specifier, - const url::URL& base); +struct ModuleResolution { + url::URL url; + bool legacy; +}; + +v8::Maybe Resolve(Environment* env, + const std::string& specifier, + const url::URL& base); class ModuleWrap : public BaseObject { public: diff --git a/src/node_errors.h b/src/node_errors.h index 3010ddf594b6ff..3b849abed43b66 100644 --- a/src/node_errors.h +++ b/src/node_errors.h @@ -59,6 +59,8 @@ void FatalException(const v8::FunctionCallbackInfo& args); V(ERR_CONSTRUCT_CALL_REQUIRED, Error) \ V(ERR_INVALID_ARG_VALUE, TypeError) \ V(ERR_INVALID_ARG_TYPE, TypeError) \ + V(ERR_INVALID_MODULE_SPECIFIER, TypeError) \ + V(ERR_INVALID_PACKAGE_CONFIG, SyntaxError) \ V(ERR_INVALID_TRANSFER_OBJECT, TypeError) \ V(ERR_MEMORY_ALLOCATION_FAILED, Error) \ V(ERR_MISSING_ARGS, TypeError) \ diff --git a/src/node_options.cc b/src/node_options.cc index 125a1b4dd70ef0..4246866d251ee9 100644 --- a/src/node_options.cc +++ b/src/node_options.cc @@ -93,10 +93,6 @@ const DebugOptionsParser DebugOptionsParser::instance; #endif // HAVE_INSPECTOR EnvironmentOptionsParser::EnvironmentOptionsParser() { - AddOption("--experimental-modules", - "experimental ES Module support and caching modules", - &EnvironmentOptions::experimental_modules, - kAllowedInEnvironment); AddOption("--experimental-repl-await", "experimental await keyword support in REPL", &EnvironmentOptions::experimental_repl_await, diff --git a/test/common/index.mjs b/test/common/index.mjs index 41592098eb50d5..deb1cac62adb3e 100644 --- a/test/common/index.mjs +++ b/test/common/index.mjs @@ -1,4 +1,3 @@ -// Flags: --experimental-modules /* eslint-disable node-core/required-modules */ import { createRequireFromPath } from 'module'; diff --git a/test/es-module/test-esm-basic-imports.mjs b/test/es-module/test-esm-basic-imports.mjs index d9bb22be0a5f12..43839834f4a354 100644 --- a/test/es-module/test-esm-basic-imports.mjs +++ b/test/es-module/test-esm-basic-imports.mjs @@ -1,4 +1,3 @@ -// Flags: --experimental-modules /* eslint-disable node-core/required-modules */ import '../common/index.mjs'; import assert from 'assert'; diff --git a/test/es-module/test-esm-cjs-main.js b/test/es-module/test-esm-cjs-main.js index 8308308a2dce72..6db308a2836175 100644 --- a/test/es-module/test-esm-cjs-main.js +++ b/test/es-module/test-esm-cjs-main.js @@ -7,7 +7,7 @@ const assert = require('assert'); const entry = fixtures.path('/es-modules/cjs.js'); -const child = spawn(process.execPath, ['--experimental-modules', entry]); +const child = spawn(process.execPath, [entry]); let stderr = ''; child.stderr.setEncoding('utf8'); child.stderr.on('data', (data) => { diff --git a/test/es-module/test-esm-cyclic-dynamic-import.mjs b/test/es-module/test-esm-cyclic-dynamic-import.mjs index a207efc73ecb0a..7e3f441d9a362e 100644 --- a/test/es-module/test-esm-cyclic-dynamic-import.mjs +++ b/test/es-module/test-esm-cyclic-dynamic-import.mjs @@ -1,4 +1,3 @@ -// Flags: --experimental-modules /* eslint-disable node-core/required-modules */ import '../common/index.mjs'; import('./test-esm-cyclic-dynamic-import.mjs'); diff --git a/test/es-module/test-esm-double-encoding.mjs b/test/es-module/test-esm-double-encoding.mjs index 9366d4bd6bcc68..8b0bf5542f411b 100644 --- a/test/es-module/test-esm-double-encoding.mjs +++ b/test/es-module/test-esm-double-encoding.mjs @@ -1,4 +1,3 @@ -// Flags: --experimental-modules /* eslint-disable node-core/required-modules */ import '../common/index.mjs'; diff --git a/test/es-module/test-esm-dynamic-import.js b/test/es-module/test-esm-dynamic-import.js index 325011b50f91ee..519c58d193f161 100644 --- a/test/es-module/test-esm-dynamic-import.js +++ b/test/es-module/test-esm-dynamic-import.js @@ -1,5 +1,3 @@ -// Flags: --experimental-modules - 'use strict'; const common = require('../common'); const assert = require('assert'); diff --git a/test/es-module/test-esm-encoded-path-native.js b/test/es-module/test-esm-encoded-path-native.js index a3106742d9ee8e..b8f5719b6089ee 100644 --- a/test/es-module/test-esm-encoded-path-native.js +++ b/test/es-module/test-esm-encoded-path-native.js @@ -5,7 +5,7 @@ const assert = require('assert'); const { spawn } = require('child_process'); const native = fixtures.path('es-module-url/native.mjs'); -const child = spawn(process.execPath, ['--experimental-modules', native]); +const child = spawn(process.execPath, [native]); child.on('exit', (code) => { assert.strictEqual(code, 1); }); diff --git a/test/es-module/test-esm-encoded-path.mjs b/test/es-module/test-esm-encoded-path.mjs index 2cabfdacff3954..9716e905216f85 100644 --- a/test/es-module/test-esm-encoded-path.mjs +++ b/test/es-module/test-esm-encoded-path.mjs @@ -1,4 +1,3 @@ -// Flags: --experimental-modules /* eslint-disable node-core/required-modules */ import '../common/index.mjs'; import assert from 'assert'; diff --git a/test/es-module/test-esm-error-cache.js b/test/es-module/test-esm-error-cache.js index 79f76357eca3ea..9e170ea501fefc 100644 --- a/test/es-module/test-esm-error-cache.js +++ b/test/es-module/test-esm-error-cache.js @@ -1,5 +1,3 @@ -// Flags: --experimental-modules - 'use strict'; require('../common'); diff --git a/test/es-module/test-esm-forbidden-globals.mjs b/test/es-module/test-esm-forbidden-globals.mjs index cf110ff2900eff..50badf872cb9df 100644 --- a/test/es-module/test-esm-forbidden-globals.mjs +++ b/test/es-module/test-esm-forbidden-globals.mjs @@ -1,4 +1,3 @@ -// Flags: --experimental-modules /* eslint-disable node-core/required-modules */ import '../common/index.mjs'; diff --git a/test/es-module/test-esm-import-meta.mjs b/test/es-module/test-esm-import-meta.mjs index 4c34b337fb8914..a61ce3bf55a0e5 100644 --- a/test/es-module/test-esm-import-meta.mjs +++ b/test/es-module/test-esm-import-meta.mjs @@ -1,4 +1,3 @@ -// Flags: --experimental-modules /* eslint-disable node-core/required-modules */ import '../common/index.mjs'; diff --git a/test/es-module/test-esm-live-binding.mjs b/test/es-module/test-esm-live-binding.mjs index 880a6c389b422c..4271ab994e0935 100644 --- a/test/es-module/test-esm-live-binding.mjs +++ b/test/es-module/test-esm-live-binding.mjs @@ -1,4 +1,3 @@ -// Flags: --experimental-modules /* eslint-disable node-core/required-modules */ import '../common/index.mjs'; diff --git a/test/es-module/test-esm-main-lookup.mjs b/test/es-module/test-esm-main-lookup.mjs index 19c025beab9ea9..cc302d96f37de6 100644 --- a/test/es-module/test-esm-main-lookup.mjs +++ b/test/es-module/test-esm-main-lookup.mjs @@ -1,4 +1,3 @@ -// Flags: --experimental-modules /* eslint-disable node-core/required-modules */ import '../common/index.mjs'; import assert from 'assert'; diff --git a/test/es-module/test-esm-namespace.mjs b/test/es-module/test-esm-namespace.mjs index 38b7ef12d585fc..107839922ae8c0 100644 --- a/test/es-module/test-esm-namespace.mjs +++ b/test/es-module/test-esm-namespace.mjs @@ -1,4 +1,3 @@ -// Flags: --experimental-modules /* eslint-disable node-core/required-modules */ import '../common/index.mjs'; diff --git a/test/es-module/test-esm-preserve-symlinks-main.js b/test/es-module/test-esm-preserve-symlinks-main.js index 808b6c9a15f6fd..d76349fbd0f08d 100644 --- a/test/es-module/test-esm-preserve-symlinks-main.js +++ b/test/es-module/test-esm-preserve-symlinks-main.js @@ -53,5 +53,5 @@ function doTest(flags, done) { // first test the commonjs module loader doTest([], () => { // now test the new loader - doTest(['--experimental-modules'], () => {}); + doTest([], () => {}); }); diff --git a/test/es-module/test-esm-preserve-symlinks.js b/test/es-module/test-esm-preserve-symlinks.js index 28cba3e7022c1a..a91373b0c05b93 100644 --- a/test/es-module/test-esm-preserve-symlinks.js +++ b/test/es-module/test-esm-preserve-symlinks.js @@ -32,7 +32,7 @@ try { } spawn(process.execPath, - ['--experimental-modules', '--preserve-symlinks', entry], + ['--preserve-symlinks', entry], { stdio: 'inherit' }).on('exit', (code) => { assert.strictEqual(code, 0); }); diff --git a/test/es-module/test-esm-require-cache.mjs b/test/es-module/test-esm-require-cache.mjs index 09030e0578e8c5..1fcaabb49bb7cf 100644 --- a/test/es-module/test-esm-require-cache.mjs +++ b/test/es-module/test-esm-require-cache.mjs @@ -1,4 +1,3 @@ -// Flags: --experimental-modules /* eslint-disable node-core/required-modules */ import { createRequire } from '../common/index.mjs'; import assert from 'assert'; diff --git a/test/es-module/test-esm-shebang.mjs b/test/es-module/test-esm-shebang.mjs index 486e04dadece61..9d7b5cf97f4315 100644 --- a/test/es-module/test-esm-shebang.mjs +++ b/test/es-module/test-esm-shebang.mjs @@ -1,5 +1,4 @@ #! }]) // isn't js -// Flags: --experimental-modules /* eslint-disable node-core/required-modules */ import '../common/index.mjs'; diff --git a/test/es-module/test-esm-symlink-main.js b/test/es-module/test-esm-symlink-main.js index 871180f5ccf4bb..48b4d8bbe65daf 100644 --- a/test/es-module/test-esm-symlink-main.js +++ b/test/es-module/test-esm-symlink-main.js @@ -19,7 +19,7 @@ try { } spawn(process.execPath, - ['--experimental-modules', '--preserve-symlinks', symlinkPath], + ['--preserve-symlinks', symlinkPath], { stdio: 'inherit' }).on('exit', (code) => { assert.strictEqual(code, 0); }); diff --git a/test/es-module/test-esm-symlink.js b/test/es-module/test-esm-symlink.js index 9b9eb98cd98406..139e6820ed5354 100644 --- a/test/es-module/test-esm-symlink.js +++ b/test/es-module/test-esm-symlink.js @@ -41,7 +41,7 @@ try { common.skip('insufficient privileges for symlinks'); } -spawn(process.execPath, ['--experimental-modules', entry], +spawn(process.execPath, [entry], { stdio: 'inherit' }).on('exit', (code) => { assert.strictEqual(code, 0); }); diff --git a/test/message/esm_display_syntax_error.mjs b/test/message/esm_display_syntax_error.mjs index 829186725554bf..bda4a7e6ebe3a3 100644 --- a/test/message/esm_display_syntax_error.mjs +++ b/test/message/esm_display_syntax_error.mjs @@ -1,3 +1,2 @@ -// Flags: --experimental-modules 'use strict'; await async () => 0; diff --git a/test/parallel/test-module-main-extension-lookup.js b/test/parallel/test-module-main-extension-lookup.js index 9e7eab295e8795..58d78e09b1199e 100644 --- a/test/parallel/test-module-main-extension-lookup.js +++ b/test/parallel/test-module-main-extension-lookup.js @@ -5,7 +5,5 @@ const { execFileSync } = require('child_process'); const node = process.argv[0]; -execFileSync(node, ['--experimental-modules', - fixtures.path('es-modules', 'test-esm-ok.mjs')]); -execFileSync(node, ['--experimental-modules', - fixtures.path('es-modules', 'noext')]); +execFileSync(node, [fixtures.path('es-modules', 'test-esm-ok.mjs')]); +execFileSync(node, [fixtures.path('es-modules', 'noext')]); diff --git a/test/parallel/test-module-main-fail.js b/test/parallel/test-module-main-fail.js index a6457f33b659dd..25ad058d413d21 100644 --- a/test/parallel/test-module-main-fail.js +++ b/test/parallel/test-module-main-fail.js @@ -4,18 +4,16 @@ const assert = require('assert'); const { execFileSync } = require('child_process'); const entryPoints = ['iDoNotExist', 'iDoNotExist.js', 'iDoNotExist.mjs']; -const flags = [[], ['--experimental-modules']]; const node = process.argv[0]; -for (const args of flags) { - for (const entryPoint of entryPoints) { - try { - execFileSync(node, args.concat(entryPoint), { stdio: 'pipe' }); - } catch (e) { - assert(e.toString().match(/Error: Cannot find module/)); - continue; - } - assert.fail('Executing node with inexistent entry point should ' + - `fail. Entry point: ${entryPoint}, Flags: [${args}]`); + +for (const entryPoint of entryPoints) { + try { + execFileSync(node, [entryPoint], { stdio: 'pipe' }); + } catch (e) { + assert(e.toString().match(/Error: Cannot find module/)); + continue; } + assert.fail('Executing node with inexistent entry point should ' + + `fail. Entry point: ${entryPoint}`); } diff --git a/test/parallel/test-module-main-preserve-symlinks-fail.js b/test/parallel/test-module-main-preserve-symlinks-fail.js index b46497b625261f..bbaf451c3ce539 100644 --- a/test/parallel/test-module-main-preserve-symlinks-fail.js +++ b/test/parallel/test-module-main-preserve-symlinks-fail.js @@ -4,7 +4,7 @@ const assert = require('assert'); const { execFileSync } = require('child_process'); const entryPoints = ['iDoNotExist', 'iDoNotExist.js', 'iDoNotExist.mjs']; -const flags = [[], ['--experimental-modules', '--preserve-symlinks']]; +const flags = [[], ['--preserve-symlinks']]; const node = process.argv[0]; for (const args of flags) {