DEBUG.WIKI
V.PS:让项目靠近用户,亚太、欧洲、北美 KVM VPS
IPPeak:从目标地区访问网页,住宅代理、SEO 监测、广告验证
ZgoCloud:多地区部署,按需选节点;日本、美国、香港、德国

Node.js 的 require 报 ERR_REQUIRE_ASYNC_MODULE,检查依赖中的顶层 await

升级 Node 后插件启动报 ERR_REQUIRE_ASYNC_MODULE,核对顶层 await、旧加载器捕获码和异步调用链。

升级 Node.js 后,原本加载插件的代码在启动阶段报 ERR_REQUIRE_ASYNC_MODULE,还没进入插件的业务逻辑。错误通常出在同步 require() 遇到了带顶层 await 的 ESM:不仅当前文件要检查,它导入的依赖也可能让整条模块图变成异步。

这个错误值得单独处理。Node.js 22.12.0、20.19.0 开始默认启用 require(esm),但同步加载仍要求整个依赖图没有顶层 await。旧加载器若只捕获 ERR_REQUIRE_ESM,升级后收到新的错误码,就可能跳过原来的异步导入分支。

先找到实际调用 require 的那一层

记录启动进程的 Node 版本、错误中的模块路径和完整调用栈。编辑器采用的 Node、交互式终端里的 Node,与服务或 CI 真正使用的版本可能不同。报错发生在模块加载阶段,改网络超时或业务重试没有作用。

例如 osls 3.49.0 的插件加载辅助函数,以及 Serverless 3.40.0 对应的辅助函数,只在错误码为 ERR_REQUIRE_ESM 时回退到 import()。如果栈落在这种加载器内,先核对已安装版本的实现;应用入口已经写成 import,也不代表框架内部没有同步加载插件。

顶层 await 不一定就在报错路径对应文件中。继续检查它的静态导入和实际构建产物。函数内部的普通 await 与模块求值时的顶层 await 不同,不能把所有异步函数都当成原因。

Node.js 22.14.0 提供 --experimental-print-required-tla 帮助定位这类顶层等待,但这一版本的诊断会先求值模块,再寻找等待位置。模块初始化可能连接外部服务或写入数据,应在隔离环境中使用,不能当作纯静态检查。

自己维护的加载器,需要把调用链也改为异步

确认该模块确实需要异步初始化后,可用原生动态 import() 加载它。下面是 CommonJS 加载器片段,specifier 应替换为原来已经确认的包名或合法模块 URL:

// plugin-loader.cjs
async function loadPlugin(specifier) {
  const namespace = await import(specifier);
  return namespace;
}

module.exports = { loadPlugin };

调用方必须等待返回值,不能继续把它当作同步插件对象。import() 返回模块命名空间,默认导出在 namespace.default;原来取命名导出还是默认导出,应按插件实际接口保留,不能一律加 .default。

如果现有框架的插件接口只接受同步结果,应先采用支持该依赖加载方式的框架版本,或按框架的异步初始化入口调整。单独把一行 require 改成 import(),却不改上游调用,容易变成新的 Promise 使用错误。还要看最终 JavaScript 是否保留原生动态导入,避免构建工具又把它转换回同步 require()。

模块初始化本来可以同步完成时,也可以评估去掉顶层等待、把外部资源初始化移到明确的异步函数里。这会改变初始化时序,必须检查谁负责等待、谁处理失败,以及业务是否会在资源准备前启动。

暂时关闭新加载能力,只适用于确认过的版本组合

在 Node.js 20.19.0、22.14.0 这类环境中,--no-experimental-require-module 可以临时关闭 require(esm),让某些旧加载器再次收到 ERR_REQUIRE_ESM 并进入已有回退分支。它不会让同步 require() 获得等待顶层 await 的能力,也不保证所有框架都有回退代码。

这项开关应限定在受影响的启动进程中,并保留已有启动参数。较新 Node 的开关名称还有变化,不能把历史参数复制成所有版本的全局 NODE_OPTIONS。若暂用旧版本组合,也要保留依赖锁文件和运行版本记录,避免下次重建又换回另一套行为。

验收时从原来的服务或 CI 入口启动,确认模块加载和初始化都完成,再调用依赖该插件的功能。只有错误码消失还不够:异步初始化失败被吞掉、插件对象变成 Promise,或导出形状改变,都可能让启动后的行为仍然异常。撤回加载器修改时,应一起恢复与它配套的调用方和运行参数。