@@ -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,29 @@ 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: undefined|ModuleLoadResult} } If there are no load
1174+ * hooks or the load hooks do not override the format of the builtin, load and return the exports
1175+ * of the builtin module. 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+ // Format has been overridden, return result for the caller to continue loading.
1188+ return { builtinExports : undefined , resultFromHook } ;
11821189 }
11831190 }
11841191
11851192 // No hooks or the hooks have not overridden the format. Load it as a builtin module and return the
11861193 // exports.
11871194 const mod = loadBuiltinModule ( id ) ;
1188- return mod . exports ;
1195+ return { builtinExports : mod . exports , resultFromHook : undefined } ;
11891196}
11901197
11911198/**
@@ -1223,47 +1230,64 @@ Module._load = function(request, parent, isMain) {
12231230 }
12241231 }
12251232
1226- const { url, format, filename } = resolveForCJSWithHooks ( request , parent , isMain ) ;
1233+ const resolveResult = resolveForCJSWithHooks ( request , parent , isMain ) ;
1234+ let { format } = resolveResult ;
1235+ const { url, filename } = resolveResult ;
12271236
1237+ let resultFromLoadHook ;
12281238 // For backwards compatibility, if the request itself starts with node:, load it before checking
12291239 // 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 ;
1240+ // TODO(joyeecheung): a more sensible handling is probably, if there are hooks, always go through the hooks
1241+ // first before checking the cache. Otherwise, check the cache first, then proceed to default loading.
1242+ if ( request === url && StringPrototypeStartsWith ( request , 'node:' ) ) {
1243+ const normalized = BuiltinModule . normalizeRequirableId ( request ) ;
1244+ if ( normalized ) { // It's a builtin module.
1245+ const { resultFromHook, builtinExports } = loadBuiltinWithHooks ( normalized , url , format ) ;
1246+ if ( builtinExports ) {
1247+ return builtinExports ;
1248+ }
1249+ // The format of the builtin has been overridden by user hooks. Continue loading.
1250+ resultFromLoadHook = resultFromHook ;
1251+ format = resultFromLoadHook . format ;
12341252 }
1235- // The format of the builtin has been overridden by user hooks. Continue loading.
12361253 }
12371254
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 ) ;
1255+ // If load hooks overrides the format for a built-in, bypass the cache.
1256+ let cachedModule ;
1257+ if ( resultFromLoadHook === undefined ) {
1258+ cachedModule = Module . _cache [ filename ] ;
1259+ debug ( 'Module._load checking cache for' , filename , ! ! cachedModule ) ;
1260+ if ( cachedModule !== undefined ) {
1261+ updateChildren ( parent , cachedModule , true ) ;
1262+ if ( cachedModule . loaded ) {
1263+ return cachedModule . exports ;
1264+ }
1265+ // If it's not cached by the ESM loader, the loading request
1266+ // comes from required CJS, and we can consider it a circular
1267+ // dependency when it's cached.
1268+ if ( ! cachedModule [ kIsCachedByESMLoader ] ) {
1269+ return getExportsForCircularRequire ( cachedModule ) ;
1270+ }
1271+ // If it's cached by the ESM loader as a way to indirectly pass
1272+ // the module in to avoid creating it twice, the loading request
1273+ // came from imported CJS. In that case use the kModuleCircularVisited
1274+ // to determine if it's loading or not.
1275+ if ( cachedModule [ kModuleCircularVisited ] ) {
1276+ return getExportsForCircularRequire ( cachedModule ) ;
1277+ }
1278+ // This is an ESM loader created cache entry, mark it as visited and fallthrough to loading the module.
1279+ cachedModule [ kModuleCircularVisited ] = true ;
12561280 }
1257- // This is an ESM loader created cache entry, mark it as visited and fallthrough to loading the module.
1258- cachedModule [ kModuleCircularVisited ] = true ;
12591281 }
12601282
1261- if ( BuiltinModule . canBeRequiredWithoutScheme ( filename ) ) {
1262- const result = loadBuiltinWithHooks ( filename , url , format ) ;
1263- if ( result ) {
1264- return result ;
1283+ if ( resultFromLoadHook === undefined && BuiltinModule . canBeRequiredWithoutScheme ( filename ) ) {
1284+ const { resultFromHook , builtinExports } = loadBuiltinWithHooks ( filename , url , format ) ;
1285+ if ( builtinExports ) {
1286+ return builtinExports ;
12651287 }
12661288 // The format of the builtin has been overridden by user hooks. Continue loading.
1289+ resultFromLoadHook = resultFromHook ;
1290+ format = resultFromLoadHook . format ;
12671291 }
12681292
12691293 // Don't call updateChildren(), Module constructor already does.
@@ -1278,6 +1302,9 @@ Module._load = function(request, parent, isMain) {
12781302 } else {
12791303 module [ kIsMainSymbol ] = false ;
12801304 }
1305+ if ( resultFromLoadHook !== undefined ) {
1306+ module [ kModuleSource ] = resultFromLoadHook . source ;
1307+ }
12811308
12821309 reportModuleToWatchMode ( filename ) ;
12831310 Module . _cache [ filename ] = module ;
@@ -1463,6 +1490,17 @@ function createEsmNotFoundErr(request, path) {
14631490 return err ;
14641491}
14651492
1493+ function getExtensionForFormat ( format ) {
1494+ switch ( format ) {
1495+ case 'addon' :
1496+ return '.node' ;
1497+ case 'json' :
1498+ return '.json' ;
1499+ default :
1500+ return '.js' ;
1501+ }
1502+ }
1503+
14661504/**
14671505 * Given a file name, pass it to the proper extension handler.
14681506 * @param {string } filename The `require` specifier
@@ -1475,7 +1513,13 @@ Module.prototype.load = function(filename) {
14751513 this . filename ??= filename ;
14761514 this . paths ??= Module . _nodeModulePaths ( path . dirname ( filename ) ) ;
14771515
1478- const extension = findLongestRegisteredExtension ( filename ) ;
1516+ // If the format is already overridden by hooks, convert that back to extension.
1517+ let extension ;
1518+ if ( this [ kFormat ] !== undefined ) {
1519+ extension = getExtensionForFormat ( this [ kFormat ] ) ;
1520+ } else {
1521+ extension = findLongestRegisteredExtension ( filename ) ;
1522+ }
14791523
14801524 Module . _extensions [ extension ] ( this , filename ) ;
14811525 this . loaded = true ;
0 commit comments