| FazBrowse GitHub Viewer | Trending | | Home |
| Tools: [Download Repo ZIP] [Original HTTPS Page] |
1 parent 4dae68c commit 51b88fa
28 files changed
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -2521,6 +2521,21 @@ Accessing `Object.prototype.__proto__` has been forbidden using | |||
| 2521 | 2521 | [`Object.setPrototypeOf`][] should be used to get and set the prototype of an | |
| 2522 | 2522 | object. | |
| 2523 | 2523 | ||
| 2524 | + <a id="ERR_REQUIRE_CYCLE_MODULE"></a> | ||
| 2525 | + | ||
| 2526 | + ### `ERR_REQUIRE_CYCLE_MODULE` | ||
| 2527 | + | ||
| 2528 | + > Stability: 1 - Experimental | ||
| 2529 | + | ||
| 2530 | + When trying to `require()` a [ES Module][] under `--experimental-require-module`, | ||
| 2531 | + a CommonJS to ESM or ESM to CommonJS edge participates in an immediate cycle. | ||
| 2532 | + This is not allowed because ES Modules cannot be evaluated while they are | ||
| 2533 | + already being evaluated. | ||
| 2534 | + | ||
| 2535 | + To avoid the cycle, the `require()` call involved in a cycle should not happen | ||
| 2536 | + at the top-level of either a ES Module (via `createRequire()`) or a CommonJS | ||
| 2537 | + module, and should be done lazily in an inner function. | ||
| 2538 | + | ||
| 2524 | 2539 | <a id="ERR_REQUIRE_ASYNC_MODULE"></a> | |
| 2525 | 2540 | ||
| 2526 | 2541 | ### `ERR_REQUIRE_ASYNC_MODULE` | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -1692,6 +1692,7 @@ E('ERR_PARSE_ARGS_UNKNOWN_OPTION', (option, allowPositionals) => { | |||
| 1692 | 1692 | E('ERR_PERFORMANCE_INVALID_TIMESTAMP', | |
| 1693 | 1693 | '%d is not a valid timestamp', TypeError); | |
| 1694 | 1694 | E('ERR_PERFORMANCE_MEASURE_INVALID_OPTIONS', '%s', TypeError); | |
| 1695 | + E('ERR_REQUIRE_CYCLE_MODULE', '%s', Error); | ||
| 1695 | 1696 | E('ERR_REQUIRE_ESM', | |
| 1696 | 1697 | function(filename, hasEsmSyntax, parentPath = null, packageJsonPath = null) { | |
| 1697 | 1698 | hideInternalStackFrames(this); | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -63,24 +63,33 @@ const { | |||
| 63 | 63 | Symbol, | |
| 64 | 64 | } = primordials; | |
| 65 | 65 | ||
| 66 | + const { kEvaluated } = internalBinding('module_wrap'); | ||
| 67 | + | ||
| 66 | 68 | // Map used to store CJS parsing data or for ESM loading. | |
| 67 | - const cjsSourceCache = new SafeWeakMap(); | ||
| 69 | + const importedCJSCache = new SafeWeakMap(); | ||
| 68 | 70 | /** | |
| 69 | 71 | * Map of already-loaded CJS modules to use. | |
| 70 | 72 | */ | |
| 71 | 73 | const cjsExportsCache = new SafeWeakMap(); | |
| 74 | + const requiredESMSourceCache = new SafeWeakMap(); | ||
| 72 | 75 | ||
| 76 | + const kIsMainSymbol = Symbol('kIsMainSymbol'); | ||
| 77 | + const kIsCachedByESMLoader = Symbol('kIsCachedByESMLoader'); | ||
| 78 | + const kRequiredModuleSymbol = Symbol('kRequiredModuleSymbol'); | ||
| 79 | + const kIsExecuting = Symbol('kIsExecuting'); | ||
| 73 | 80 | // Set first due to cycle with ESM loader functions. | |
| 74 | 81 | module.exports = { | |
| 75 | 82 | cjsExportsCache, | |
| 76 | - cjsSourceCache, | ||
| 83 | + importedCJSCache, | ||
| 77 | 84 | initializeCJS, | |
| 78 | 85 | Module, | |
| 79 | 86 | wrapSafe, | |
| 87 | + kIsMainSymbol, | ||
| 88 | + kIsCachedByESMLoader, | ||
| 89 | + kRequiredModuleSymbol, | ||
| 90 | + kIsExecuting, | ||
| 80 | 91 | }; | |
| 81 | 92 | ||
| 82 | - const kIsMainSymbol = Symbol('kIsMainSymbol'); | ||
| 83 | - | ||
| 84 | 93 | const { BuiltinModule } = require('internal/bootstrap/realm'); | |
| 85 | 94 | const { | |
| 86 | 95 | maybeCacheSourceMap, | |
@@ -137,6 +146,7 @@ const { | |||
| 137 | 146 | codes: { | |
| 138 | 147 | ERR_INVALID_ARG_VALUE, | |
| 139 | 148 | ERR_INVALID_MODULE_SPECIFIER, | |
| 149 | + ERR_REQUIRE_CYCLE_MODULE, | ||
| 140 | 150 | ERR_REQUIRE_ESM, | |
| 141 | 151 | ERR_UNKNOWN_BUILTIN_MODULE, | |
| 142 | 152 | }, | |
@@ -942,6 +952,16 @@ const CircularRequirePrototypeWarningProxy = new Proxy({}, { | |||
| 942 | 952 | * @param {Module} module The module instance | |
| 943 | 953 | */ | |
| 944 | 954 | function getExportsForCircularRequire(module) { | |
| 955 | + const requiredESM = module[kRequiredModuleSymbol]; | ||
| 956 | + if (requiredESM && requiredESM.getStatus() !== kEvaluated) { | ||
| 957 | + let message = `Cannot require() ES Module ${module.id} in a cycle.`; | ||
| 958 | + const parent = moduleParentCache.get(module); | ||
| 959 | + if (parent) { | ||
| 960 | + message += ` (from ${parent.filename})`; | ||
| 961 | + } | ||
| 962 | + throw new ERR_REQUIRE_CYCLE_MODULE(message); | ||
| 963 | + } | ||
| 964 | + | ||
| 945 | 965 | if (module.exports && | |
| 946 | 966 | !isProxy(module.exports) && | |
| 947 | 967 | ObjectGetPrototypeOf(module.exports) === ObjectPrototype && | |
@@ -1009,11 +1029,21 @@ Module._load = function(request, parent, isMain) { | |||
| 1009 | 1029 | if (cachedModule !== undefined) { | |
| 1010 | 1030 | updateChildren(parent, cachedModule, true); | |
| 1011 | 1031 | if (!cachedModule.loaded) { | |
| 1012 | - const parseCachedModule = cjsSourceCache.get(cachedModule); | ||
| 1013 | - if (!parseCachedModule || parseCachedModule.loaded) { | ||
| 1032 | + // If it's not cached by the ESM loader, the loading request | ||
| 1033 | + // comes from required CJS, and we can consider it a circular | ||
| 1034 | + // dependency when it's cached. | ||
| 1035 | + if (!cachedModule[kIsCachedByESMLoader]) { | ||
| 1014 | 1036 | return getExportsForCircularRequire(cachedModule); | |
| 1015 | 1037 | } | |
| 1016 | - parseCachedModule.loaded = true; | ||
| 1038 | + // If it's cached by the ESM loader as a way to indirectly pass | ||
| 1039 | + // the module in to avoid creating it twice, the loading request | ||
| 1040 | + // come from imported CJS. In that case use the importedCJSCache | ||
| 1041 | + // to determine if it's loading or not. | ||
| 1042 | + const importedCJSMetadata = importedCJSCache.get(cachedModule); | ||
| 1043 | + if (importedCJSMetadata.loading) { | ||
| 1044 | + return getExportsForCircularRequire(cachedModule); | ||
| 1045 | + } | ||
| 1046 | + importedCJSMetadata.loading = true; | ||
| 1017 | 1047 | } else { | |
| 1018 | 1048 | return cachedModule.exports; | |
| 1019 | 1049 | } | |
@@ -1027,18 +1057,21 @@ Module._load = function(request, parent, isMain) { | |||
| 1027 | 1057 | // Don't call updateChildren(), Module constructor already does. | |
| 1028 | 1058 | const module = cachedModule || new Module(filename, parent); | |
| 1029 | 1059 | ||
| 1030 | - if (isMain) { | ||
| 1031 | - setOwnProperty(process, 'mainModule', module); | ||
| 1032 | - setOwnProperty(module.require, 'main', process.mainModule); | ||
| 1033 | - module.id = '.'; | ||
| 1034 | - module[kIsMainSymbol] = true; | ||
| 1035 | - } else { | ||
| 1036 | - module[kIsMainSymbol] = false; | ||
| 1037 | - } | ||
| 1060 | + if (!cachedModule) { | ||
| 1061 | + if (isMain) { | ||
| 1062 | + setOwnProperty(process, 'mainModule', module); | ||
| 1063 | + setOwnProperty(module.require, 'main', process.mainModule); | ||
| 1064 | + module.id = '.'; | ||
| 1065 | + module[kIsMainSymbol] = true; | ||
| 1066 | + } else { | ||
| 1067 | + module[kIsMainSymbol] = false; | ||
| 1068 | + } | ||
| 1038 | 1069 | ||
| 1039 | - reportModuleToWatchMode(filename); | ||
| 1070 | + reportModuleToWatchMode(filename); | ||
| 1071 | + Module._cache[filename] = module; | ||
| 1072 | + module[kIsCachedByESMLoader] = false; | ||
| 1073 | + } | ||
| 1040 | 1074 | ||
| 1041 | - Module._cache[filename] = module; | ||
| 1042 | 1075 | if (parent !== undefined) { | |
| 1043 | 1076 | relativeResolveCache[relResolveCacheIdentifier] = filename; | |
| 1044 | 1077 | } | |
@@ -1280,7 +1313,7 @@ function loadESMFromCJS(mod, filename) { | |||
| 1280 | 1313 | const isMain = mod[kIsMainSymbol]; | |
| 1281 | 1314 | // TODO(joyeecheung): we may want to invent optional special handling for default exports here. | |
| 1282 | 1315 | // For now, it's good enough to be identical to what `import()` returns. | |
| 1283 | - mod.exports = cascadedLoader.importSyncForRequire(filename, source, isMain); | ||
| 1316 | + mod.exports = cascadedLoader.importSyncForRequire(mod, filename, source, isMain, moduleParentCache.get(mod)); | ||
| 1284 | 1317 | } | |
| 1285 | 1318 | ||
| 1286 | 1319 | /** | |
@@ -1366,7 +1399,7 @@ Module.prototype._compile = function(content, filename, loadAsESM = false) { | |||
| 1366 | 1399 | // Only modules being require()'d really need to avoid TLA. | |
| 1367 | 1400 | if (loadAsESM) { | |
| 1368 | 1401 | // Pass the source into the .mjs extension handler indirectly through the cache. | |
| 1369 | - cjsSourceCache.set(this, { source: content }); | ||
| 1402 | + requiredESMSourceCache.set(this, content); | ||
| 1370 | 1403 | loadESMFromCJS(this, filename); | |
| 1371 | 1404 | return; | |
| 1372 | 1405 | } | |
@@ -1407,13 +1440,15 @@ Module.prototype._compile = function(content, filename, loadAsESM = false) { | |||
| 1407 | 1440 | const module = this; | |
| 1408 | 1441 | if (requireDepth === 0) { statCache = new SafeMap(); } | |
| 1409 | 1442 | setHasStartedUserCJSExecution(); | |
| 1443 | + this[kIsExecuting] = true; | ||
| 1410 | 1444 | if (inspectorWrapper) { | |
| 1411 | 1445 | result = inspectorWrapper(compiledWrapper, thisValue, exports, | |
| 1412 | 1446 | require, module, filename, dirname); | |
| 1413 | 1447 | } else { | |
| 1414 | 1448 | result = ReflectApply(compiledWrapper, thisValue, | |
| 1415 | 1449 | [exports, require, module, filename, dirname]); | |
| 1416 | 1450 | } | |
| 1451 | + this[kIsExecuting] = false; | ||
| 1417 | 1452 | if (requireDepth === 0) { statCache = null; } | |
| 1418 | 1453 | return result; | |
| 1419 | 1454 | }; | |
@@ -1425,15 +1460,15 @@ Module.prototype._compile = function(content, filename, loadAsESM = false) { | |||
| 1425 | 1460 | * @returns {string} | |
| 1426 | 1461 | */ | |
| 1427 | 1462 | function getMaybeCachedSource(mod, filename) { | |
| 1428 | - const cached = cjsSourceCache.get(mod); | ||
| 1463 | + const cached = importedCJSCache.get(mod); | ||
| 1429 | 1464 | let content; | |
| 1430 | 1465 | if (cached?.source) { | |
| 1431 | 1466 | content = cached.source; | |
| 1432 | 1467 | cached.source = undefined; | |
| 1433 | 1468 | } else { | |
| 1434 | 1469 | // TODO(joyeecheung): we can read a buffer instead to speed up | |
| 1435 | 1470 | // compilation. | |
| 1436 | - content = fs.readFileSync(filename, 'utf8'); | ||
| 1471 | + content = requiredESMSourceCache.get(mod) ?? fs.readFileSync(filename, 'utf8'); | ||
| 1437 | 1472 | } | |
| 1438 | 1473 | return content; | |
| 1439 | 1474 | } | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -152,6 +152,11 @@ async function defaultLoad(url, context = kEmptyObject) { | |||
| 152 | 152 | ||
| 153 | 153 | validateAttributes(url, format, importAttributes); | |
| 154 | 154 | ||
| 155 | + // Use the synchronous commonjs translator which can deal with cycles. | ||
| 156 | + if (format === 'commonjs' && getOptionValue('--experimental-require-module')) { | ||
| 157 | + format = 'commonjs-sync'; | ||
| 158 | + } | ||
| 159 | + | ||
| 155 | 160 | return { | |
| 156 | 161 | __proto__: null, | |
| 157 | 162 | format, | |
@@ -201,6 +206,11 @@ function defaultLoadSync(url, context = kEmptyObject) { | |||
| 201 | 206 | ||
| 202 | 207 | validateAttributes(url, format, importAttributes); | |
| 203 | 208 | ||
| 209 | + // Use the synchronous commonjs translator which can deal with cycles. | ||
| 210 | + if (format === 'commonjs' && getOptionValue('--experimental-require-module')) { | ||
| 211 | + format = 'commonjs-sync'; | ||
| 212 | + } | ||
| 213 | + | ||
| 204 | 214 | return { | |
| 205 | 215 | __proto__: null, | |
| 206 | 216 | format, | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -1,7 +1,10 @@ | |||
| 1 | 1 | 'use strict'; | |
| 2 | 2 | ||
| 3 | 3 | // This is needed to avoid cycles in esm/resolve <-> cjs/loader | |
| 4 | - require('internal/modules/cjs/loader'); | ||
| 4 | + const { | ||
| 5 | + kIsExecuting, | ||
| 6 | + kRequiredModuleSymbol, | ||
| 7 | + } = require('internal/modules/cjs/loader'); | ||
| 5 | 8 | ||
| 6 | 9 | const { | |
| 7 | 10 | ArrayPrototypeJoin, | |
@@ -15,8 +18,11 @@ const { | |||
| 15 | 18 | hardenRegExp, | |
| 16 | 19 | } = primordials; | |
| 17 | 20 | ||
| 21 | + const { imported_cjs_symbol } = internalBinding('symbols'); | ||
| 22 | + | ||
| 18 | 23 | const assert = require('internal/assert'); | |
| 19 | 24 | const { | |
| 25 | + ERR_REQUIRE_CYCLE_MODULE, | ||
| 20 | 26 | ERR_REQUIRE_ESM, | |
| 21 | 27 | ERR_NETWORK_IMPORT_DISALLOWED, | |
| 22 | 28 | ERR_UNKNOWN_MODULE_FORMAT, | |
@@ -30,7 +36,10 @@ const { | |||
| 30 | 36 | } = require('internal/modules/esm/utils'); | |
| 31 | 37 | const { kImplicitAssertType } = require('internal/modules/esm/assert'); | |
| 32 | 38 | const { canParse } = internalBinding('url'); | |
| 33 | - const { ModuleWrap } = internalBinding('module_wrap'); | ||
| 39 | + const { ModuleWrap, kEvaluating, kEvaluated } = internalBinding('module_wrap'); | ||
| 40 | + const { | ||
| 41 | + urlToFilename, | ||
| 42 | + } = require('internal/modules/helpers'); | ||
| 34 | 43 | let defaultResolve, defaultLoad, defaultLoadSync, importMetaInitializer; | |
| 35 | 44 | ||
| 36 | 45 | /** | |
@@ -248,17 +257,36 @@ class ModuleLoader { | |||
| 248 | 257 | /** | |
| 249 | 258 | * This constructs (creates, instantiates and evaluates) a module graph that | |
| 250 | 259 | * is require()'d. | |
| 260 | + * @param {import('../cjs/loader.js').Module} mod CJS module wrapper of the ESM. | ||
| 251 | 261 | * @param {string} filename Resolved filename of the module being require()'d | |
| 252 | 262 | * @param {string} source Source code. TODO(joyeecheung): pass the raw buffer. | |
| 253 | 263 | * @param {string} isMain Whether this module is a main module. | |
| 254 | - * @returns {ModuleNamespaceObject} | ||
| 264 | + * @param {import('../cjs/loader.js').Module|undefined} parent Parent module, if any. | ||
| 265 | + * @returns {{ModuleWrap}} | ||
| 255 | 266 | */ | |
| 256 | - importSyncForRequire(filename, source, isMain) { | ||
| 267 | + importSyncForRequire(mod, filename, source, isMain, parent) { | ||
| 257 | 268 | const url = pathToFileURL(filename).href; | |
| 258 | 269 | let job = this.loadCache.get(url, kImplicitAssertType); | |
| 259 | - // This module is already loaded, check whether it's synchronous and return the | ||
| 260 | - // namespace. | ||
| 270 | + // This module job is already created: | ||
| 271 | + // 1. If it was loaded by `require()` before, at this point the instantiation | ||
| 272 | + // is already completed and we can check the whether it is in a cycle | ||
| 273 | + // (in that case the module status is kEvaluaing), and whether the | ||
| 274 | + // required graph is synchronous. | ||
| 275 | + // 2. If it was loaded by `import` before, only allow it if it's already evaluated | ||
| 276 | + // to forbid cycles. | ||
| 277 | + // TODO(joyeecheung): ensure that imported synchronous graphs are evaluated | ||
| 278 | + // synchronously so that any previously imported synchronous graph is already | ||
| 279 | + // evaluated at this point. | ||
| 261 | 280 | if (job !== undefined) { | |
| 281 | + mod[kRequiredModuleSymbol] = job.module; | ||
| 282 | + if (job.module.getStatus() !== kEvaluated) { | ||
| 283 | + const parentFilename = urlToFilename(parent?.filename); | ||
| 284 | + let message = `Cannot require() ES Module ${filename} in a cycle.`; | ||
| 285 | + if (parentFilename) { | ||
| 286 | + message += ` (from ${parentFilename})`; | ||
| 287 | + } | ||
| 288 | + throw new ERR_REQUIRE_CYCLE_MODULE(message); | ||
| 289 | + } | ||
| 262 | 290 | return job.module.getNamespaceSync(); | |
| 263 | 291 | } | |
| 264 | 292 | // TODO(joyeecheung): refactor this so that we pre-parse in C++ and hit the | |
@@ -270,6 +298,7 @@ class ModuleLoader { | |||
| 270 | 298 | const { ModuleJobSync } = require('internal/modules/esm/module_job'); | |
| 271 | 299 | job = new ModuleJobSync(this, url, kEmptyObject, wrap, isMain, inspectBrk); | |
| 272 | 300 | this.loadCache.set(url, kImplicitAssertType, job); | |
| 301 | + mod[kRequiredModuleSymbol] = job.module; | ||
| 273 | 302 | return job.runSync().namespace; | |
| 274 | 303 | } | |
| 275 | 304 | ||
@@ -304,19 +333,29 @@ class ModuleLoader { | |||
| 304 | 333 | const resolvedImportAttributes = resolveResult.importAttributes ?? importAttributes; | |
| 305 | 334 | let job = this.loadCache.get(url, resolvedImportAttributes.type); | |
| 306 | 335 | if (job !== undefined) { | |
| 307 | - // This module is previously imported before. We will return the module now and check | ||
| 308 | - // asynchronicity of the entire graph later, after the graph is instantiated. | ||
| 336 | + // This module is being evaluated, which means it's imported in a previous link | ||
| 337 | + // in a cycle. | ||
| 338 | + if (job.module.getStatus() === kEvaluating) { | ||
| 339 | + const parentFilename = urlToFilename(parentURL); | ||
| 340 | + let message = `Cannot import Module ${specifier} in a cycle.`; | ||
| 341 | + if (parentFilename) { | ||
| 342 | + message += ` (from ${parentFilename})`; | ||
| 343 | + } | ||
| 344 | + throw new ERR_REQUIRE_CYCLE_MODULE(message); | ||
| 345 | + } | ||
| 346 | + // Othersie the module could be imported before but the evaluation may be already | ||
| 347 | + // completed (e.g. the require call is lazy) so it's okay. We will return the | ||
| 348 | + // module now and check asynchronicity of the entire graph later, after the | ||
| 349 | + // graph is instantiated. | ||
| 309 | 350 | return job.module; | |
| 310 | 351 | } | |
| 311 | 352 | ||
| 312 | 353 | defaultLoadSync ??= require('internal/modules/esm/load').defaultLoadSync; | |
| 313 | 354 | const loadResult = defaultLoadSync(url, { format, importAttributes }); | |
| 314 | 355 | const { responseURL, source } = loadResult; | |
| 315 | - let { format: finalFormat } = loadResult; | ||
| 356 | + const { format: finalFormat } = loadResult; | ||
| 316 | 357 | this.validateLoadResult(url, finalFormat); | |
| 317 | - if (finalFormat === 'commonjs') { | ||
| 318 | - finalFormat = 'commonjs-sync'; | ||
| 319 | - } else if (finalFormat === 'wasm') { | ||
| 358 | + if (finalFormat === 'wasm') { | ||
| 320 | 359 | assert.fail('WASM is currently unsupported by require(esm)'); | |
| 321 | 360 | } | |
| 322 | 361 | ||
@@ -333,6 +372,20 @@ class ModuleLoader { | |||
| 333 | 372 | process.send({ 'watch:import': [url] }); | |
| 334 | 373 | } | |
| 335 | 374 | ||
| 375 | + const cjsModule = wrap[imported_cjs_symbol]; | ||
| 376 | + if (cjsModule) { | ||
| 377 | + assert(finalFormat === 'commonjs-sync'); | ||
| 378 | + // Check if the ESM initiating import CJS is being required by the same CJS module. | ||
| 379 | + if (cjsModule && cjsModule[kIsExecuting]) { | ||
| 380 | + const parentFilename = urlToFilename(parentURL); | ||
| 381 | + let message = `Cannot import CommonJS Module ${specifier} in a cycle.`; | ||
| 382 | + if (parentFilename) { | ||
| 383 | + message += ` (from ${parentFilename})`; | ||
| 384 | + } | ||
| 385 | + throw new ERR_REQUIRE_CYCLE_MODULE(message); | ||
| 386 | + } | ||
| 387 | + } | ||
| 388 | + | ||
| 336 | 389 | const inspectBrk = (isMain && getOptionValue('--inspect-brk')); | |
| 337 | 390 | const { ModuleJobSync } = require('internal/modules/esm/module_job'); | |
| 338 | 391 | job = new ModuleJobSync(this, url, importAttributes, wrap, isMain, inspectBrk); | |
| Back | FazBrowse Home | New Git URL |
0 commit comments