esm: fallback to getSource
when load
returns nullish source
When using the Modules Customization Hooks API to load CommonJS modules, we want to support the returned value of `defaultLoad` which must be nullish to preserve backward compatibility. This can be achieved by fetching the source from the translator. PR-URL: https://github.com/nodejs/node/pull/50825 Fixes: https://github.com/nodejs/node/issues/50435 Reviewed-By: Geoffrey Booth <webadmin@geoffreybooth.com> Reviewed-By: Jacob Smith <jacob@frende.me>
This commit is contained in:
parent
3263228ebf
commit
4eafcf8ff5
@ -261,5 +261,6 @@ function throwUnknownModuleFormat(url, format) {
|
|||||||
module.exports = {
|
module.exports = {
|
||||||
defaultLoad,
|
defaultLoad,
|
||||||
defaultLoadSync,
|
defaultLoadSync,
|
||||||
|
getSourceSync,
|
||||||
throwUnknownModuleFormat,
|
throwUnknownModuleFormat,
|
||||||
};
|
};
|
||||||
|
@ -31,6 +31,7 @@ function lazyTypes() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const { containsModuleSyntax } = internalBinding('contextify');
|
const { containsModuleSyntax } = internalBinding('contextify');
|
||||||
|
const { BuiltinModule } = require('internal/bootstrap/realm');
|
||||||
const assert = require('internal/assert');
|
const assert = require('internal/assert');
|
||||||
const { readFileSync } = require('fs');
|
const { readFileSync } = require('fs');
|
||||||
const { dirname, extname, isAbsolute } = require('path');
|
const { dirname, extname, isAbsolute } = require('path');
|
||||||
@ -58,6 +59,17 @@ const asyncESM = require('internal/process/esm_loader');
|
|||||||
const { emitWarningSync } = require('internal/process/warning');
|
const { emitWarningSync } = require('internal/process/warning');
|
||||||
const { internalCompileFunction } = require('internal/vm');
|
const { internalCompileFunction } = require('internal/vm');
|
||||||
|
|
||||||
|
// Lazy-loading to avoid circular dependencies.
|
||||||
|
let getSourceSync;
|
||||||
|
/**
|
||||||
|
* @param {Parameters<typeof import('./load').getSourceSync>[0]} url
|
||||||
|
* @returns {ReturnType<typeof import('./load').getSourceSync>}
|
||||||
|
*/
|
||||||
|
function getSource(url) {
|
||||||
|
getSourceSync ??= require('internal/modules/esm/load').getSourceSync;
|
||||||
|
return getSourceSync(url);
|
||||||
|
}
|
||||||
|
|
||||||
/** @type {import('deps/cjs-module-lexer/lexer.js').parse} */
|
/** @type {import('deps/cjs-module-lexer/lexer.js').parse} */
|
||||||
let cjsParse;
|
let cjsParse;
|
||||||
/**
|
/**
|
||||||
@ -225,10 +237,9 @@ function loadCJSModule(module, source, url, filename) {
|
|||||||
// eslint-disable-next-line func-name-matching,func-style
|
// eslint-disable-next-line func-name-matching,func-style
|
||||||
const requireFn = function require(specifier) {
|
const requireFn = function require(specifier) {
|
||||||
let importAttributes = kEmptyObject;
|
let importAttributes = kEmptyObject;
|
||||||
if (!StringPrototypeStartsWith(specifier, 'node:')) {
|
if (!StringPrototypeStartsWith(specifier, 'node:') && !BuiltinModule.normalizeRequirableId(specifier)) {
|
||||||
// TODO: do not depend on the monkey-patchable CJS loader here.
|
// TODO: do not depend on the monkey-patchable CJS loader here.
|
||||||
const path = CJSModule._resolveFilename(specifier, module);
|
const path = CJSModule._resolveFilename(specifier, module);
|
||||||
if (specifier !== path) {
|
|
||||||
switch (extname(path)) {
|
switch (extname(path)) {
|
||||||
case '.json':
|
case '.json':
|
||||||
importAttributes = { __proto__: null, type: 'json' };
|
importAttributes = { __proto__: null, type: 'json' };
|
||||||
@ -240,7 +251,6 @@ function loadCJSModule(module, source, url, filename) {
|
|||||||
}
|
}
|
||||||
specifier = `${pathToFileURL(path)}`;
|
specifier = `${pathToFileURL(path)}`;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
const job = asyncESM.esmLoader.getModuleJobSync(specifier, url, importAttributes);
|
const job = asyncESM.esmLoader.getModuleJobSync(specifier, url, importAttributes);
|
||||||
job.runSync();
|
job.runSync();
|
||||||
return cjsCache.get(job.url).exports;
|
return cjsCache.get(job.url).exports;
|
||||||
@ -276,7 +286,8 @@ function createCJSModuleWrap(url, source, isMain, loadCJS = loadCJSModule) {
|
|||||||
debug(`Translating CJSModule ${url}`);
|
debug(`Translating CJSModule ${url}`);
|
||||||
|
|
||||||
const filename = StringPrototypeStartsWith(url, 'file://') ? fileURLToPath(url) : url;
|
const filename = StringPrototypeStartsWith(url, 'file://') ? fileURLToPath(url) : url;
|
||||||
source = stringify(source);
|
// In case the source was not provided by the `load` step, we need fetch it now.
|
||||||
|
source = stringify(source ?? getSource(new URL(url)).source);
|
||||||
|
|
||||||
const { exportNames, module } = cjsPreparseModuleExports(filename, source);
|
const { exportNames, module } = cjsPreparseModuleExports(filename, source);
|
||||||
cjsCache.set(url, module);
|
cjsCache.set(url, module);
|
||||||
|
@ -746,4 +746,39 @@ describe('Loader hooks', { concurrency: true }, () => {
|
|||||||
assert.strictEqual(code, 0);
|
assert.strictEqual(code, 0);
|
||||||
assert.strictEqual(signal, null);
|
assert.strictEqual(signal, null);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should handle mixed of opt-in modules and non-opt-in ones', async () => {
|
||||||
|
const { code, signal, stdout, stderr } = await spawnPromisified(execPath, [
|
||||||
|
'--no-warnings',
|
||||||
|
'--experimental-loader',
|
||||||
|
`data:text/javascript,const fixtures=${JSON.stringify(fixtures.path('empty.js'))};export ${
|
||||||
|
encodeURIComponent(function resolve(s, c, n) {
|
||||||
|
if (s.endsWith('entry-point')) {
|
||||||
|
return {
|
||||||
|
shortCircuit: true,
|
||||||
|
url: 'file:///c:/virtual-entry-point',
|
||||||
|
format: 'commonjs',
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return n(s, c);
|
||||||
|
})
|
||||||
|
}export ${
|
||||||
|
encodeURIComponent(async function load(u, c, n) {
|
||||||
|
if (u === 'file:///c:/virtual-entry-point') {
|
||||||
|
return {
|
||||||
|
shortCircuit: true,
|
||||||
|
source: `"use strict";require(${JSON.stringify(fixtures)});console.log("Hello");`,
|
||||||
|
format: 'commonjs',
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return n(u, c);
|
||||||
|
})}`,
|
||||||
|
'entry-point',
|
||||||
|
]);
|
||||||
|
|
||||||
|
assert.strictEqual(stderr, '');
|
||||||
|
assert.strictEqual(stdout, 'Hello\n');
|
||||||
|
assert.strictEqual(code, 0);
|
||||||
|
assert.strictEqual(signal, null);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
Loading…
x
Reference in New Issue
Block a user