-
-
Notifications
You must be signed in to change notification settings - Fork 32.3k
module: support ESM detection in the CJS loader #52047
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 1 commit
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -180,9 +180,12 @@ regarding which files are parsed as ECMAScript modules. | |
If `--experimental-require-module` is enabled, and the ECMAScript module being | ||
loaded by `require()` meets the following requirements: | ||
|
||
* Explicitly marked as an ES module with a `"type": "module"` field in | ||
the closest package.json or a `.mjs` extension. | ||
* Fully synchronous (contains no top-level `await`). | ||
* One of these conditions are met: | ||
1. The file has a `.mjs` extension. | ||
2. The file has a `.js` extension, and the closest `package.json` contains `"type": "module"` | ||
3. The file has a `.js` extension, the closest `package.json` does not contain | ||
`"type": "commonjs"`, and `--experimental-detect-module` is enabled. | ||
* The module is fully synchronous (contains no top-level `await`). | ||
joyeecheung marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
`require()` will load the requested module as an ES Module, and return | ||
the module name space object. In this case it is similar to dynamic | ||
|
@@ -249,18 +252,27 @@ require(X) from module at path Y | |
6. LOAD_NODE_MODULES(X, dirname(Y)) | ||
7. THROW "not found" | ||
|
||
MAYBE_DETECT_AND_LOAD(X) | ||
1. If X parses as a CommonJS module, load X as a CommonJS module. STOP. | ||
2. Else, if `--experimental-require-module` and `--experimental-detect-module` are | ||
enabled, and the source code of X can be parsed as ECMAScript module using | ||
<a href="esm.md#resolver-algorithm-specification">DETECT_MODULE_SYNTAX defined in | ||
the ESM resolver</a>, | ||
a. Load X as an ECMAScript module. STOP. | ||
3. THROW the SyntaxError from attempting to parse X as CommonJS in 1. STOP. | ||
|
||
LOAD_AS_FILE(X) | ||
1. If X is a file, load X as its file extension format. STOP | ||
2. If X.js is a file, | ||
a. Find the closest package scope SCOPE to X. | ||
b. If no scope was found, load X.js as a CommonJS module. STOP. | ||
b. If no scope was found | ||
1. MAYBE_DETECT_AND_LOAD(X.js) | ||
c. If the SCOPE/package.json contains "type" field, | ||
1. If the "type" field is "module", load X.js as an ECMAScript module. STOP. | ||
2. Else, load X.js as an CommonJS module. STOP. | ||
2. If the "type" field is "commonjs", load X.js as an CommonJS module. STOP. | ||
d. MAYBE_DETECT_AND_LOAD(X.js) | ||
3. If X.json is a file, load X.json to a JavaScript Object. STOP | ||
4. If X.node is a file, load X.node as binary addon. STOP | ||
5. If X.mjs is a file, and `--experimental-require-module` is enabled, | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I noticed that this is actually unnecessary, when X is already a file, it's covered by 1 (and even before we supported |
||
load X.mjs as an ECMAScript module. STOP | ||
|
||
LOAD_INDEX(X) | ||
1. If X/index.js is a file | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -4,7 +4,6 @@ const { | |
ArrayPrototypeMap, | ||
Boolean, | ||
JSONParse, | ||
ObjectGetPrototypeOf, | ||
ObjectKeys, | ||
ObjectPrototypeHasOwnProperty, | ||
ReflectApply, | ||
|
@@ -15,7 +14,6 @@ const { | |
StringPrototypeReplaceAll, | ||
StringPrototypeSlice, | ||
StringPrototypeStartsWith, | ||
SyntaxErrorPrototype, | ||
globalThis: { WebAssembly }, | ||
} = primordials; | ||
|
||
|
@@ -30,7 +28,6 @@ function lazyTypes() { | |
} | ||
|
||
const { | ||
containsModuleSyntax, | ||
compileFunctionForCJSLoader, | ||
} = internalBinding('contextify'); | ||
|
||
|
@@ -62,7 +59,6 @@ const { | |
const { maybeCacheSourceMap } = require('internal/source_map/source_map_cache'); | ||
const moduleWrap = internalBinding('module_wrap'); | ||
const { ModuleWrap } = moduleWrap; | ||
const { emitWarningSync } = require('internal/process/warning'); | ||
|
||
// Lazy-loading to avoid circular dependencies. | ||
let getSourceSync; | ||
|
@@ -107,7 +103,6 @@ function initCJSParseSync() { | |
|
||
const translators = new SafeMap(); | ||
exports.translators = translators; | ||
exports.enrichCJSError = enrichCJSError; | ||
|
||
let DECODER = null; | ||
/** | ||
|
@@ -169,25 +164,6 @@ translators.set('module', function moduleStrategy(url, source, isMain) { | |
return module; | ||
}); | ||
|
||
/** | ||
* Provide a more informative error for CommonJS imports. | ||
* @param {Error | any} err | ||
* @param {string} [content] Content of the file, if known. | ||
* @param {string} [filename] The filename of the erroring module. | ||
*/ | ||
function enrichCJSError(err, content, filename) { | ||
if (err != null && ObjectGetPrototypeOf(err) === SyntaxErrorPrototype && | ||
containsModuleSyntax(content, filename)) { | ||
// Emit the warning synchronously because we are in the middle of handling | ||
// a SyntaxError that will throw and likely terminate the process before an | ||
// asynchronous warning would be emitted. | ||
emitWarningSync( | ||
'To load an ES module, set "type": "module" in the package.json or use ' + | ||
'the .mjs extension.', | ||
); | ||
} | ||
} | ||
|
||
/** | ||
* Loads a CommonJS module via the ESM Loader sync CommonJS translator. | ||
* This translator creates its own version of the `require` function passed into CommonJS modules. | ||
|
@@ -197,15 +173,11 @@ function enrichCJSError(err, content, filename) { | |
* @param {string} source - The source code of the module. | ||
* @param {string} url - The URL of the module. | ||
* @param {string} filename - The filename of the module. | ||
* @param {boolean} isMain - Whether the module is the entrypoint | ||
*/ | ||
function loadCJSModule(module, source, url, filename) { | ||
let compileResult; | ||
try { | ||
compileResult = compileFunctionForCJSLoader(source, filename); | ||
} catch (err) { | ||
enrichCJSError(err, source, filename); | ||
throw err; | ||
} | ||
function loadCJSModule(module, source, url, filename, isMain) { | ||
const compileResult = compileFunctionForCJSLoader(source, filename, isMain, false); | ||
|
||
// Cache the source map for the cjs module if present. | ||
if (compileResult.sourceMapURL) { | ||
maybeCacheSourceMap(url, source, null, false, undefined, compileResult.sourceMapURL); | ||
|
@@ -283,7 +255,7 @@ function createCJSModuleWrap(url, source, isMain, loadCJS = loadCJSModule) { | |
debug(`Loading CJSModule ${url}`); | ||
|
||
if (!module.loaded) { | ||
loadCJS(module, source, url, filename); | ||
loadCJS(module, source, url, filename, !!isMain); | ||
} | ||
|
||
let exports; | ||
|
@@ -315,9 +287,10 @@ translators.set('commonjs-sync', function requireCommonJS(url, source, isMain) { | |
initCJSParseSync(); | ||
assert(!isMain); // This is only used by imported CJS modules. | ||
|
||
return createCJSModuleWrap(url, source, isMain, (module, source, url, filename) => { | ||
return createCJSModuleWrap(url, source, isMain, (module, source, url, filename, isMain) => { | ||
assert(module === CJSModule._cache[filename]); | ||
CJSModule._load(filename); | ||
assert(!isMain); | ||
CJSModule._load(filename, null, isMain); | ||
Comment on lines
+292
to
+293
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is there a reason not to pass it explicitly and rely on the implicit default value of CJSModule._load even though the value is readily available? |
||
}); | ||
}); | ||
|
||
|
@@ -340,14 +313,9 @@ translators.set('commonjs', async function commonjsStrategy(url, source, | |
// For backward-compatibility, it's possible to return a nullish value for | ||
// CJS source associated with a file: URL. In this case, the source is | ||
// obtained by calling the monkey-patchable CJS loader. | ||
const cjsLoader = source == null ? (module, source, url, filename) => { | ||
try { | ||
assert(module === CJSModule._cache[filename]); | ||
CJSModule._load(filename); | ||
} catch (err) { | ||
enrichCJSError(err, source, filename); | ||
throw err; | ||
} | ||
const cjsLoader = source == null ? (module, source, url, filename, isMain) => { | ||
assert(module === CJSModule._cache[filename]); | ||
CJSModule._load(filename, undefined, isMain); | ||
} : loadCJSModule; | ||
|
||
try { | ||
|
Uh oh!
There was an error while loading. Please reload this page.