Skip to content

Commit 2c5561f

Browse files
joyeecheungmarco-ippolito
authored andcommitted
module: allow cycles in require() in the CJS handling in ESM loader
When --import is used, the ESM loader is used to handle even pure CJS entry points, and it can run into CJS module facades in the evaluating state when the parent CJS module is being evaluated. In this case it should be allowed, since the ESM <-> CJS cycles that are meant to be disallowed (for the time being) should already be detected before evaluation and wouldn't get here, and CJS <-> CJS cycles are fine. PR-URL: #58598 Backport-PR-URL: #59504 Fixes: #58515 Reviewed-By: Anna Henningsen <[email protected]> Reviewed-By: Ethan Arrowood <[email protected]> Refs: #52697
1 parent 94447a1 commit 2c5561f

File tree

7 files changed

+45
-4
lines changed

7 files changed

+45
-4
lines changed

lib/internal/modules/esm/module_job.js

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ const {
2525
ModuleWrap,
2626
kErrored,
2727
kEvaluated,
28+
kEvaluating,
2829
kInstantiated,
2930
kUninstantiated,
3031
} = internalBinding('module_wrap');
@@ -301,8 +302,14 @@ class ModuleJob extends ModuleJobBase {
301302
return { __proto__: null, module: this.module, namespace };
302303
}
303304
throw this.module.getError();
304-
305-
} else if (status === kEvaluated) {
305+
} else if (status === kEvaluating || status === kEvaluated) {
306+
// kEvaluating can show up when this is being used to deal with CJS <-> CJS cycles.
307+
// Allow it for now, since we only need to ban ESM <-> CJS cycles which would be
308+
// detected earlier during the linking phase, though the CJS handling in the ESM
309+
// loader won't be able to emit warnings on pending circular exports like what
310+
// the CJS loader does.
311+
// TODO(joyeecheung): remove the re-invented require() in the ESM loader and
312+
// always handle CJS using the CJS loader to eliminate the quirks.
306313
return { __proto__: null, module: this.module, namespace: this.module.getNamespaceSync() };
307314
}
308315
assert.fail(`Unexpected module status ${status}.`);

src/module_wrap.cc

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -693,11 +693,10 @@ void ModuleWrap::GetNamespaceSync(const FunctionCallbackInfo<Value>& args) {
693693
return realm->env()->ThrowError(
694694
"Cannot get namespace, module has not been instantiated");
695695
case v8::Module::Status::kInstantiated:
696+
case v8::Module::Status::kEvaluating:
696697
case v8::Module::Status::kEvaluated:
697698
case v8::Module::Status::kErrored:
698699
break;
699-
case v8::Module::Status::kEvaluating:
700-
UNREACHABLE();
701700
}
702701

703702
if (module->IsGraphAsync()) {
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
'use strict';
2+
3+
// This tests that --import preload does not break CJS entry points that contains
4+
// require cycles.
5+
6+
require('../common');
7+
const fixtures = require('../common/fixtures');
8+
const { spawnSyncAndAssert } = require('../common/child_process');
9+
10+
spawnSyncAndAssert(
11+
process.execPath,
12+
[
13+
'--import',
14+
fixtures.fileURL('import-require-cycle/preload.mjs'),
15+
fixtures.path('import-require-cycle/c.js'),
16+
],
17+
{
18+
stdout: /cycle equality true/,
19+
}
20+
);
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
module.exports.b = require('./b.js');
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
module.exports.a = require('./a.js');
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
const obj = require('./b.js');
2+
3+
console.log('cycle equality', obj.a.b === obj);
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
import * as mod from "module";
2+
3+
// This API is not available on v20.x. We are just checking that a
4+
// using a --import preload to force the ESM loader to load CJS
5+
// still handles CJS <-> CJS cycles just fine.
6+
// mod.registerHooks({
7+
// load(url, context, nextLoad) {
8+
// return nextLoad(url, context);
9+
// },
10+
// });

0 commit comments

Comments
 (0)