module: fix sync resolve hooks for require with node: prefixes · nodejs/node@e3b5593

@@ -1112,7 +1112,9 @@ function resolveForCJSWithHooks(specifier, parent, isMain) {

11121112

filename = 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

*/

11751178

function 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

}

1185119211861193

// 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

}

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 {

12801304

module[kIsMainSymbol] = false;

12811305

}

1306+

if (resultFromLoadHook !== undefined) {

1307+

module[kModuleSource] = resultFromLoadHook.source;

1308+

}

1282130912831310

reportModuleToWatchMode(filename);

12841311

Module._cache[filename] = module;

@@ -1464,6 +1491,17 @@ function createEsmNotFoundErr(request, path) {

14641491

return 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) {

14761514

this.filename ??= filename;

14771515

this.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+

}

1480152414811525

Module._extensions[extension](this, filename);

14821526

this.loaded = true;