@@ -1086,7 +1086,9 @@ function resolveForCJSWithHooks(specifier, parent, isMain) {
10861086 filename = convertURLToCJSFilename ( url ) ;
10871087 }
10881088
1089- return { __proto__ : null , url, format, filename, parentURL } ;
1089+ const result = { __proto__ : null , url, format, filename, parentURL } ;
1090+ debug ( 'resolveForCJSWithHooks' , specifier , parent ?. id , '->' , result ) ;
1091+ return result ;
10901092}
10911093
10921094/**
@@ -1143,24 +1145,29 @@ function getDefaultLoad(url, filename) {
11431145 * @param {string } id The module ID (without the node: prefix)
11441146 * @param {string } url The module URL (with the node: prefix)
11451147 * @param {string } format Format from resolution.
1146- * @returns {any } If there are no load hooks or the load hooks do not override the format of the
1147- * builtin, load and return the exports of the builtin. Otherwise, return undefined.
1148+ * @returns {{builtinExports: any, resultFromHook: undefined|ModuleLoadResult} } If there are no load
1149+ * hooks or the load hooks do not override the format of the builtin, load and return the exports
1150+ * of the builtin module. Otherwise, return the loadResult for the caller to continue loading.
11481151 */
11491152function loadBuiltinWithHooks ( id , url , format ) {
1153+ let resultFromHook ;
11501154 if ( loadHooks . length ) {
11511155 url ??= `node:${ id } ` ;
1156+ debug ( 'loadBuiltinWithHooks ' , loadHooks . length , id , url , format ) ;
11521157 // TODO(joyeecheung): do we really want to invoke the load hook for the builtins?
1153- const loadResult = loadWithHooks ( url , format || 'builtin' , /* importAttributes */ undefined ,
1154- getCjsConditionsArray ( ) , getDefaultLoad ( url , id ) , validateLoadStrict ) ;
1155- if ( loadResult . format && loadResult . format !== 'builtin' ) {
1156- return undefined ; // Format has been overridden, return undefined for the caller to continue loading.
1158+ resultFromHook = loadWithHooks ( url , format || 'builtin' , /* importAttributes */ undefined ,
1159+ getCjsConditionsArray ( ) , getDefaultLoad ( url , id ) , validateLoadStrict ) ;
1160+ if ( resultFromHook . format && resultFromHook . format !== 'builtin' ) {
1161+ debug ( 'loadBuiltinWithHooks overriding module' , id , url , resultFromHook ) ;
1162+ // Format has been overridden, return result for the caller to continue loading.
1163+ return { builtinExports : undefined , resultFromHook } ;
11571164 }
11581165 }
11591166
11601167 // No hooks or the hooks have not overridden the format. Load it as a builtin module and return the
11611168 // exports.
11621169 const mod = loadBuiltinModule ( id ) ;
1163- return mod . exports ;
1170+ return { builtinExports : mod . exports , resultFromHook : undefined } ;
11641171}
11651172
11661173/**
@@ -1197,47 +1204,64 @@ Module._load = function(request, parent, isMain) {
11971204 }
11981205 }
11991206
1200- const { url, format, filename } = resolveForCJSWithHooks ( request , parent , isMain ) ;
1207+ const resolveResult = resolveForCJSWithHooks ( request , parent , isMain ) ;
1208+ let { format } = resolveResult ;
1209+ const { url, filename } = resolveResult ;
12011210
1211+ let resultFromLoadHook ;
12021212 // For backwards compatibility, if the request itself starts with node:, load it before checking
12031213 // Module._cache. Otherwise, load it after the check.
1204- if ( StringPrototypeStartsWith ( request , 'node:' ) ) {
1205- const result = loadBuiltinWithHooks ( filename , url , format ) ;
1206- if ( result ) {
1207- return result ;
1214+ // TODO(joyeecheung): a more sensible handling is probably, if there are hooks, always go through the hooks
1215+ // first before checking the cache. Otherwise, check the cache first, then proceed to default loading.
1216+ if ( request === url && StringPrototypeStartsWith ( request , 'node:' ) ) {
1217+ const normalized = BuiltinModule . normalizeRequirableId ( request ) ;
1218+ if ( normalized ) { // It's a builtin module.
1219+ const { resultFromHook, builtinExports } = loadBuiltinWithHooks ( normalized , url , format ) ;
1220+ if ( builtinExports ) {
1221+ return builtinExports ;
1222+ }
1223+ // The format of the builtin has been overridden by user hooks. Continue loading.
1224+ resultFromLoadHook = resultFromHook ;
1225+ format = resultFromLoadHook . format ;
12081226 }
1209- // The format of the builtin has been overridden by user hooks. Continue loading.
12101227 }
12111228
1212- const cachedModule = Module . _cache [ filename ] ;
1213- if ( cachedModule !== undefined ) {
1214- updateChildren ( parent , cachedModule , true ) ;
1215- if ( cachedModule . loaded ) {
1216- return cachedModule . exports ;
1217- }
1218- // If it's not cached by the ESM loader, the loading request
1219- // comes from required CJS, and we can consider it a circular
1220- // dependency when it's cached.
1221- if ( ! cachedModule [ kIsCachedByESMLoader ] ) {
1222- return getExportsForCircularRequire ( cachedModule ) ;
1223- }
1224- // If it's cached by the ESM loader as a way to indirectly pass
1225- // the module in to avoid creating it twice, the loading request
1226- // came from imported CJS. In that case use the kModuleCircularVisited
1227- // to determine if it's loading or not.
1228- if ( cachedModule [ kModuleCircularVisited ] ) {
1229- return getExportsForCircularRequire ( cachedModule ) ;
1229+ // If load hooks overrides the format for a built-in, bypass the cache.
1230+ let cachedModule ;
1231+ if ( resultFromLoadHook === undefined ) {
1232+ cachedModule = Module . _cache [ filename ] ;
1233+ debug ( 'Module._load checking cache for' , filename , ! ! cachedModule ) ;
1234+ if ( cachedModule !== undefined ) {
1235+ updateChildren ( parent , cachedModule , true ) ;
1236+ if ( cachedModule . loaded ) {
1237+ return cachedModule . exports ;
1238+ }
1239+ // If it's not cached by the ESM loader, the loading request
1240+ // comes from required CJS, and we can consider it a circular
1241+ // dependency when it's cached.
1242+ if ( ! cachedModule [ kIsCachedByESMLoader ] ) {
1243+ return getExportsForCircularRequire ( cachedModule ) ;
1244+ }
1245+ // If it's cached by the ESM loader as a way to indirectly pass
1246+ // the module in to avoid creating it twice, the loading request
1247+ // came from imported CJS. In that case use the kModuleCircularVisited
1248+ // to determine if it's loading or not.
1249+ if ( cachedModule [ kModuleCircularVisited ] ) {
1250+ return getExportsForCircularRequire ( cachedModule ) ;
1251+ }
1252+ // This is an ESM loader created cache entry, mark it as visited and fallthrough to loading the module.
1253+ cachedModule [ kModuleCircularVisited ] = true ;
12301254 }
1231- // This is an ESM loader created cache entry, mark it as visited and fallthrough to loading the module.
1232- cachedModule [ kModuleCircularVisited ] = true ;
12331255 }
12341256
1235- if ( BuiltinModule . canBeRequiredWithoutScheme ( filename ) ) {
1236- const result = loadBuiltinWithHooks ( filename , url , format ) ;
1237- if ( result ) {
1238- return result ;
1257+ if ( resultFromLoadHook === undefined && BuiltinModule . canBeRequiredWithoutScheme ( filename ) ) {
1258+ const { resultFromHook , builtinExports } = loadBuiltinWithHooks ( filename , url , format ) ;
1259+ if ( builtinExports ) {
1260+ return builtinExports ;
12391261 }
12401262 // The format of the builtin has been overridden by user hooks. Continue loading.
1263+ resultFromLoadHook = resultFromHook ;
1264+ format = resultFromLoadHook . format ;
12411265 }
12421266
12431267 // Don't call updateChildren(), Module constructor already does.
@@ -1252,6 +1276,9 @@ Module._load = function(request, parent, isMain) {
12521276 } else {
12531277 module [ kIsMainSymbol ] = false ;
12541278 }
1279+ if ( resultFromLoadHook !== undefined ) {
1280+ module [ kModuleSource ] = resultFromLoadHook . source ;
1281+ }
12551282
12561283 reportModuleToWatchMode ( filename ) ;
12571284 Module . _cache [ filename ] = module ;
@@ -1436,6 +1463,17 @@ function createEsmNotFoundErr(request, path) {
14361463 return err ;
14371464}
14381465
1466+ function getExtensionForFormat ( format ) {
1467+ switch ( format ) {
1468+ case 'addon' :
1469+ return '.node' ;
1470+ case 'json' :
1471+ return '.json' ;
1472+ default :
1473+ return '.js' ;
1474+ }
1475+ }
1476+
14391477/**
14401478 * Given a file name, pass it to the proper extension handler.
14411479 * @param {string } filename The `require` specifier
@@ -1447,7 +1485,13 @@ Module.prototype.load = function(filename) {
14471485 this . filename ??= filename ;
14481486 this . paths ??= Module . _nodeModulePaths ( path . dirname ( filename ) ) ;
14491487
1450- const extension = findLongestRegisteredExtension ( filename ) ;
1488+ // If the format is already overridden by hooks, convert that back to extension.
1489+ let extension ;
1490+ if ( this [ kFormat ] !== undefined ) {
1491+ extension = getExtensionForFormat ( this [ kFormat ] ) ;
1492+ } else {
1493+ extension = findLongestRegisteredExtension ( filename ) ;
1494+ }
14511495
14521496 Module . _extensions [ extension ] ( this , filename ) ;
14531497 this . loaded = true ;
0 commit comments