Node.js 循环 import 为什么会在启动时抛错
两个模块相互 import,启动时出现 Cannot access before initialization。先找顶层求值时谁读了尚未初始化的绑定。循环依赖可以加载,某条提前读取的执行路径会触发异常。
1. 生成一对失败模块和一组修复模块
保存为 esm_cycle_lab.mjs,运行 node esm_cycle_lab.mjs。脚本使用临时目录,每组在独立 Node 进程运行。
import assert from 'node:assert/strict';
import { execFileSync } from 'node:child_process';
import { mkdtempSync, rmSync, writeFileSync } from 'node:fs';
import { tmpdir } from 'node:os';
import { join } from 'node:path';
const dir = mkdtempSync(join(tmpdir(), 'esm-cycle-'));
function write(name, content) {
writeFileSync(join(dir, name), content);
}
function run(name) {
return execFileSync(process.execPath, [join(dir, name)], {
encoding: 'utf8', stdio: ['ignore', 'pipe', 'pipe'],
}).trim();
}
try {
write('entry.mjs', "import { label } from './view.mjs'; export const env = 'prod'; console.log(label);");
write('view.mjs', "import { env } from './entry.mjs'; export const label = 'env=' + env;");
assert.throws(() => run('entry.mjs'), error =>
error.status !== 0 && /before initialization/.test(error.stderr));
console.log('cyclic startup: ReferenceError before initialization');
write('settings.mjs', "export const env = 'prod';");
write('view.mjs', "import { env } from './settings.mjs'; export const label = 'env=' + env;");
write('entry.mjs', "import { label } from './view.mjs'; console.log(label);");
assert.equal(run('entry.mjs'), 'env=prod');
console.log('acyclic startup: env=prod');
} finally {
rmSync(dir, { recursive: true, force: true });
}
本机实测输出如下。
cyclic startup: ReferenceError before initialization
acyclic startup: env=prod
2. 顺着入口查顶层求值
实验以 entry 为入口。view 在顶层拼接字符串时需要读取 env,此时 entry 的 const 还未初始化。重新组织后,settings 不依赖业务模块,求值顺序为 settings、view、entry。
常见触发点包括模块顶层的实例构造、注册表初始化和静态字段读取。把共享常量移至叶子模块,或由入口构造依赖并通过参数传给业务函数。单纯把读取移进函数只能改变读取时间,仍需要确认调用时相关模块已完成初始化。
| 验收点 | 做法 |
|---|---|
| 启动路径 | 用部署命令直接启动实际入口 |
| 多入口项目 | 分别从服务、CLI 和 worker 入口测试 |
| 构建产物 | 对实际部署文件复测模块求值 |
小结
异常指向的是访问时机。先找顶层读取位置,再沿依赖关系将共享配置与应用组装拆开。
数据来源与检索时间
检索时间 2026-11-13。
| 来源 | URL | 对应内容 |
|---|---|---|
| ECMAScript Modules | https://tc39.es/ecma262/multipage/ecmascript-language-scripts-and-modules.html#sec-cyclic-module-records | 循环模块记录与求值 |
| Node.js v24.19.0 ESM | https://nodejs.org/download/release/v24.19.0/docs/api/esm.html | 使用 .mjs 启动 ESM |
核验说明
- Node.js v24.19.0 分别验证循环依赖报错和移出配置后的启动。未纳入打包器或 CommonJS。