症状
错误文本自带判决书和处置指引:ERR_REQUIRE_ESM 告诉你 require() 加载的包只发布 ES Module,并直接提示改用 import()。检查产出该策略的元数据:包的 type 为 module、exports 映射不含 require 条件,说明作者是有意关掉 require() 这扇门的。触发器几乎总是版本推进:锁文件升到纯 ESM 的主版本,CommonJS 文件里的 require 调用在结构上不再被支持。源码零改动、装完依赖就挂,是这类包(chalk、node-fetch、execa 家族)v3 迁移期的经典形态。
诊断思路
先读包的 type 与 exports 策略确认“设计如此”;再翻 package-lock.json 的 git 历史找到跨主版本推进的那次变更,解释为什么曾经能跑的代码停止加载;最后确认导入方文件是 require 还是 import 语法,这决定可选修复:ESM 迁移、动态 import,或留在兼容 CJS 的版本上。还要注意间接依赖若停在旧主版本不受影响,只有直接导入拉到新主版本才会踩雷;工具用 require 加载配置文件、配置里再导入纯 ESM 助手,是隐蔽变体。
根因
该依赖现在只发布 ES Module。CommonJS 的 require() 是同步加载并返回命名空间对象,与 ESM 的链接语义不兼容,Node 以 ERR_REQUIRE_ESM 拒绝加载,而不是静默返回一个残缺模块。
修复与验证
按文件选择迁移形态:保持 CJS 的文件用 const mod = await import(‘pkg’) 惰性加载;彻底迁移就把 package.json 设为 type: module 并改写 import 语法;必须拖延时显式钉住最后一批双模式版本(如 chalk ^4.1.2)并挂上带日期的迁移任务。验证时启动所有此前失败的入口(服务器、任务、脚本)确认干净加载;完整跑一遍测试套件,重点核对导出形态——纯命名导出的包要把 default x 改成解构 { x },反之亦然;最后全仓 grep 对这些包的 require() 调用,确认每处都已转换或有意钉住,让下次锁文件刷新不再复发。Node 22.12+ 原生支持 require(esm),可整体退役这类错误,但依赖它之前务必验证命名导出互操作行为。
代码示例
Error [ERR_REQUIRE_ESM]: require() of ES Module
/app/node_modules/chalk/source/index.js not supported.
Instead change the requiring code to use import()
node -p "require('./node_modules/chalk/package.json').type"
# module
async function run() {
const { default: chalk } = await import('chalk');
console.log(chalk.green('done'));
}
{ "type": "module" }
import chalk from 'chalk';
export function ok(msg) { console.log(chalk.green(msg)); }
{ "dependencies": { "chalk": "^4.1.2" } }