Skip to content

Commit d147c08

Browse files
joyeecheungaduh95
authored andcommitted
module: do not invoke resolve hooks twice for imported cjs
Previously the resolve hook can be invoked twice from the synthetic module evaluation step of imported CJS in the extra module._load() call that's invoked on the resolved full path. Add an option to avoid it, since the resolution and loading has already been done before. PR-URL: #61529Fixes: #57125 Refs: #55808 Refs: #56241 Reviewed-By: Jacob Smith <jacob@frende.me>
1 parent 17122e5 commit d147c08

5 files changed

Lines changed: 59 additions & 11 deletions

File tree

‎lib/internal/modules/cjs/loader.js‎

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -235,7 +235,7 @@ let statCache = null;
235235
* See more {@link Module._load}
236236
* @returns {object}
237237
*/
238-
functionwrapModuleLoad(request,parent,isMain){
238+
functionwrapModuleLoad(request,parent,isMain,options){
239239
constlogLabel=`[${parent?.id||''}] [${request}]`;
240240
consttraceLabel=`require('${request}')`;
241241
constchannel=onRequire();
@@ -248,11 +248,11 @@ function wrapModuleLoad(request, parent, isMain) {
248248
__proto__: null,
249249
parentFilename: parent?.filename,
250250
id: request,
251-
},Module,request,parent,isMain);
251+
},Module,request,parent,isMain,options);
252252
}
253253
// No subscribers, skip the wrapping to avoid clobbering stack traces
254254
// and debugging steps.
255-
returnModule._load(request,parent,isMain);
255+
returnModule._load(request,parent,isMain,options);
256256
}finally{
257257
endTimer(logLabel,traceLabel);
258258
}
@@ -1040,9 +1040,10 @@ function getExportsForCircularRequire(module) {
10401040
* @param {string} specifier
10411041
* @param {Module|undefined} parent
10421042
* @param {boolean} isMain
1043+
* @param {boolean} shouldSkipModuleHooks
10431044
* @returns {{url?: string, format?: string, parentURL?: string, filename: string}}
10441045
*/
1045-
functionresolveForCJSWithHooks(specifier,parent,isMain){
1046+
functionresolveForCJSWithHooks(specifier,parent,isMain,shouldSkipModuleHooks){
10461047
letdefaultResolvedURL;
10471048
letdefaultResolvedFilename;
10481049
letformat;
@@ -1065,7 +1066,7 @@ function resolveForCJSWithHooks(specifier, parent, isMain) {
10651066
}
10661067

10671068
// Fast path: no hooks, just return simple results.
1068-
if(!resolveHooks.length){
1069+
if(!resolveHooks.length||shouldSkipModuleHooks){
10691070
constfilename=defaultResolveImpl(specifier,parent,isMain);
10701071
return{__proto__: null,url: defaultResolvedURL, filename, format };
10711072
}
@@ -1118,7 +1119,7 @@ function resolveForCJSWithHooks(specifier, parent, isMain) {
11181119
}
11191120

11201121
constresult={__proto__: null, url, format, filename, parentURL };
1121-
debug('resolveForCJSWithHooks',specifier,parent?.id,'->',result);
1122+
debug('resolveForCJSWithHooks',specifier,parent?.id,isMain,shouldSkipModuleHooks,'->',result);
11221123
returnresult;
11231124
}
11241125

@@ -1211,9 +1212,10 @@ function loadBuiltinWithHooks(id, url, format) {
12111212
* @param {string} request Specifier of module to load via `require`
12121213
* @param {Module} parent Absolute path of the module importing the child
12131214
* @param {boolean} isMain Whether the module is the main entry point
1215+
* @param {object|undefined} options Additional options for loading the module
12141216
* @returns {object}
12151217
*/
1216-
Module._load=function(request,parent,isMain){
1218+
Module._load=function(request,parent,isMain,options=kEmptyObject){
12171219
letrelResolveCacheIdentifier;
12181220
if(parent){
12191221
debug('Module._load REQUEST %s parent: %s',request,parent.id);
@@ -1236,7 +1238,7 @@ Module._load = function(request, parent, isMain) {
12361238
}
12371239
}
12381240

1239-
constresolveResult=resolveForCJSWithHooks(request,parent,isMain);
1241+
constresolveResult=resolveForCJSWithHooks(request,parent,isMain,options.shouldSkipModuleHooks);
12401242
let{ format }=resolveResult;
12411243
const{ url, filename }=resolveResult;
12421244

‎lib/internal/modules/esm/translators.js‎

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,7 @@ translators.set('module', function moduleStrategy(url, translateContext, parentU
100100
});
101101

102102
const{requestTypes: { kRequireInImportedCJS }}=require('internal/modules/esm/utils');
103+
constkShouldSkipModuleHooks={__proto__: null,shouldSkipModuleHooks: true};
103104
/**
104105
* Loads a CommonJS module via the ESM Loader sync CommonJS translator.
105106
* This translator creates its own version of the `require` function passed into CommonJS modules.
@@ -132,7 +133,9 @@ function loadCJSModule(module, source, url, filename, isMain) {
132133
importAttributes={__proto__: null,type: 'json'};
133134
break;
134135
case'.node':
135-
returnwrapModuleLoad(specifier,module);
136+
// If it gets here in the translators, the hooks must have already been invoked
137+
// in the loader. Skip them in the synthetic module evaluation step.
138+
returnwrapModuleLoad(specifier,module,false,kShouldSkipModuleHooks);
136139
default:
137140
// fall through
138141
}
@@ -280,7 +283,9 @@ function createCJSNoSourceModuleWrap(url, parentURL) {
280283
debug(`Loading CJSModule ${url}`);
281284

282285
if(!module.loaded){
283-
wrapModuleLoad(filename,null,isMain);
286+
// If it gets here in the translators, the hooks must have already been invoked
287+
// in the loader. Skip them in the synthetic module evaluation step.
288+
wrapModuleLoad(filename,null,isMain,kShouldSkipModuleHooks);
284289
}
285290

286291
/** @type {import('./loader').ModuleExports} */
@@ -317,7 +322,9 @@ translators.set('require-commonjs-typescript', (url, translateContext, parentURL
317322
// This goes through Module._load to accommodate monkey-patchers.
318323
functionloadCJSModuleWithModuleLoad(module,source,url,filename,isMain){
319324
assert(module===CJSModule._cache[filename]);
320-
wrapModuleLoad(filename,undefined,isMain);
325+
// If it gets here in the translators, the hooks must have already been invoked
326+
// in the loader. Skip them in the synthetic module evaluation step.
327+
wrapModuleLoad(filename,undefined,isMain,kShouldSkipModuleHooks);
321328
}
322329

323330
// Handle CommonJS modules referenced by `import` statements or expressions,

‎test/fixtures/value.cjs‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
exports.value=42;
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
'use strict';
2+
// Test that load hook in imported CJS only gets invoked once.
3+
4+
constcommon=require('../common');
5+
constassert=require('assert');
6+
const{ registerHooks }=require('module');
7+
constpath=require('path');
8+
const{ pathToFileURL }=require('url');
9+
10+
consthook=registerHooks({
11+
load: common.mustCall(function(url,context,nextLoad){
12+
assert.strictEqual(url,pathToFileURL(path.resolve(__dirname,'../fixtures/value.cjs')).href);
13+
returnnextLoad(url,context);
14+
},1),
15+
});
16+
17+
import('../fixtures/value.cjs').then(common.mustCall((result)=>{
18+
assert.strictEqual(result.value,42);
19+
hook.deregister();
20+
}));
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
'use strict';
2+
// Test that resolve hook in imported CJS only gets invoked once.
3+
4+
constcommon=require('../common');
5+
constassert=require('assert');
6+
const{ registerHooks }=require('module');
7+
8+
consthook=registerHooks({
9+
resolve: common.mustCall(function(specifier,context,nextResolve){
10+
assert.strictEqual(specifier,'../fixtures/value.cjs');
11+
returnnextResolve(specifier,context);
12+
},1),
13+
});
14+
15+
import('../fixtures/value.cjs').then(common.mustCall((result)=>{
16+
assert.strictEqual(result.value,42);
17+
hook.deregister();
18+
}));

0 commit comments

Comments
 (0)