| FazBrowse GitHub Viewer | Trending | | Home |
| Tools: [Download Repo ZIP] [Original HTTPS Page] |
1 parent 1dd15c9 commit 2d560e4
10 files changed
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -1111,7 +1111,9 @@ function resolveForCJSWithHooks(specifier, parent, isMain) { | |||
| 1111 | 1111 | filename = convertURLToCJSFilename(url); | |
| 1112 | 1112 | } | |
| 1113 | 1113 | ||
| 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; | ||
| 1115 | 1117 | } | |
| 1116 | 1118 | ||
| 1117 | 1119 | /** | |
@@ -1168,24 +1170,29 @@ function getDefaultLoad(url, filename) { | |||
| 1168 | 1170 | * @param {string} id The module ID (without the node: prefix) | |
| 1169 | 1171 | * @param {string} url The module URL (with the node: prefix) | |
| 1170 | 1172 | * @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. | ||
| 1173 | 1176 | */ | |
| 1174 | 1177 | function loadBuiltinWithHooks(id, url, format) { | |
| 1178 | + let resultFromHook; | ||
| 1175 | 1179 | if (loadHooks.length) { | |
| 1176 | 1180 | url ??= `node:${id}`; | |
| 1181 | + debug('loadBuiltinWithHooks ', loadHooks.length, id, url, format); | ||
| 1177 | 1182 | // 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 }; | ||
| 1182 | 1189 | } | |
| 1183 | 1190 | } | |
| 1184 | 1191 | ||
| 1185 | 1192 | // No hooks or the hooks have not overridden the format. Load it as a builtin module and return the | |
| 1186 | 1193 | // exports. | |
| 1187 | 1194 | const mod = loadBuiltinModule(id); | |
| 1188 | - return mod.exports; | ||
| 1195 | + return { builtinExports: mod.exports, resultFromHook: undefined }; | ||
| 1189 | 1196 | } | |
| 1190 | 1197 | ||
| 1191 | 1198 | /** | |
@@ -1223,47 +1230,64 @@ Module._load = function(request, parent, isMain) { | |||
| 1223 | 1230 | } | |
| 1224 | 1231 | } | |
| 1225 | 1232 | ||
| 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; | ||
| 1227 | 1236 | ||
| 1237 | + let resultFromLoadHook; | ||
| 1228 | 1238 | // For backwards compatibility, if the request itself starts with node:, load it before checking | |
| 1229 | 1239 | // 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; | ||
| 1234 | 1252 | } | |
| 1235 | - // The format of the builtin has been overridden by user hooks. Continue loading. | ||
| 1236 | 1253 | } | |
| 1237 | 1254 | ||
| 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; | ||
| 1256 | 1280 | } | |
| 1257 | - // This is an ESM loader created cache entry, mark it as visited and fallthrough to loading the module. | ||
| 1258 | - cachedModule[kModuleCircularVisited] = true; | ||
| 1259 | 1281 | } | |
| 1260 | 1282 | ||
| 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; | ||
| 1265 | 1287 | } | |
| 1266 | 1288 | // The format of the builtin has been overridden by user hooks. Continue loading. | |
| 1289 | + resultFromLoadHook = resultFromHook; | ||
| 1290 | + format = resultFromLoadHook.format; | ||
| 1267 | 1291 | } | |
| 1268 | 1292 | ||
| 1269 | 1293 | // Don't call updateChildren(), Module constructor already does. | |
@@ -1278,6 +1302,9 @@ Module._load = function(request, parent, isMain) { | |||
| 1278 | 1302 | } else { | |
| 1279 | 1303 | module[kIsMainSymbol] = false; | |
| 1280 | 1304 | } | |
| 1305 | + if (resultFromLoadHook !== undefined) { | ||
| 1306 | + module[kModuleSource] = resultFromLoadHook.source; | ||
| 1307 | + } | ||
| 1281 | 1308 | ||
| 1282 | 1309 | reportModuleToWatchMode(filename); | |
| 1283 | 1310 | Module._cache[filename] = module; | |
@@ -1463,6 +1490,17 @@ function createEsmNotFoundErr(request, path) { | |||
| 1463 | 1490 | return err; | |
| 1464 | 1491 | } | |
| 1465 | 1492 | ||
| 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 | + | ||
| 1466 | 1504 | /** | |
| 1467 | 1505 | * Given a file name, pass it to the proper extension handler. | |
| 1468 | 1506 | * @param {string} filename The `require` specifier | |
@@ -1475,7 +1513,13 @@ Module.prototype.load = function(filename) { | |||
| 1475 | 1513 | this.filename ??= filename; | |
| 1476 | 1514 | this.paths ??= Module._nodeModulePaths(path.dirname(filename)); | |
| 1477 | 1515 | ||
| 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 | + } | ||
| 1479 | 1523 | ||
| 1480 | 1524 | Module._extensions[extension](this, filename); | |
| 1481 | 1525 | this.loaded = true; | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -0,0 +1 @@ | |||
| 1 | + export const url = import.meta.url; | ||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -0,0 +1,37 @@ | |||
| 1 | + 'use strict'; | ||
| 2 | + | ||
| 3 | + // This tests that load hooks can override the format of builtin modules | ||
| 4 | + // to 'commonjs' format. | ||
| 5 | + const common = require('../common'); | ||
| 6 | + const assert = require('assert'); | ||
| 7 | + const { registerHooks } = require('module'); | ||
| 8 | + | ||
| 9 | + // Pick a builtin that's unlikely to be loaded already - like zlib. | ||
| 10 | + assert(!process.moduleLoadList.includes('NativeModule zlib')); | ||
| 11 | + | ||
| 12 | + const hook = registerHooks({ | ||
| 13 | + load: common.mustCall(function load(url, context, nextLoad) { | ||
| 14 | + // Only intercept zlib builtin | ||
| 15 | + if (url === 'node:zlib') { | ||
| 16 | + // Return a different format to override the builtin | ||
| 17 | + return { | ||
| 18 | + source: 'exports.custom_zlib = "overridden by load hook";', | ||
| 19 | + format: 'commonjs', | ||
| 20 | + shortCircuit: true, | ||
| 21 | + }; | ||
| 22 | + } | ||
| 23 | + return nextLoad(url, context); | ||
| 24 | + }, 2), // Called twice: once for 'zlib', once for 'node:zlib' | ||
| 25 | + }); | ||
| 26 | + | ||
| 27 | + // Test: Load hook overrides builtin format to commonjs | ||
| 28 | + const zlib = require('zlib'); | ||
| 29 | + assert.strictEqual(zlib.custom_zlib, 'overridden by load hook'); | ||
| 30 | + assert.strictEqual(typeof zlib.createGzip, 'undefined'); // Original zlib API should not be available | ||
| 31 | + | ||
| 32 | + // Test with node: prefix | ||
| 33 | + const zlib2 = require('node:zlib'); | ||
| 34 | + assert.strictEqual(zlib2.custom_zlib, 'overridden by load hook'); | ||
| 35 | + assert.strictEqual(typeof zlib2.createGzip, 'undefined'); | ||
| 36 | + | ||
| 37 | + hook.deregister(); | ||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -0,0 +1,37 @@ | |||
| 1 | + 'use strict'; | ||
| 2 | + | ||
| 3 | + // This tests that load hooks can override the format of builtin modules | ||
| 4 | + // to 'json' format. | ||
| 5 | + const common = require('../common'); | ||
| 6 | + const assert = require('assert'); | ||
| 7 | + const { registerHooks } = require('module'); | ||
| 8 | + | ||
| 9 | + // Pick a builtin that's unlikely to be loaded already - like zlib. | ||
| 10 | + assert(!process.moduleLoadList.includes('NativeModule zlib')); | ||
| 11 | + | ||
| 12 | + const hook = registerHooks({ | ||
| 13 | + load: common.mustCall(function load(url, context, nextLoad) { | ||
| 14 | + // Only intercept zlib builtin | ||
| 15 | + if (url === 'node:zlib') { | ||
| 16 | + // Return JSON format to override the builtin | ||
| 17 | + return { | ||
| 18 | + source: JSON.stringify({ custom_zlib: 'JSON overridden zlib' }), | ||
| 19 | + format: 'json', | ||
| 20 | + shortCircuit: true, | ||
| 21 | + }; | ||
| 22 | + } | ||
| 23 | + return nextLoad(url, context); | ||
| 24 | + }, 2), // Called twice: once for 'zlib', once for 'node:zlib' | ||
| 25 | + }); | ||
| 26 | + | ||
| 27 | + // Test: Load hook overrides builtin format to json | ||
| 28 | + const zlib = require('zlib'); | ||
| 29 | + assert.strictEqual(zlib.custom_zlib, 'JSON overridden zlib'); | ||
| 30 | + assert.strictEqual(typeof zlib.createGzip, 'undefined'); // Original zlib API should not be available | ||
| 31 | + | ||
| 32 | + // Test with node: prefix | ||
| 33 | + const zlib2 = require('node:zlib'); | ||
| 34 | + assert.strictEqual(zlib2.custom_zlib, 'JSON overridden zlib'); | ||
| 35 | + assert.strictEqual(typeof zlib2.createGzip, 'undefined'); | ||
| 36 | + | ||
| 37 | + hook.deregister(); | ||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -0,0 +1,41 @@ | |||
| 1 | + 'use strict'; | ||
| 2 | + | ||
| 3 | + // This tests that load hooks can override the format of builtin modules | ||
| 4 | + // to 'module', and require() can load them. | ||
| 5 | + const common = require('../common'); | ||
| 6 | + const assert = require('assert'); | ||
| 7 | + const { registerHooks } = require('module'); | ||
| 8 | + | ||
| 9 | + // Pick a builtin that's unlikely to be loaded already - like zlib. | ||
| 10 | + assert(!process.moduleLoadList.includes('NativeModule zlib')); | ||
| 11 | + | ||
| 12 | + const hook = registerHooks({ | ||
| 13 | + load: common.mustCall(function load(url, context, nextLoad) { | ||
| 14 | + // Only intercept zlib builtin | ||
| 15 | + if (url === 'node:zlib') { | ||
| 16 | + // Return ES module format to override the builtin | ||
| 17 | + // Note: For require() to work with ESM, we need to export 'module.exports' | ||
| 18 | + return { | ||
| 19 | + source: `const exports = { custom_zlib: "ESM overridden zlib" }; | ||
| 20 | + export default exports; | ||
| 21 | + export { exports as 'module.exports' };`, | ||
| 22 | + format: 'module', | ||
| 23 | + shortCircuit: true, | ||
| 24 | + }; | ||
| 25 | + } | ||
| 26 | + return nextLoad(url, context); | ||
| 27 | + }, 2), // Called twice: once for 'zlib', once for 'node:zlib' | ||
| 28 | + }); | ||
| 29 | + | ||
| 30 | + // Test: Load hook overrides builtin format to module. | ||
| 31 | + // With the 'module.exports' export, require() should work | ||
| 32 | + const zlib = require('zlib'); | ||
| 33 | + assert.strictEqual(zlib.custom_zlib, 'ESM overridden zlib'); | ||
| 34 | + assert.strictEqual(typeof zlib.createGzip, 'undefined'); // Original zlib API should not be available | ||
| 35 | + | ||
| 36 | + // Test with node: prefix | ||
| 37 | + const zlib2 = require('node:zlib'); | ||
| 38 | + assert.strictEqual(zlib2.custom_zlib, 'ESM overridden zlib'); | ||
| 39 | + assert.strictEqual(typeof zlib2.createGzip, 'undefined'); | ||
| 40 | + | ||
| 41 | + hook.deregister(); | ||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -0,0 +1,33 @@ | |||
| 1 | + 'use strict'; | ||
| 2 | + | ||
| 3 | + // This tests that builtins can be redirected to a local file when they are prefixed | ||
| 4 | + // with `node:`. | ||
| 5 | + require('../common'); | ||
| 6 | + | ||
| 7 | + const assert = require('assert'); | ||
| 8 | + const { registerHooks } = require('module'); | ||
| 9 | + const fixtures = require('../common/fixtures'); | ||
| 10 | + | ||
| 11 | + // This tests that builtins can be redirected to a local file. | ||
| 12 | + // Pick a builtin that's unlikely to be loaded already - like zlib. | ||
| 13 | + assert(!process.moduleLoadList.includes('NativeModule zlib')); | ||
| 14 | + | ||
| 15 | + const hook = registerHooks({ | ||
| 16 | + resolve(specifier, context, nextLoad) { | ||
| 17 | + specifier = specifier.replaceAll('node:', ''); | ||
| 18 | + return { | ||
| 19 | + url: fixtures.fileURL('module-hooks', `redirected-${specifier}.js`).href, | ||
| 20 | + shortCircuit: true, | ||
| 21 | + }; | ||
| 22 | + }, | ||
| 23 | + }); | ||
| 24 | + | ||
| 25 | + // Check assert, which is already loaded. | ||
| 26 | + // eslint-disable-next-line node-core/must-call-assert | ||
| 27 | + assert.strictEqual(require('node:assert').exports_for_test, 'redirected assert'); | ||
| 28 | + // Check zlib, which is not yet loaded. | ||
| 29 | + assert.strictEqual(require('node:zlib').exports_for_test, 'redirected zlib'); | ||
| 30 | + // Check fs, which is redirected to an ESM | ||
| 31 | + assert.strictEqual(require('node:fs').exports_for_test, 'redirected fs'); | ||
| 32 | + | ||
| 33 | + hook.deregister(); | ||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -0,0 +1,36 @@ | |||
| 1 | + 'use strict'; | ||
| 2 | + | ||
| 3 | + // This tests the interaction between resolve and load hooks for builtins with the | ||
| 4 | + // `node:` prefix. | ||
| 5 | + const common = require('../common'); | ||
| 6 | + const assert = require('assert'); | ||
| 7 | + const { registerHooks } = require('module'); | ||
| 8 | + const fixtures = require('../common/fixtures'); | ||
| 9 | + | ||
| 10 | + // Pick a builtin that's unlikely to be loaded already - like zlib. | ||
| 11 | + assert(!process.moduleLoadList.includes('NativeModule zlib')); | ||
| 12 | + | ||
| 13 | + const redirectedURL = fixtures.fileURL('module-hooks/redirected-zlib.js').href; | ||
| 14 | + | ||
| 15 | + registerHooks({ | ||
| 16 | + resolve: common.mustCall(function resolve(specifier, context, nextResolve) { | ||
| 17 | + assert.strictEqual(specifier, 'node:zlib'); | ||
| 18 | + return { | ||
| 19 | + url: redirectedURL, | ||
| 20 | + format: 'module', | ||
| 21 | + shortCircuit: true, | ||
| 22 | + }; | ||
| 23 | + }), | ||
| 24 | + | ||
| 25 | + load: common.mustCall(function load(url, context, nextLoad) { | ||
| 26 | + assert.strictEqual(url, redirectedURL); | ||
| 27 | + return { | ||
| 28 | + source: 'export const loadURL = import.meta.url;', | ||
| 29 | + format: 'module', | ||
| 30 | + shortCircuit: true, | ||
| 31 | + }; | ||
| 32 | + }), | ||
| 33 | + }); | ||
| 34 | + | ||
| 35 | + const zlib = require('node:zlib'); | ||
| 36 | + assert.strictEqual(zlib.loadURL, redirectedURL); | ||
| Back | FazBrowse Home | New Git URL |
0 commit comments