Skip to content

Commit 6b93ba7

Browse files
joyeecheungRafaelGSS
authored andcommitted
module: use synchronous hooks for preparsing in import(cjs)
PR-URL: #55698 Backport-PR-URL: #57130 Reviewed-By: Geoffrey Booth <webadmin@geoffreybooth.com> Reviewed-By: Chengzhong Wu <legendecas@gmail.com> Reviewed-By: Guy Bedford <guybedford@gmail.com>
1 parent b2e44a8 commit 6b93ba7

File tree

1 file changed

+34
-31
lines changed

1 file changed

+34
-31
lines changed

lib/internal/modules/esm/translators.js

+34-31
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ const {
2525
const { BuiltinModule } = require('internal/bootstrap/realm');
2626
const assert = require('internal/assert');
2727
const { readFileSync } = require('fs');
28-
const { dirname, extname, isAbsolute } = require('path');
28+
const { dirname, extname } = require('path');
2929
const {
3030
assertBufferSource,
3131
loadBuiltinModule,
@@ -41,6 +41,9 @@ const {
4141
kModuleSource,
4242
kModuleExport,
4343
kModuleExportNames,
44+
findLongestRegisteredExtension,
45+
resolveForCJSWithHooks,
46+
loadSourceForCJSWithHooks,
4447
} = require('internal/modules/cjs/loader');
4548
const { fileURLToPath, pathToFileURL, URL } = require('internal/url');
4649
let debug = require('internal/util/debuglog').debuglog('esm', (fn) => {
@@ -170,17 +173,18 @@ const cjsCache = new SafeMap();
170173
* @param {string} url - The URL of the module.
171174
* @param {string} source - The source code of the module.
172175
* @param {boolean} isMain - Whether the module is the main module.
176+
* @param {string} format - Format of the module.
173177
* @param {typeof loadCJSModule} [loadCJS=loadCJSModule] - The function to load the CommonJS module.
174178
* @returns {ModuleWrap} The ModuleWrap object for the CommonJS module.
175179
*/
176-
function createCJSModuleWrap(url, source, isMain, loadCJS = loadCJSModule) {
180+
function createCJSModuleWrap(url, source, isMain, format, loadCJS = loadCJSModule) {
177181
debug(`Translating CJSModule ${url}`);
178182

179183
const filename = urlToFilename(url);
180184
// In case the source was not provided by the `load` step, we need fetch it now.
181185
source = stringify(source ?? getSource(new URL(url)).source);
182186

183-
const { exportNames, module } = cjsPreparseModuleExports(filename, source);
187+
const { exportNames, module } = cjsPreparseModuleExports(filename, source, isMain, format);
184188
cjsCache.set(url, module);
185189
const namesWithDefault = exportNames.has('default') ?
186190
[...exportNames] : ['default', ...exportNames];
@@ -224,7 +228,7 @@ function createCJSModuleWrap(url, source, isMain, loadCJS = loadCJSModule) {
224228
translators.set('commonjs-sync', function requireCommonJS(url, source, isMain) {
225229
initCJSParseSync();
226230

227-
return createCJSModuleWrap(url, source, isMain, (module, source, url, filename, isMain) => {
231+
return createCJSModuleWrap(url, source, isMain, 'commonjs', (module, source, url, filename, isMain) => {
228232
assert(module === CJSModule._cache[filename]);
229233
wrapModuleLoad(filename, null, isMain);
230234
});
@@ -236,15 +240,15 @@ translators.set('require-commonjs', (url, source, isMain) => {
236240
initCJSParseSync();
237241
assert(cjsParse);
238242

239-
return createCJSModuleWrap(url, source);
243+
return createCJSModuleWrap(url, source, isMain, 'commonjs');
240244
});
241245

242246
// Handle CommonJS modules referenced by `require` calls.
243247
// This translator function must be sync, as `require` is sync.
244248
translators.set('require-commonjs-typescript', (url, source, isMain) => {
245249
assert(cjsParse);
246250
const code = stripTypeScriptModuleTypes(stringify(source), url);
247-
return createCJSModuleWrap(url, code);
251+
return createCJSModuleWrap(url, code, isMain, 'commonjs-typescript');
248252
});
249253

250254
// Handle CommonJS modules referenced by `import` statements or expressions,
@@ -268,16 +272,17 @@ translators.set('commonjs', function commonjsStrategy(url, source, isMain) {
268272
} catch {
269273
// Continue regardless of error.
270274
}
271-
return createCJSModuleWrap(url, source, isMain, cjsLoader);
275+
return createCJSModuleWrap(url, source, isMain, 'commonjs', cjsLoader);
272276
});
273277

274278
/**
275279
* Pre-parses a CommonJS module's exports and re-exports.
276280
* @param {string} filename - The filename of the module.
277281
* @param {string} [source] - The source code of the module.
282+
* @param {boolean} isMain - Whether it is pre-parsing for the entry point.
283+
* @param {string} format
278284
*/
279-
function cjsPreparseModuleExports(filename, source) {
280-
// TODO: Do we want to keep hitting the user mutable CJS loader here?
285+
function cjsPreparseModuleExports(filename, source, isMain, format) {
281286
let module = CJSModule._cache[filename];
282287
if (module && module[kModuleExportNames] !== undefined) {
283288
return { module, exportNames: module[kModuleExportNames] };
@@ -288,10 +293,15 @@ function cjsPreparseModuleExports(filename, source) {
288293
module.filename = filename;
289294
module.paths = CJSModule._nodeModulePaths(module.path);
290295
module[kIsCachedByESMLoader] = true;
291-
module[kModuleSource] = source;
292296
CJSModule._cache[filename] = module;
293297
}
294298

299+
if (source === undefined) {
300+
({ source } = loadSourceForCJSWithHooks(module, filename, format));
301+
}
302+
module[kModuleSource] = source;
303+
304+
debug(`Preparsing exports of ${filename}`);
295305
let exports, reexports;
296306
try {
297307
({ exports, reexports } = cjsParse(source || ''));
@@ -305,34 +315,27 @@ function cjsPreparseModuleExports(filename, source) {
305315
// Set first for cycles.
306316
module[kModuleExportNames] = exportNames;
307317

318+
// If there are any re-exports e.g. `module.exports = { ...require(...) }`,
319+
// pre-parse the dependencies to find transitively exported names.
308320
if (reexports.length) {
309-
module.filename = filename;
310-
module.paths = CJSModule._nodeModulePaths(module.path);
321+
module.filename ??= filename;
322+
module.paths ??= CJSModule._nodeModulePaths(dirname(filename));
323+
311324
for (let i = 0; i < reexports.length; i++) {
325+
debug(`Preparsing re-exports of '${filename}'`);
312326
const reexport = reexports[i];
313327
let resolved;
328+
let format;
314329
try {
315-
// TODO: this should be calling the `resolve` hook chain instead.
316-
// Doing so would mean dropping support for CJS in the loader thread, as
317-
// this call needs to be sync from the perspective of the main thread,
318-
// which we can do via HooksProxy and Atomics, but we can't do within
319-
// the loaders thread. Until this is done, the lexer will use the
320-
// monkey-patchable CJS loader to get the path to the module file to
321-
// load (which may or may not be aligned with the URL that the `resolve`
322-
// hook have returned).
323-
resolved = CJSModule._resolveFilename(reexport, module);
324-
} catch {
330+
({ format, filename: resolved } = resolveForCJSWithHooks(reexport, module, false));
331+
} catch (e) {
332+
debug(`Failed to resolve '${reexport}', skipping`, e);
325333
continue;
326334
}
327-
// TODO: this should be calling the `load` hook chain and check if it returns
328-
// `format: 'commonjs'` instead of relying on file extensions.
329-
const ext = extname(resolved);
330-
if ((ext === '.js' || ext === '.cjs' || !CJSModule._extensions[ext]) &&
331-
isAbsolute(resolved)) {
332-
// TODO: this should be calling the `load` hook chain to get the source
333-
// (and fallback to reading the FS only if the source is nullish).
334-
const source = readFileSync(resolved, 'utf-8');
335-
const { exportNames: reexportNames } = cjsPreparseModuleExports(resolved, source);
335+
336+
if (format === 'commonjs' ||
337+
(!BuiltinModule.normalizeRequirableId(resolved) && findLongestRegisteredExtension(resolved) === '.js')) {
338+
const { exportNames: reexportNames } = cjsPreparseModuleExports(resolved, undefined, false, format);
336339
for (const name of reexportNames) {
337340
exportNames.add(name);
338341
}

0 commit comments

Comments
 (0)