@@ -1111,7 +1111,9 @@ function resolveForCJSWithHooks(specifier, parent, isMain) {
11111111 filename = convertURLToCJSFilename ( url ) ;
11121112 }
11131113
1114- return { __proto__ : null , url, format, filename, parentURL } ;
1114+ const result = { __proto__ : null , url, format, filename, parentURL } ;
1115+ debug ( 'resolveForCJSWithHooks' , specifier , parent ?. id , '->' , result ) ;
1116+ return result ;
11151117}
11161118
11171119/**
@@ -1168,24 +1170,28 @@ function getDefaultLoad(url, filename) {
11681170 * @param {string } id The module ID (without the node: prefix)
11691171 * @param {string } url The module URL (with the node: prefix)
11701172 * @param {string } format Format from resolution.
1171- * @returns {any } If there are no load hooks or the load hooks do not override the format of the
1172- * builtin, load and return the exports of the builtin. Otherwise, return undefined.
1173+ * @returns {{builtinExports?: any, resultFromHook?: any} } If there are no load hooks or the load hooks do not
1174+ * override the format of the builtin, load and return the exports of the builtin module.
1175+ * Otherwise, return the loadResult for the caller to continue loading.
11731176 */
11741177function loadBuiltinWithHooks ( id , url , format ) {
1178+ let resultFromHook ;
11751179 if ( loadHooks . length ) {
11761180 url ??= `node:${ id } ` ;
1181+ debug ( 'loadBuiltinWithHooks ' , loadHooks . length , id , url , format ) ;
11771182 // TODO(joyeecheung): do we really want to invoke the load hook for the builtins?
1178- const loadResult = loadWithHooks ( url , format || 'builtin' , /* importAttributes */ undefined ,
1179- getCjsConditionsArray ( ) , getDefaultLoad ( url , id ) , validateLoadStrict ) ;
1180- if ( loadResult . format && loadResult . format !== 'builtin' ) {
1181- return undefined ; // Format has been overridden, return undefined for the caller to continue loading.
1183+ resultFromHook = loadWithHooks ( url , format || 'builtin' , /* importAttributes */ undefined ,
1184+ getCjsConditionsArray ( ) , getDefaultLoad ( url , id ) , validateLoadStrict ) ;
1185+ if ( resultFromHook . format && resultFromHook . format !== 'builtin' ) {
1186+ debug ( 'loadBuiltinWithHooks overriding module' , id , url , resultFromHook ) ;
1187+ return { resultFromHook } ; // Format has been overridden, return result for the caller to continue loading.
11821188 }
11831189 }
11841190
11851191 // No hooks or the hooks have not overridden the format. Load it as a builtin module and return the
11861192 // exports.
11871193 const mod = loadBuiltinModule ( id ) ;
1188- return mod . exports ;
1194+ return { builtinExports : mod . exports } ;
11891195}
11901196
11911197/**
@@ -1223,47 +1229,64 @@ Module._load = function(request, parent, isMain) {
12231229 }
12241230 }
12251231
1226- const { url, format, filename } = resolveForCJSWithHooks ( request , parent , isMain ) ;
1232+ const resolveResult = resolveForCJSWithHooks ( request , parent , isMain ) ;
1233+ let { format } = resolveResult ;
1234+ const { url, filename } = resolveResult ;
12271235
1236+ let resultFromLoadHook ;
12281237 // For backwards compatibility, if the request itself starts with node:, load it before checking
12291238 // Module._cache. Otherwise, load it after the check.
1230- if ( StringPrototypeStartsWith ( request , 'node:' ) ) {
1231- const result = loadBuiltinWithHooks ( filename , url , format ) ;
1232- if ( result ) {
1233- return result ;
1239+ // TODO(joyeecheung): a more sensible handling is probably, if there are hooks, always go through the hooks
1240+ // first before checking the cache. Otherwise, check the cache first, then proceed to default loading.
1241+ if ( request === url && StringPrototypeStartsWith ( request , 'node:' ) ) {
1242+ const normalized = BuiltinModule . normalizeRequirableId ( request ) ;
1243+ if ( normalized ) { // It's a builtin module.
1244+ const { resultFromHook, builtinExports } = loadBuiltinWithHooks ( normalized , url , format ) ;
1245+ if ( builtinExports ) {
1246+ return builtinExports ;
1247+ }
1248+ // The format of the builtin has been overridden by user hooks. Continue loading.
1249+ resultFromLoadHook = resultFromHook ;
1250+ format = resultFromLoadHook . format ;
12341251 }
1235- // The format of the builtin has been overridden by user hooks. Continue loading.
12361252 }
12371253
1238- const cachedModule = Module . _cache [ filename ] ;
1239- if ( cachedModule !== undefined ) {
1240- updateChildren ( parent , cachedModule , true ) ;
1241- if ( cachedModule . loaded ) {
1242- return cachedModule . exports ;
1243- }
1244- // If it's not cached by the ESM loader, the loading request
1245- // comes from required CJS, and we can consider it a circular
1246- // dependency when it's cached.
1247- if ( ! cachedModule [ kIsCachedByESMLoader ] ) {
1248- return getExportsForCircularRequire ( cachedModule ) ;
1249- }
1250- // If it's cached by the ESM loader as a way to indirectly pass
1251- // the module in to avoid creating it twice, the loading request
1252- // came from imported CJS. In that case use the kModuleCircularVisited
1253- // to determine if it's loading or not.
1254- if ( cachedModule [ kModuleCircularVisited ] ) {
1255- return getExportsForCircularRequire ( cachedModule ) ;
1254+ // If load hooks overrides the format for a built-in, bypass the cache.
1255+ let cachedModule ;
1256+ if ( resultFromLoadHook === undefined ) {
1257+ cachedModule = Module . _cache [ filename ] ;
1258+ debug ( 'Module._load checking cache for' , filename , ! ! cachedModule ) ;
1259+ if ( cachedModule !== undefined ) {
1260+ updateChildren ( parent , cachedModule , true ) ;
1261+ if ( cachedModule . loaded ) {
1262+ return cachedModule . exports ;
1263+ }
1264+ // If it's not cached by the ESM loader, the loading request
1265+ // comes from required CJS, and we can consider it a circular
1266+ // dependency when it's cached.
1267+ if ( ! cachedModule [ kIsCachedByESMLoader ] ) {
1268+ return getExportsForCircularRequire ( cachedModule ) ;
1269+ }
1270+ // If it's cached by the ESM loader as a way to indirectly pass
1271+ // the module in to avoid creating it twice, the loading request
1272+ // came from imported CJS. In that case use the kModuleCircularVisited
1273+ // to determine if it's loading or not.
1274+ if ( cachedModule [ kModuleCircularVisited ] ) {
1275+ return getExportsForCircularRequire ( cachedModule ) ;
1276+ }
1277+ // This is an ESM loader created cache entry, mark it as visited and fallthrough to loading the module.
1278+ cachedModule [ kModuleCircularVisited ] = true ;
12561279 }
1257- // This is an ESM loader created cache entry, mark it as visited and fallthrough to loading the module.
1258- cachedModule [ kModuleCircularVisited ] = true ;
12591280 }
12601281
1261- if ( BuiltinModule . canBeRequiredWithoutScheme ( filename ) ) {
1262- const result = loadBuiltinWithHooks ( filename , url , format ) ;
1263- if ( result ) {
1264- return result ;
1282+ if ( resultFromLoadHook === undefined && BuiltinModule . canBeRequiredWithoutScheme ( filename ) ) {
1283+ const { resultFromHook , builtinExports } = loadBuiltinWithHooks ( filename , url , format ) ;
1284+ if ( builtinExports ) {
1285+ return builtinExports ;
12651286 }
12661287 // The format of the builtin has been overridden by user hooks. Continue loading.
1288+ resultFromLoadHook = resultFromHook ;
1289+ format = resultFromLoadHook . format ;
12671290 }
12681291
12691292 // Don't call updateChildren(), Module constructor already does.
@@ -1278,6 +1301,9 @@ Module._load = function(request, parent, isMain) {
12781301 } else {
12791302 module [ kIsMainSymbol ] = false ;
12801303 }
1304+ if ( resultFromLoadHook !== undefined ) {
1305+ module [ kModuleSource ] = resultFromLoadHook . source ;
1306+ }
12811307
12821308 reportModuleToWatchMode ( filename ) ;
12831309 Module . _cache [ filename ] = module ;
@@ -1463,6 +1489,17 @@ function createEsmNotFoundErr(request, path) {
14631489 return err ;
14641490}
14651491
1492+ function getExtensionForFormat ( format ) {
1493+ switch ( format ) {
1494+ case 'addon' :
1495+ return '.node' ;
1496+ case 'json' :
1497+ return '.json' ;
1498+ default :
1499+ return '.js' ;
1500+ }
1501+ }
1502+
14661503/**
14671504 * Given a file name, pass it to the proper extension handler.
14681505 * @param {string } filename The `require` specifier
@@ -1475,7 +1512,13 @@ Module.prototype.load = function(filename) {
14751512 this . filename ??= filename ;
14761513 this . paths ??= Module . _nodeModulePaths ( path . dirname ( filename ) ) ;
14771514
1478- const extension = findLongestRegisteredExtension ( filename ) ;
1515+ // If the format is already overridden by hooks, convert that back to extension.
1516+ let extension ;
1517+ if ( this [ kFormat ] !== undefined ) {
1518+ extension = getExtensionForFormat ( this [ kFormat ] ) ;
1519+ } else {
1520+ extension = findLongestRegisteredExtension ( filename ) ;
1521+ }
14791522
14801523 Module . _extensions [ extension ] ( this , filename ) ;
14811524 this . loaded = true ;
0 commit comments