KernelRanch
返回文章列表

Node.js 循环 import 为什么会在启动时抛错

2026-11-13 #nodejs#esm#architecture

两个模块相互 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 Moduleshttps://tc39.es/ecma262/multipage/ecmascript-language-scripts-and-modules.html#sec-cyclic-module-records循环模块记录与求值
Node.js v24.19.0 ESMhttps://nodejs.org/download/release/v24.19.0/docs/api/esm.html使用 .mjs 启动 ESM

核验说明

  • Node.js v24.19.0 分别验证循环依赖报错和移出配置后的启动。未纳入打包器或 CommonJS。