Node.js 模块加载 - 4 - CJS 和 ESM 互操作避坑清单

这份清单覆盖项目配置、模块导入导出、路径解析、第三方包兼容四大核心场景,为避开跨模块规范的常见问题。

一、 项目基础配置避坑

  1. 明确模块类型,避免自动切换混乱
    • Node.js 默认 .js 文件为 CJS,若要全局启用 ESM,必须在项目根目录 package.json 中配置 "type": "module"
    • 若只想单个文件用 ESM,可将后缀改为 .mjs;单个文件用 CJS,后缀改为 .cjs(不受 package.jsontype 影响)。
    • ❌ 错误:项目根目录没配 "type": "module",却在 .js 文件里写 import/export → 直接报错。
  1. package.json 字段优先级要分清
json 复制代码
{
  "main": "./dist/index.cjs",
  "exports": {
    ".": {
      "require": "./dist/index.cjs",
      "import": "./dist/index.mjs"
    }
  }
}
    • ESM 优先读取 exports 字段,CJS 优先读取 main 字段。
    • 若要同时兼容 CJS 和 ESM,需在 exports 中分别声明:

二、 模块导入导出避坑

  1. 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):
  1. 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' → 导入的 fooundefined
    • ✅ 解决:要么解构默认导出,要么让 CJS 模块兼容导出:
  1. __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 手动实现:

三、 路径解析避坑

  1. ESM 必须写全文件扩展名
    • 这是最容易踩的坑!CJS 支持省略 .js/.json,ESM 不行。
    • ❌ 错误:import helper from './utils/helper' → 报 ERR_MODULE_NOT_FOUND
    • ✅ 正确:import helper from './utils/helper.js'
  1. ESM 加载目录必须配置 exports
json 复制代码
{
  "exports": { ".": "./index.js" } // 映射目录根到 index.js
}
    • CJS 加载目录时,会自动找 index.js;ESM 不会,必须在目录的 package.json 中配置 exports
    • 示例:utils 目录下加 package.json
    • 之后才能在 ESM 中这样写:import utils from './utils'
  1. 第三方包的路径映射优先看 exports
    • 若第三方包的 package.jsonexports 字段,ESM 会严格按其规则加载,不能"越级"导入子文件。
    • ❌ 错误:包 my-pkgexports 只暴露了根,却写 import foo from 'my-pkg/src/foo.js' → 报错。
    • ✅ 解决:要么让包在 exports 中添加该路径,要么用 CJS 加载(CJS 不受 exports 限制)。

四、 命令行运行与工具链避坑

  1. 运行 ESM 文件的参数注意
    • 若没配置 package.json"type": "module",运行 .mjs 文件无需额外参数;运行 .js 文件需加 --experimental-modules(低版本 Node),高版本 Node(v14.3+)无需。
    • 命令示例:node esm-file.js(已配 type: module)、node esm-file.mjs(无需配置)。
  1. TypeScript/打包工具的兼容设置
json 复制代码
{
  "compilerOptions": {
    "module": "ESNext",
    "moduleResolution": "NodeNext", // 匹配 Node.js 的 ESM 解析规则
    "outDir": "./dist",
    "esModuleInterop": true // 兼容 CJS 模块的默认导出
  }
}
    • tsc 编译时,若目标是 ESM,需在 tsconfig.json 中设置:
    • webpack/rollup 时,需明确配置 output.formatcjsesm,避免打包后模块类型混乱。

五、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')fooundefined 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 路径的大小写和实际文件名完全一致

六、额外通用排查技巧

  1. 检查 Node.js 版本:确保 Node.js ≥ v14.3.0,低版本对 ESM 支持不完善。
  2. 清理缓存 :运行 node --clear-module-cache src/xxx.js 清除模块缓存,解决缓存导致的解析异常。
  3. 绝对路径测试 :若相对路径报错,改用绝对路径(结合 __dirname)测试,排除相对路径层级错误。
相关推荐
be or not to be21 小时前
CSS 背景(background)系列属性
前端·css·css3
前端snow21 小时前
在手机端做个滚动效果
前端
webkubor21 小时前
🧠 2025:AI 写代码越来越强,但我的项目返工却更多了
前端·机器学习·ai编程
Sun_小杰杰哇21 小时前
Dayjs常用操作使用
开发语言·前端·javascript·typescript·vue·reactjs·anti-design-vue
戴维南21 小时前
TypeScript 在项目中的实际解决的问题
前端
晴殇i21 小时前
package.json 中的 dependencies 与 devDependencies:深度解析
前端·设计模式·前端框架
pas13621 小时前
25-mini-vue fragment & Text
前端·javascript·vue.js
何贤21 小时前
2025 年终回顾:25 岁,从“混吃等死”到别人眼中的“技术专家”
前端·程序员·年终总结
冴羽1 天前
CSS 新特性!瀑布流布局的终极解决方案
前端·javascript·css