Skip to content

Commit 2e438ab

Browse files
committed
module: implement module.registerHooks()
1 parent 5ecc8f8 commit 2e438ab

File tree

21 files changed

+572
-23
lines changed

21 files changed

+572
-23
lines changed

lib/internal/modules/cjs/loader.js

Lines changed: 100 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,7 @@ const kIsCachedByESMLoader = Symbol('kIsCachedByESMLoader');
101101
const kRequiredModuleSymbol = Symbol('kRequiredModuleSymbol');
102102
const kIsExecuting = Symbol('kIsExecuting');
103103

104+
const kURL = Symbol('kURL');
104105
const kFormat = Symbol('kFormat');
105106

106107
// Set first due to cycle with ESM loader functions.
@@ -111,6 +112,9 @@ module.exports = {
111112
kModuleCircularVisited,
112113
initializeCJS,
113114
Module,
115+
findLongestRegisteredExtension,
116+
resolveForCJSWithHooks,
117+
loadSourceForCJSWithHooks: loadSource,
114118
wrapSafe,
115119
wrapModuleLoad,
116120
kIsMainSymbol,
@@ -155,6 +159,13 @@ const {
155159
stripBOM,
156160
toRealPath,
157161
} = require('internal/modules/helpers');
162+
const {
163+
convertCJSFilenameToURL,
164+
convertURLToCJSFilename,
165+
loadWithHooks,
166+
registerHooks,
167+
resolveWithHooks,
168+
} = require('internal/modules/sync_hooks');
158169
const { stripTypeScriptModuleTypes } = require('internal/modules/typescript');
159170
const packageJsonReader = require('internal/modules/package_json_reader');
160171
const { getOptionValue, getEmbedderOptions } = require('internal/options');
@@ -584,7 +595,7 @@ function trySelfParentPath(parent) {
584595
* @param {string} parentPath The path of the parent module
585596
* @param {string} request The module request to resolve
586597
*/
587-
function trySelf(parentPath, request) {
598+
function trySelf(parentPath, request, conditions) {
588599
if (!parentPath) { return false; }
589600

590601
const pkg = packageJsonReader.getNearestParentPackageJSON(parentPath);
@@ -605,7 +616,7 @@ function trySelf(parentPath, request) {
605616
const { packageExportsResolve } = require('internal/modules/esm/resolve');
606617
return finalizeEsmResolution(packageExportsResolve(
607618
pathToFileURL(pkg.path), expansion, pkg.data,
608-
pathToFileURL(parentPath), getCjsConditions()), parentPath, pkg.path);
619+
pathToFileURL(parentPath), conditions), parentPath, pkg.path);
609620
} catch (e) {
610621
if (e.code === 'ERR_MODULE_NOT_FOUND') {
611622
throw createEsmNotFoundErr(request, pkg.path);
@@ -626,7 +637,7 @@ const EXPORTS_PATTERN = /^((?:@[^/\\%]+\/)?[^./\\%][^/\\%]*)(\/.*)?$/;
626637
* @param {string} nmPath The path to the module.
627638
* @param {string} request The request for the module.
628639
*/
629-
function resolveExports(nmPath, request) {
640+
function resolveExports(nmPath, request, conditions) {
630641
// The implementation's behavior is meant to mirror resolution in ESM.
631642
const { 1: name, 2: expansion = '' } =
632643
RegExpPrototypeExec(EXPORTS_PATTERN, request) || kEmptyObject;
@@ -638,7 +649,7 @@ function resolveExports(nmPath, request) {
638649
const { packageExportsResolve } = require('internal/modules/esm/resolve');
639650
return finalizeEsmResolution(packageExportsResolve(
640651
pathToFileURL(pkgPath + '/package.json'), '.' + expansion, pkg, null,
641-
getCjsConditions()), null, pkgPath);
652+
conditions), null, pkgPath);
642653
} catch (e) {
643654
if (e.code === 'ERR_MODULE_NOT_FOUND') {
644655
throw createEsmNotFoundErr(request, pkgPath + '/package.json');
@@ -680,7 +691,7 @@ function getDefaultExtensions() {
680691
* @param {boolean} isMain Whether the request is the main app entry point
681692
* @returns {string | false}
682693
*/
683-
Module._findPath = function(request, paths, isMain) {
694+
Module._findPath = function(request, paths, isMain, conditions = getCjsConditions()) {
684695
const absoluteRequest = path.isAbsolute(request);
685696
if (absoluteRequest) {
686697
paths = [''];
@@ -738,7 +749,7 @@ Module._findPath = function(request, paths, isMain) {
738749
}
739750

740751
if (!absoluteRequest) {
741-
const exportsResolved = resolveExports(curPath, request);
752+
const exportsResolved = resolveExports(curPath, request, conditions);
742753
if (exportsResolved) {
743754
return exportsResolved;
744755
}
@@ -1019,6 +1030,50 @@ function getExportsForCircularRequire(module) {
10191030
return module.exports;
10201031
}
10211032

1033+
/**
1034+
* Resolve a module request for CommonJS
1035+
* @param {string} specifier
1036+
* @param {Module|undefined} parent
1037+
* @param {boolean} isMain
1038+
* @returns {{url: string, format?: string, parentURL?: string, filename: string}}
1039+
*/
1040+
function resolveForCJSWithHooks(specifier, parent, isMain) {
1041+
let parentURL;
1042+
if (parent) {
1043+
if (!parent[kURL] && parent.filename) {
1044+
parent[kURL] = convertCJSFilenameToURL(parent.filename);
1045+
}
1046+
parentURL = parent[kURL];
1047+
}
1048+
1049+
let defaultResolvedURL;
1050+
let defaultResolvedFilename;
1051+
function defaultResolve(specifier, context) {
1052+
// TODO(joyeecheung): parent and isMain should be part of context, then we
1053+
// no longer need to use a different defaultResolve for every resolution.
1054+
defaultResolvedFilename = Module._resolveFilename(specifier, parent, isMain, {
1055+
__proto__: null,
1056+
conditions: context.conditions,
1057+
}).toString();
1058+
defaultResolvedURL = convertCJSFilenameToURL(defaultResolvedFilename);
1059+
return { url: defaultResolvedURL };
1060+
}
1061+
1062+
const {
1063+
url, format,
1064+
} = resolveWithHooks(specifier, parentURL, /* importAttributes */undefined,
1065+
getCjsConditions(), defaultResolve);
1066+
1067+
let filename;
1068+
if (url === defaultResolvedURL) { // Not overridden, skip the re-conversion.
1069+
filename = defaultResolvedFilename;
1070+
} else {
1071+
filename = convertURLToCJSFilename(url);
1072+
}
1073+
1074+
return { __proto__: null, url, format, filename, parentURL };
1075+
}
1076+
10221077
/**
10231078
* Load a module from cache if it exists, otherwise create a new module instance.
10241079
* 1. If a module already exists in the cache: return its exports object.
@@ -1065,7 +1120,8 @@ Module._load = function(request, parent, isMain) {
10651120
return module.exports;
10661121
}
10671122

1068-
const filename = Module._resolveFilename(request, parent, isMain);
1123+
const { url, format, filename } = resolveForCJSWithHooks(request, parent, isMain);
1124+
10691125
const cachedModule = Module._cache[filename];
10701126
if (cachedModule !== undefined) {
10711127
updateChildren(parent, cachedModule, true);
@@ -1110,6 +1166,10 @@ Module._load = function(request, parent, isMain) {
11101166
reportModuleToWatchMode(filename);
11111167
Module._cache[filename] = module;
11121168
module[kIsCachedByESMLoader] = false;
1169+
// If there are resolve hooks, carry the context information into the
1170+
// load hooks for the module keyed by the (potentially customized) filename.
1171+
module[kURL] = url;
1172+
module[kFormat] = format;
11131173
}
11141174

11151175
if (parent !== undefined) {
@@ -1157,6 +1217,7 @@ Module._resolveFilename = function(request, parent, isMain, options) {
11571217
if (BuiltinModule.normalizeRequirableId(request)) {
11581218
return request;
11591219
}
1220+
const conditions = (options?.conditions) || getCjsConditions();
11601221

11611222
let paths;
11621223

@@ -1202,7 +1263,7 @@ Module._resolveFilename = function(request, parent, isMain, options) {
12021263
try {
12031264
const { packageImportsResolve } = require('internal/modules/esm/resolve');
12041265
return finalizeEsmResolution(
1205-
packageImportsResolve(request, pathToFileURL(parentPath), getCjsConditions()),
1266+
packageImportsResolve(request, pathToFileURL(parentPath), conditions),
12061267
parentPath,
12071268
pkg.path,
12081269
);
@@ -1217,7 +1278,7 @@ Module._resolveFilename = function(request, parent, isMain, options) {
12171278

12181279
// Try module self resolution first
12191280
const parentPath = trySelfParentPath(parent);
1220-
const selfResolved = trySelf(parentPath, request);
1281+
const selfResolved = trySelf(parentPath, request, conditions);
12211282
if (selfResolved) {
12221283
const cacheKey = request + '\x00' +
12231284
(paths.length === 1 ? paths[0] : ArrayPrototypeJoin(paths, '\x00'));
@@ -1226,7 +1287,7 @@ Module._resolveFilename = function(request, parent, isMain, options) {
12261287
}
12271288

12281289
// Look up the filename first, since that's the cache key.
1229-
const filename = Module._findPath(request, paths, isMain);
1290+
const filename = Module._findPath(request, paths, isMain, conditions);
12301291
if (filename) { return filename; }
12311292
const requireStack = [];
12321293
for (let cursor = parent;
@@ -1293,8 +1354,8 @@ Module.prototype.load = function(filename) {
12931354
debug('load %j for module %j', filename, this.id);
12941355

12951356
assert(!this.loaded);
1296-
this.filename = filename;
1297-
this.paths = Module._nodeModulePaths(path.dirname(filename));
1357+
this.filename ??= filename;
1358+
this.paths ??= Module._nodeModulePaths(path.dirname(filename));
12981359

12991360
const extension = findLongestRegisteredExtension(filename);
13001361

@@ -1563,20 +1624,36 @@ Module.prototype._compile = function(content, filename, format) {
15631624
* @returns {{source: string, format?: string}}
15641625
*/
15651626
function loadSource(mod, filename, formatFromNode) {
1566-
if (formatFromNode !== undefined) {
1627+
if (mod[kFormat] === undefined) {
15671628
mod[kFormat] = formatFromNode;
15681629
}
1569-
const format = mod[kFormat];
1630+
// If the module was loaded before, just return.
1631+
if (mod[kModuleSource] !== undefined) {
1632+
return { source: mod[kModuleSource], format: mod[kFormat] };
1633+
}
15701634

1571-
let source = mod[kModuleSource];
1572-
if (source !== undefined) {
1573-
mod[kModuleSource] = undefined;
1574-
} else {
1575-
// TODO(joyeecheung): we can read a buffer instead to speed up
1576-
// compilation.
1577-
source = fs.readFileSync(filename, 'utf8');
1635+
if (mod[kURL] === undefined) {
1636+
mod[kURL] = convertCJSFilenameToURL(filename);
15781637
}
1579-
return { source, format };
1638+
const url = mod[kURL];
1639+
1640+
const originalFormat = mod[kFormat];
1641+
1642+
function defaultLoad(url, context) {
1643+
// If the url is the same as the original one, save the conversion.
1644+
const newFilename = (url === mod[kURL]) ? filename : convertURLToCJSFilename(url);
1645+
const source = fs.readFileSync(newFilename, 'utf8');
1646+
return { source, format: originalFormat };
1647+
}
1648+
1649+
const loadResult = loadWithHooks(url, originalFormat, undefined, getCjsConditions(), defaultLoad);
1650+
1651+
// Reset the module properties with load hook results.
1652+
if (loadResult.format !== undefined) {
1653+
mod[kFormat] = loadResult.format;
1654+
}
1655+
mod[kModuleSource] = loadResult.source;
1656+
return { source: mod[kModuleSource], format: mod[kFormat] };
15801657
}
15811658

15821659
/**
@@ -1594,7 +1671,6 @@ function loadMTS(mod, filename) {
15941671
* @param {Module} module CJS module instance
15951672
* @param {string} filename The file path of the module
15961673
*/
1597-
15981674
function loadCTS(module, filename) {
15991675
const loadResult = loadSource(module, filename, 'commonjs-typescript');
16001676
module._compile(loadResult.source, filename, loadResult.format);
@@ -1862,3 +1938,4 @@ ObjectDefineProperty(Module.prototype, 'constructor', {
18621938

18631939
// Backwards compatibility
18641940
Module.Module = Module;
1941+
Module.registerHooks = registerHooks;

0 commit comments

Comments
 (0)