
前言
最近项目启动时突然遇到一个经典报错:
1 | Error [ERR_REQUIRE_ESM]: |
奇怪的是:
- 业务代码没改
- Vite配置没改
- package.json没改
唯一做过的事情只有:
1 | rm -rf node_modules |
本以为是一次简单的依赖重装,没想到最后一路追查到了:
- pnpm锁文件
- SemVer版本规则
- 间接依赖升级
- Pure ESM
- Node20兼容机制
最终定位出了真正原因。
本文记录完整排查过程。
问题现象
执行:
1 | pnpm dev |
项目启动失败:
1 | Error [ERR_REQUIRE_ESM]: |
错误信息中最重要的是:
1 | from |
说明问题并不在业务代码。
而是在第三方依赖链。
第一步:顺着错误栈找调用链
打开:
1 | node_modules/vite-plugin-svg-icons-ng/dist/index.cjs |
发现:
1 | ; |
调用链立即变得清晰:
1 | 我的项目 |
问题变成:
svg-icon-baker 到底是什么格式?
第二步:如何判断一个包是不是 Pure ESM?
很多人遇到 ERR_REQUIRE_ESM 时第一反应是:
1 | 加 type:module |
其实第一件事应该是:
1 | 打开 package.json |
直接查看目标包的元数据。
查看 svg-icon-baker
package.json:
1 | { |
说明:
1 | 默认按 ESM 解析 |
但仅凭这一项还不能完全确定。
继续查看 exports:
1 | { |
注意:
这里只有:
1 | import |
没有:
1 | require |
入口。
再看:
1 | { |
直接指向:
1 | index.mjs |
而不是:
1 | index.cjs |
至此可以确定:
1 | svg-icon-baker |
即:
1 | 纯 ESM 包 |
如何快速判断一个包是不是 Pure ESM?
以后拿到任何陌生包。
直接检查三处:
第二看 exports
如果只有:
1 | { |
或者:
1 | { |
没有:
1 | { |
大概率就是 Pure ESM。
第三看 main
如果:
1 | { |
而不是:
1 | { |
基本可以确认作者已经放弃 CommonJS 支持。
对比:什么叫 Dual Package?
再看 vite-plugin-svg-icons-ng:
1 | { |
同时存在:
1 | import |
入口。
说明:
1 | vite-plugin-svg-icons-ng |
即:
1 | 同时支持 ESM 和 CommonJS |
第三步:为什么会报 ERR_REQUIRE_ESM?
现在调用链已经完整:
1 | vite-plugin-svg-icons-ng |
问题本质:
1 | CommonJS |
在 Node20.18 及以下:
1 | require('svg-icon-baker') |
执行后:
1 | ERR_REQUIRE_ESM |
直接报错。
第四步:我一开始为什么怀疑是 pnpm 缓存?
这是这次排查最大的误区。
项目 package.json 中写的是:
1 | { |
但每次安装后都是:
1 | vite-plugin-svg-icons-ng@1.9.1 |
于是我开始怀疑:
1 | pnpm store |
是不是缓存没有清理干净。
后来发现完全不是。
pnpm Store 到底负责什么?
很多开发者容易误解。
pnpm Store:
1 | 负责保存包文件 |
例如:
1 | vite-plugin-svg-icons-ng@1.9.1 |
下载后存到:
1 | ~/.pnpm-store |
供多个项目复用。
但它不负责决定:
1 | 安装哪个版本 |
真正决定版本的是:
1 | package.json |
真正的问题:SemVer + 删除锁文件
我的 package.json:
1 | { |
很多人会误认为:
1 | 安装1.3版本 |
其实不是。
^1.3.0 的真实含义:
1 | >=1.3.0 |
也就是说:
1 | 1.3.0 ✔ |
全部合法。
为什么以前一直是1.3?
因为存在:
1 | pnpm-lock.yaml |
锁文件。
例如:
1 | vite-plugin-svg-icons-ng: |
无论仓库后来发布:
1 | 1.4 |
项目始终安装:
1 | 1.3.2 |
删除锁文件后发生了什么?
执行:
1 | rm pnpm-lock.yaml |
然后:
1 | pnpm install |
pnpm开始重新解析依赖树。
发现:
1 | ^1.3.0 |
允许安装:
1 | <2.0.0 |
范围内的所有版本。
于是自动选择:
1 | 1.9.1 |
即:
1 | 当前满足条件的最新版本 |
真正的事故链
至此所有问题全部串起来了:
1 | 删除 pnpm-lock.yaml |
Node20.19 为什么能解决?
很多文章喜欢说:
1 | Node20 已经兼容 ESM |
实际上并不准确。
真正重要的是:
1 | Node20.19+ |
之后。
Node20.18:
1 | require() |
Node20.19+:
1 | require() |
因此:
1 | require('svg-icon-baker') |
不再直接报错。
最终解决方案
方案一:
升级 Node:
1 | >=20.19.0 |
推荐。
方案二:
恢复历史 lock 文件。
方案三:
固定依赖版本:
1 | { |
避免未来版本漂移。
复盘总结
这次事故表面上是:
1 | ERR_REQUIRE_ESM |
实际上根因是:
1 | 删除 pnpm-lock.yaml |
最终触发:
1 | Pure ESM |
兼容问题。
最大的收获不是学会了升级 Node。
而是掌握了一套排查思路:
看到 ERR_REQUIRE_ESM 时:
第一步看错误栈。
第二步找是谁在 require。
第三步打开目标包 package.json。
第四步判断它是不是 Pure ESM。
第五步检查依赖树是否发生漂移。
很多时候问题根本不在业务代码,而是在依赖链。