这份清单覆盖项目配置、模块导入导出、路径解析、第三方包兼容四大核心场景,为避开跨模块规范的常见问题。
一、 项目基础配置避坑
- 明确模块类型,避免自动切换混乱
-
- Node.js 默认
.js文件为 CJS,若要全局启用 ESM,必须在项目根目录package.json中配置"type": "module"。 - 若只想单个文件用 ESM,可将后缀改为
.mjs;单个文件用 CJS,后缀改为.cjs(不受package.json的type影响)。 - ❌ 错误:项目根目录没配
"type": "module",却在.js文件里写import/export→ 直接报错。
- Node.js 默认
package.json字段优先级要分清
json
{
"main": "./dist/index.cjs",
"exports": {
".": {
"require": "./dist/index.cjs",
"import": "./dist/index.mjs"
}
}
}
-
- ESM 优先读取
exports字段,CJS 优先读取main字段。 - 若要同时兼容 CJS 和 ESM,需在
exports中分别声明:
- ESM 优先读取
二、 模块导入导出避坑
- CJS 不能直接
require()ESM 模块
js
// CJS 文件中加载 ESM 模块
import('./esm-file.js').then((module) => {
console.log(module.default); // 拿到 ESM 的默认导出
});
-
- ❌ 错误:
const esmModule = require('./esm-file.js')(该文件是 ESM 格式)→ 报ERR_REQUIRE_ESM。 - ✅ 解决:用
import()动态导入(返回 Promise):
- ❌ 错误:
- ESM 加载 CJS 模块的注意事项
js
// ESM 文件中加载 CJS 模块
import cjsModule from './cjs-file.js';
// 等价于 CJS 的 const cjsModule = require('./cjs-file.js')
js
// 方案1:解构默认导出
import cjsModule from './cjs-file.js';
const { foo } = cjsModule;
// 方案2:CJS 模块兼容具名导出写法
module.exports = { foo: 'bar' }; // ESM 可解构
-
- ✅ ESM 可以直接用
import加载 CJS 模块,CJS 的module.exports会被视为 ESM 的默认导出。 - ❌ 坑:CJS 模块没有 ESM 的具名导出,不能写
import { foo } from './cjs-file.js'→ 导入的foo为undefined。 - ✅ 解决:要么解构默认导出,要么让 CJS 模块兼容导出:
- ✅ ESM 可以直接用
__dirname/__filename在 ESM 中不可用
js
// ESM 中替代 __dirname/__filename
import { fileURLToPath } from 'url';
import { dirname } from 'path';
const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename);
-
- CJS 中的
__dirname(当前文件目录路径)、__filename(当前文件路径)在 ESM 中被移除。 - ❌ 错误:在 ESM 文件中直接用
__dirname→ 报ReferenceError。 - ✅ 解决:用
import.meta.url手动实现:
- CJS 中的
三、 路径解析避坑
- ESM 必须写全文件扩展名
-
- 这是最容易踩的坑!CJS 支持省略
.js/.json,ESM 不行。 - ❌ 错误:
import helper from './utils/helper'→ 报ERR_MODULE_NOT_FOUND。 - ✅ 正确:
import helper from './utils/helper.js'。
- 这是最容易踩的坑!CJS 支持省略
- ESM 加载目录必须配置
exports
json
{
"exports": { ".": "./index.js" } // 映射目录根到 index.js
}
-
- CJS 加载目录时,会自动找
index.js;ESM 不会,必须在目录的package.json中配置exports。 - 示例:
utils目录下加package.json - 之后才能在 ESM 中这样写:
import utils from './utils'。
- CJS 加载目录时,会自动找
- 第三方包的路径映射优先看
exports
-
- 若第三方包的
package.json有exports字段,ESM 会严格按其规则加载,不能"越级"导入子文件。 - ❌ 错误:包
my-pkg的exports只暴露了根,却写import foo from 'my-pkg/src/foo.js'→ 报错。 - ✅ 解决:要么让包在
exports中添加该路径,要么用 CJS 加载(CJS 不受exports限制)。
- 若第三方包的
四、 命令行运行与工具链避坑
- 运行 ESM 文件的参数注意
-
- 若没配置
package.json的"type": "module",运行.mjs文件无需额外参数;运行.js文件需加--experimental-modules(低版本 Node),高版本 Node(v14.3+)无需。 - 命令示例:
node esm-file.js(已配type: module)、node esm-file.mjs(无需配置)。
- 若没配置
- TypeScript/打包工具的兼容设置
json
{
"compilerOptions": {
"module": "ESNext",
"moduleResolution": "NodeNext", // 匹配 Node.js 的 ESM 解析规则
"outDir": "./dist",
"esModuleInterop": true // 兼容 CJS 模块的默认导出
}
}
-
- 用
tsc编译时,若目标是 ESM,需在tsconfig.json中设置: - 用
webpack/rollup时,需明确配置output.format为cjs或esm,避免打包后模块类型混乱。
- 用
五、CJS & ESM 混合项目常见错误排查对照表
这份对照表覆盖 Demo 运行中最容易出现的 8 类报错,包含 错误现象、根本原因、解决方案,帮你快速定位和修复问题。
| 错误类型 | 错误提示示例 | 根本原因 | 解决方案 |
|---|---|---|---|
| ESM 扩展名缺失 | Error [ERR_MODULE_NOT_FOUND]: Cannot find module './esm-helper' imported from ... |
ESM 要求必须写全文件扩展名(.js/.mjs),不能像 CJS 那样省略 |
在 import 语句中补全扩展名,例如: import helper from './esm-helper.js' |
| CJS 直接加载 ESM | Error [ERR_REQUIRE_ESM]: require() of ES Module ... not supported. |
CJS 的 require() 不支持直接加载 ESM 模块,只能加载 CJS 模块 |
改用 ESM 的动态 import() 函数(返回 Promise): import('./esm-module.js').then(mod => { ... }) |
| ESM 加载目录无 exports | Error [ERR_MODULE_NOT_FOUND]: Cannot find module './utils' imported from ... |
ESM 加载目录时,不会默认找 index.js,必须配置目录的 package.json exports 字段 |
在 utils 目录下新建 package.json,添加: { "exports": { ".": "./index.js" } } |
| __dirname 未定义 | ReferenceError: __dirname is not defined in ES module scope |
ESM 中移除了 __dirname/__filename 全局变量 |
用 import.meta.url 手动实现,参考 esm-dirname.js 的代码 |
| type: module 冲突 | SyntaxError: Cannot use import statement outside a module |
根目录 package.json 没配置 "type": "module",.js 文件被当作 CJS 解析 |
在根 package.json 中添加: { "type": "module" },或把 ESM 文件后缀改为 .mjs |
| 第三方包 exports 限制 | Error [ERR_PACKAGE_PATH_NOT_EXPORTED]: Package subpath './src/xxx' is not defined by "exports" |
第三方包的 package.json exports 字段未暴露该子路径,ESM 严格遵循映射规则 |
1. 改用包暴露的合法路径; 2. 若为自己开发的包,在 exports 中添加该路径映射 |
| CJS 解构 ESM 具名导出失败 | const { foo } = require('./esm-module.js') → foo 为 undefined |
ESM 的具名导出不能被 CJS 直接解构,require() 只能拿到 ESM 的默认导出 |
先获取默认导出再解构: const mod = require('./esm-module.js'); const { foo } = mod.default; |
| 文件路径大小写问题 | Error [ERR_MODULE_NOT_FOUND]: Cannot find module './Esm-Helper.js' |
Node.js 路径解析区分大小写(尤其是 Linux/macOS 系统) | 保证 import 路径的大小写和实际文件名完全一致 |
六、额外通用排查技巧
- 检查 Node.js 版本:确保 Node.js ≥ v14.3.0,低版本对 ESM 支持不完善。
- 清理缓存 :运行
node --clear-module-cache src/xxx.js清除模块缓存,解决缓存导致的解析异常。 - 绝对路径测试 :若相对路径报错,改用绝对路径(结合
__dirname)测试,排除相对路径层级错误。