@@ -1112,7 +1112,9 @@ function resolveForCJSWithHooks(specifier, parent, isMain) {
11121112 filename = convertURLToCJSFilename ( url ) ;
11131113 }
11141114
1115- return { __proto__ : null , url, format, filename, parentURL } ;
1115+ const result = { __proto__ : null , url, format, filename, parentURL } ;
1116+ debug ( 'resolveForCJSWithHooks' , specifier , parent ?. id , '->' , result ) ;
1117+ return result ;
11161118}
11171119
11181120/**
@@ -1169,24 +1171,29 @@ function getDefaultLoad(url, filename) {
11691171 * @param {string } id The module ID (without the node: prefix)
11701172 * @param {string } url The module URL (with the node: prefix)
11711173 * @param {string } format Format from resolution.
1172- * @returns {any } If there are no load hooks or the load hooks do not override the format of the
1173- * builtin, load and return the exports of the builtin. Otherwise, return undefined.
1174+ * @returns {{builtinExports: any, resultFromHook: undefined|ModuleLoadResult} } If there are no load
1175+ * hooks or the load hooks do not override the format of the builtin, load and return the exports
1176+ * of the builtin module. Otherwise, return the loadResult for the caller to continue loading.
11741177 */
11751178function loadBuiltinWithHooks ( id , url , format ) {
1179+ let resultFromHook ;
11761180 if ( loadHooks . length ) {
11771181 url ??= `node:${ id } ` ;
1182+ debug ( 'loadBuiltinWithHooks ' , loadHooks . length , id , url , format ) ;
11781183 // TODO(joyeecheung): do we really want to invoke the load hook for the builtins?
1179- const loadResult = loadWithHooks ( url , format || 'builtin' , /* importAttributes */ undefined ,
1180- getCjsConditionsArray ( ) , getDefaultLoad ( url , id ) , validateLoadStrict ) ;
1181- if ( loadResult . format && loadResult . format !== 'builtin' ) {
1182- return undefined ; // Format has been overridden, return undefined for the caller to continue loading.
1184+ resultFromHook = loadWithHooks ( url , format || 'builtin' , /* importAttributes */ undefined ,
1185+ getCjsConditionsArray ( ) , getDefaultLoad ( url , id ) , validateLoadStrict ) ;
1186+ if ( resultFromHook . format && resultFromHook . format !== 'builtin' ) {
1187+ debug ( 'loadBuiltinWithHooks overriding module' , id , url , resultFromHook ) ;
1188+ // Format has been overridden, return result for the caller to continue loading.
1189+ return { builtinExports : undefined , resultFromHook } ;
11831190 }
11841191 }
11851192
11861193 // No hooks or the hooks have not overridden the format. Load it as a builtin module and return the
11871194 // exports.
11881195 const mod = loadBuiltinModule ( id ) ;
1189- return mod . exports ;
1196+ return { builtinExports : mod . exports , resultFromHook : undefined } ;
11901197}
11911198
11921199/**
@@ -1224,47 +1231,64 @@ Module._load = function(request, parent, isMain) {
12241231 }
12251232 }
12261233
1227- const { url, format, filename } = resolveForCJSWithHooks ( request , parent , isMain ) ;
1234+ const resolveResult = resolveForCJSWithHooks ( request , parent , isMain ) ;
1235+ let { format } = resolveResult ;
1236+ const { url, filename } = resolveResult ;
12281237
1238+ let resultFromLoadHook ;
12291239 // For backwards compatibility, if the request itself starts with node:, load it before checking
12301240 // Module._cache. Otherwise, load it after the check.
1231- if ( StringPrototypeStartsWith ( request , 'node:' ) ) {
1232- const result = loadBuiltinWithHooks ( filename , url , format ) ;
1233- if ( result ) {
1234- return result ;
1241+ // TODO(joyeecheung): a more sensible handling is probably, if there are hooks, always go through the hooks
1242+ // first before checking the cache. Otherwise, check the cache first, then proceed to default loading.
1243+ if ( request === url && StringPrototypeStartsWith ( request , 'node:' ) ) {
1244+ const normalized = BuiltinModule . normalizeRequirableId ( request ) ;
1245+ if ( normalized ) { // It's a builtin module.
1246+ const { resultFromHook, builtinExports } = loadBuiltinWithHooks ( normalized , url , format ) ;
1247+ if ( builtinExports ) {
1248+ return builtinExports ;
1249+ }
1250+ // The format of the builtin has been overridden by user hooks. Continue loading.
1251+ resultFromLoadHook = resultFromHook ;
1252+ format = resultFromLoadHook . format ;
12351253 }
1236- // The format of the builtin has been overridden by user hooks. Continue loading.
12371254 }
12381255
1239- const cachedModule = Module . _cache [ filename ] ;
1240- if ( cachedModule !== undefined ) {
1241- updateChildren ( parent , cachedModule , true ) ;
1242- if ( cachedModule . loaded ) {
1243- return cachedModule . exports ;
1244- }
1245- // If it's not cached by the ESM loader, the loading request
1246- // comes from required CJS, and we can consider it a circular
1247- // dependency when it's cached.
1248- if ( ! cachedModule [ kIsCachedByESMLoader ] ) {
1249- return getExportsForCircularRequire ( cachedModule ) ;
1250- }
1251- // If it's cached by the ESM loader as a way to indirectly pass
1252- // the module in to avoid creating it twice, the loading request
1253- // came from imported CJS. In that case use the kModuleCircularVisited
1254- // to determine if it's loading or not.
1255- if ( cachedModule [ kModuleCircularVisited ] ) {
1256- return getExportsForCircularRequire ( cachedModule ) ;
1256+ // If load hooks overrides the format for a built-in, bypass the cache.
1257+ let cachedModule ;
1258+ if ( resultFromLoadHook === undefined ) {
1259+ cachedModule = Module . _cache [ filename ] ;
1260+ debug ( 'Module._load checking cache for' , filename , ! ! cachedModule ) ;
1261+ if ( cachedModule !== undefined ) {
1262+ updateChildren ( parent , cachedModule , true ) ;
1263+ if ( cachedModule . loaded ) {
1264+ return cachedModule . exports ;
1265+ }
1266+ // If it's not cached by the ESM loader, the loading request
1267+ // comes from required CJS, and we can consider it a circular
1268+ // dependency when it's cached.
1269+ if ( ! cachedModule [ kIsCachedByESMLoader ] ) {
1270+ return getExportsForCircularRequire ( cachedModule ) ;
1271+ }
1272+ // If it's cached by the ESM loader as a way to indirectly pass
1273+ // the module in to avoid creating it twice, the loading request
1274+ // came from imported CJS. In that case use the kModuleCircularVisited
1275+ // to determine if it's loading or not.
1276+ if ( cachedModule [ kModuleCircularVisited ] ) {
1277+ return getExportsForCircularRequire ( cachedModule ) ;
1278+ }
1279+ // This is an ESM loader created cache entry, mark it as visited and fallthrough to loading the module.
1280+ cachedModule [ kModuleCircularVisited ] = true ;
12571281 }
1258- // This is an ESM loader created cache entry, mark it as visited and fallthrough to loading the module.
1259- cachedModule [ kModuleCircularVisited ] = true ;
12601282 }
12611283
1262- if ( BuiltinModule . canBeRequiredWithoutScheme ( filename ) ) {
1263- const result = loadBuiltinWithHooks ( filename , url , format ) ;
1264- if ( result ) {
1265- return result ;
1284+ if ( resultFromLoadHook === undefined && BuiltinModule . canBeRequiredWithoutScheme ( filename ) ) {
1285+ const { resultFromHook , builtinExports } = loadBuiltinWithHooks ( filename , url , format ) ;
1286+ if ( builtinExports ) {
1287+ return builtinExports ;
12661288 }
12671289 // The format of the builtin has been overridden by user hooks. Continue loading.
1290+ resultFromLoadHook = resultFromHook ;
1291+ format = resultFromLoadHook . format ;
12681292 }
12691293
12701294 // Don't call updateChildren(), Module constructor already does.
@@ -1279,6 +1303,9 @@ Module._load = function(request, parent, isMain) {
12791303 } else {
12801304 module [ kIsMainSymbol ] = false ;
12811305 }
1306+ if ( resultFromLoadHook !== undefined ) {
1307+ module [ kModuleSource ] = resultFromLoadHook . source ;
1308+ }
12821309
12831310 reportModuleToWatchMode ( filename ) ;
12841311 Module . _cache [ filename ] = module ;
@@ -1464,6 +1491,17 @@ function createEsmNotFoundErr(request, path) {
14641491 return err ;
14651492}
14661493
1494+ function getExtensionForFormat ( format ) {
1495+ switch ( format ) {
1496+ case 'addon' :
1497+ return '.node' ;
1498+ case 'json' :
1499+ return '.json' ;
1500+ default :
1501+ return '.js' ;
1502+ }
1503+ }
1504+
14671505/**
14681506 * Given a file name, pass it to the proper extension handler.
14691507 * @param {string } filename The `require` specifier
@@ -1476,7 +1514,13 @@ Module.prototype.load = function(filename) {
14761514 this . filename ??= filename ;
14771515 this . paths ??= Module . _nodeModulePaths ( path . dirname ( filename ) ) ;
14781516
1479- const extension = findLongestRegisteredExtension ( filename ) ;
1517+ // If the format is already overridden by hooks, convert that back to extension.
1518+ let extension ;
1519+ if ( this [ kFormat ] !== undefined ) {
1520+ extension = getExtensionForFormat ( this [ kFormat ] ) ;
1521+ } else {
1522+ extension = findLongestRegisteredExtension ( filename ) ;
1523+ }
14801524
14811525 Module . _extensions [ extension ] ( this , filename ) ;
14821526 this . loaded = true ;
0 commit comments