查看: 110|回复: 0

Node.js CommonJS与ESM混用适配及报错解决

[复制链接]
发表于 1 小时前 | 显示全部楼层 |阅读模式
Node.js 进入 CommonJS 与 ESM 并存的阶段后,npm 包新旧规范混杂,项目里经常出现 require() of ES Module not supported、Cannot use import statement in a CommonJS module、默认导出丢失、命名导出不存在等问题。原文的落地思路是先把 type 字段和两条模块加载规则定清楚,再分别处理 ESM 引 CJS、CJS 引 ESM 两种场景,最后用 TS 配置和目录隔离降低维护成本。

一、冲突根源:CJS 与 ESM 的核心差异

CommonJS 使用 require() 导入、module.exports 或 exports 导出,运行时动态加载,文件后缀可省略并默认 .js,顶层可直接使用 module、exports、require、__dirname、__filename。ESM 使用 import 或 import() 导入,使用 export default 或 export 具名导出,编译期静态解析并支持 Tree-Shaking,导入文件必须补全 .js 或 .json 后缀,顶层没有内置的 __dirname、__filename、require,需要手动兼容。

Node.js 有两条不能绕过的混合规则:CJS 可以引入 ESM,但必须使用动态 import(),不能直接 require ESM;ESM 可以直接引入 CJS,但 CJS 的默认导出会被统一挂载到 module.exports;当前文件按哪种模块规范解析,由 package.json 的 type 字段决定。

二、type 字段决定 .js 文件的模块类型

package.json 没有 type 字段,或 type 取 commonjs 时,所有 .js 文件默认按 CommonJS 解析,支持 require、module.exports,不支持顶层 import/export,直接写 import 会报错。type 取 module 时,所有 .js 文件默认按 ESM 解析,强制使用 import/export,禁止直接使用 require,并支持顶层 await、静态模块解析,文件导入必须补全后缀。

三、场景一:ESM 项目引入老旧 CJS 包

新项目使用 ESM 规范,但依赖大量 CommonJS 第三方包时,属于原生兼容场景,不需要额外配置,ESM 可以直接 import CJS 模块。
  1. import axios from 'axios';
  2. import * as lodash from 'lodash';
复制代码

如果 ESM 中需要用到 CJS 的 __dirname、__filename、require,可以手动兼容:
  1. import { fileURLToPath } from 'url';
  2. import { dirname, join } from 'path';
  3. import { createRequire } from 'module';
  4. const __filename = fileURLToPath(import.meta.url);
  5. const __dirname = dirname(__filename);
  6. const require = createRequire(import.meta.url);
  7. const oldModule = require('./commonjs-old.js');
复制代码

这里通过 fileURLToPath 和 dirname 还原 __dirname,通过 createRequire 在 ESM 中启用 CJS 导入能力。

四、场景二:CJS 项目引入新版纯 ESM 包

老项目基于 CommonJS,升级依赖后第三方包变成纯 ESM,直接 require 会报 require() of ES Module not supported。原因是 CJS 运行时不支持同步加载静态 ESM 模块,Node 禁止同步 require ESM。解决方案是在 CJS 中使用动态 import() 异步加载。
  1. async function loadESM() {
  2.   const esmModule = await import('new-esm-package');
  3.   console.log(esmModule.default);
  4. }
  5. loadESM();
复制代码

这种写法会把模块加载变成异步流程,调用方需要按 Promise 处理返回结果,不能在同步代码路径里直接拿到 ESM 导出值。

五、全局混合兼容配置

中大型项目存在 CJS 旧业务与 ESM 新功能长期并存时,原文推荐新项目、迭代中项目统一开启 type: module,通过兼容代码适配老旧 CJS 依赖,而不是反向降级。核心 package.json 配置如下:
  1. {
  2.   "type": "module",
  3.   "main": "./index.js",
  4.   "module": "./index.js",
  5.   "exports": {
  6.     ".": "./index.js"
  7.   }
  8. }
复制代码

ESM 强制要求文件后缀。不想修改源码时,可配置别名兼容,vite、webpack、node 均可适配,Node 原生可通过自定义 resolve 规避。工程上更推荐目录隔离:/src 全部使用 ESM 规范,使用 import/export;/legacy 存放老旧 CJS 代码,通过异步 import 被 src 层调用。这样能彻底规避混用混乱。

六、TS 项目混合模块适配

TypeScript 项目是模块混用重灾区,只需修改 tsconfig.json 即可适配双规范:
  1. {
  2.   "compilerOptions": {
  3.     "module": "ESNext",
  4.     "target": "ES2022",
  5.     "moduleResolution": "NodeNext",
  6.     "allowSyntheticDefaultImports": true,
  7.     "esModuleInterop": true
  8.   }
  9. }
复制代码

其中 module 输出 ESM 规范代码,target 兼容 Node18+,moduleResolution 适配 ESM 解析规则,allowSyntheticDefaultImports 与 esModuleInterop 用于抹平 CJS 与 ESM 默认导出差异,避免导入 CJS 模块时默认导出为 undefined。

七、高频报错与根治方案

报错 1:require() of ES Module not supported。原因是 CJS 模块同步 require 纯 ESM 包;解决方式是替换为 await import() 异步动态导入。

报错 2:Cannot use import statement in a CommonJS module。原因是文件没有 type: module,被 Node 识别为 CJS,无法使用 import;解决方式是在根目录 package.json 中添加 type 为 module。

报错 3:导入 CJS 模块,默认导出为 undefined。原因是 CJS 模块导出挂载在 module.exports,与 ESM 解析规则存在差异;解决方式是开启 esModuleInterop,或使用 import * as xxx 整体导入。

报错 4:相对路径导入提示模块找不到。原因是 ESM 必须补全 .js/.json 后缀;解决方式是统一添加文件后缀,或配置构建工具自动补全。

八、企业级混合模块开发最佳实践

新项目统一 ESM,Vite、TS、Vue3、React 项目强制开启 type: module,跟随现代规范;老项目渐进式迁移,CJS 老项目不整体重构,新页面新逻辑全部使用 ESM,通过动态 import 互通;单个文件内不允许同时出现 require 和 import,语法保持统一;版本兜底最低 Node16+,推荐 Node18 LTS;TS 必开 esModuleInterop,抹平双模块导出差异;新旧模块目录分离,降低维护成本与报错概率。

总结起来,Node.js 模块混合使用的核心本质是:ESM 向下兼容 CJS,CJS 无法向上同步兼容 ESM。ESM 项目引 CJS 直接 import,手动兼容 __dirname/require 即可;CJS 项目引 ESM 必须使用异步 await import(),禁止同步 require。配合 type 配置、TS 兼容配置和目录隔离规范,可以解决新旧项目迭代中的常见模块混用报错。
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 注册

本版积分规则

指导单位

江苏省公安厅

江苏省通信管理局

浙江省台州刑侦支队

DEFCON GROUP 86025

Hacking Group 021A

旗下站点

态势感知中心

应急响应中心

红盟安全

联系我们

官方QQ群:112851260

官方邮箱:security#ihonker.org(#改成@)

官方核心成员

关注微信公众号

Archiver|手机版|小黑屋| ( 沪ICP备2021026908号 )

GMT+8, 2026-10-25 16:17 , Processed in 0.022083 second(s), 29 queries , Gzip On.

Powered by ihonker.com

Copyright © 2015-现在.

  • 返回顶部