module: fix sync resolve hooks for require with node: prefixes · nodejs/node@e3b5593
@@ -1112,7 +1112,9 @@ function resolveForCJSWithHooks(specifier, parent, isMain) {
11121112filename = convertURLToCJSFilename(url);
11131113}
111411141115-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}
1117111911181120/**
@@ -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;
11761180if (loadHooks.length) {
11771181url ??= `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}
1185119211861193// No hooks or the hooks have not overridden the format. Load it as a builtin module and return the
11871194// exports.
11881195const mod = loadBuiltinModule(id);
1189-return mod.exports;
1196+return { builtinExports: mod.exports, resultFromHook: undefined };
11901197}
1191119811921199/**
@@ -1224,47 +1231,64 @@ Module._load = function(request, parent, isMain) {
12241231}
12251232}
122612331227-const { url, format, filename } = resolveForCJSWithHooks(request, parent, isMain);
1234+const resolveResult = resolveForCJSWithHooks(request, parent, isMain);
1235+let { format } = resolveResult;
1236+const { url, filename } = resolveResult;
122812371238+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}
123812551239-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}
126112831262-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}
1269129312701294// Don't call updateChildren(), Module constructor already does.
@@ -1279,6 +1303,9 @@ Module._load = function(request, parent, isMain) {
12791303} else {
12801304module[kIsMainSymbol] = false;
12811305}
1306+if (resultFromLoadHook !== undefined) {
1307+module[kModuleSource] = resultFromLoadHook.source;
1308+}
1282130912831310reportModuleToWatchMode(filename);
12841311Module._cache[filename] = module;
@@ -1464,6 +1491,17 @@ function createEsmNotFoundErr(request, path) {
14641491return err;
14651492}
146614931494+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) {
14761514this.filename ??= filename;
14771515this.paths ??= Module._nodeModulePaths(path.dirname(filename));
147815161479-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+}
1480152414811525Module._extensions[extension](this, filename);
14821526this.loaded = true;