Node+TypeScript+oclif项目引入d3.js报ERR_REQUIRE_ESM如何解决
问题根因
- d3 v7及以上版本是纯ESM包,完全不支持CommonJS规范的
require()语法导入 - 你当前用的oclif默认TS配置把编译模块规范设为
commonjs,TS编译时会把所有ESM风格的静态import、甚至你手写的动态import()都降级转成require()调用,这就是为什么你照着报错提示改了动态导入还是失败——编译后的代码根本没保留动态import语法,跑的时候还是走require加载d3,必然报ERR_REQUIRE_ESM。
可落地解决方案
方案1:保留原有CommonJS配置,最小改动修复
适合不想大改现有项目结构的场景,只需要改两处:
- 调整
tsconfig.json,新增配置项开启ESM兼容:
{ "compilerOptions": { "declaration": true, "importHelpers": true, "module": "commonjs", "moduleResolution": "node", "outDir": "dist", "rootDir": "src", "strict": true, "target": "es2019", "esModuleInterop": true }, "include": ["src/**/*"] }
- 删掉文件顶部对d3的静态导入,把所有用到d3的逻辑放到async函数中,在函数内部用动态import加载d3:
// 删掉顶部的 import * as d3 from 'd3' async function buildGraphTree() { // 注意这里要取default属性,d3是默认导出 const d3 = (await import('d3')).default; // 后续正常写d3的业务逻辑即可,select、比例尺等方法都能正常调用 }
- 重新执行构建命令
npm run build或者yarn build,再运行程序就不会报错。如果编译后发现dist里对应代码还是require,把tsconfig里的target改成es2020再重新编译就行。
方案2:全项目切换为ESM规范(长期维护推荐)
如果后续还要用其他纯ESM依赖,不想每次都写动态导入,可以直接把整个项目切到ESM,一劳永逸:
- 在
package.json顶层加配置:"type": "module" - 替换
tsconfig.json里的模块相关配置:
{ "compilerOptions": { "declaration": true, "importHelpers": true, "module": "NodeNext", "moduleResolution": "NodeNext", "outDir": "dist", "rootDir": "src", "strict": true, "target": "es2020", "esModuleInterop": true }, "include": ["src/**/*"] }
- 把项目里所有相对路径的导入补全
.js后缀,比如原来的import { parseTree } from './tree-util'要改成import { parseTree } from './tree-util.js' - 调整oclif的启动脚本、eslint规则适配ESM规范,这个方案改动量稍大,但后续不会再遇到CommonJS和ESM的兼容问题。
注意事项
- 别为了兼容去降级d3到v6及更早版本,旧版本存在未修复的安全漏洞,也不会再更新功能
- 动态导入d3的时候一定要取
.default,不然拿到的是整个模块命名空间对象,调用d3的方法会报“不是函数”的错 - 改完配置一定要检查dist目录下的编译产物,确认加载d3的代码是原生
import()语法而不是require(),不然配置改了也不生效。
内容的提问来源于stack exchange,提问作者DrKabum
相关产品推荐
相关产品推荐

