node.js

关注公众号 jb51net

关闭
首页 > 网络编程 > JavaScript > node.js > Node.js CommonJS/ESM混用适配方案

Node.js模块化混合开发之CommonJS/ESM混用适配方案与落地配置

作者:晴天16

在现代 Node.js 项目开发中,我们长期面临一个核心工程化问题:模块规范不统一,本文深入讲解CommonJS与ESM的核心差异和混用规则,提供两种混合场景的完整适配方案、全局配置和TS项目兼容设置,帮你彻底解决模块混用报错,需要的朋友可以参考下

一、前言:为什么会出现模块混用问题

在现代 Node.js 项目开发中,我们长期面临一个核心工程化问题:模块规范不统一

Node.js 发展初期默认采用 CommonJS(CJS) 规范(require / module.exports);自 Node14 开始原生稳定支持 ESM 规范(import / export),Node16+、Node18+ 已全面普及 ESM,成为 Vite、TS、前端工程化的标准规范。

但目前 npm 生态处于新旧过渡阶段

由此产生大量诡异报错:require() of ES Module not supportedCannot use import statement in a CommonJS module、默认导出丢失、命名导出不存在等。

本文聚焦 Node.js 双模块规范混合使用,讲解底层冲突原理、完整配置方案、兼容适配、工程落地规范,彻底解决模块混用报错问题。

二、CommonJS 与 ESM 核心差异(混用冲突根源)

2.1 基础语法差异

特性CommonJS(CJS)ESM(ES6 模块)
导入语法require()import / import()
导出语法module.exports / exportsexport default / export 具名
加载时机运行时动态加载编译期静态解析、Tree-Shaking 支持
文件后缀可省略,默认 .js必须补全 .js/.json 后缀
顶层变量存在 module、exports、require、__dirname、__filename无内置变量,需手动兼容

2.2 核心冲突规则(Node 硬性限制)

Node.js 有两条不可绕过的模块混用规则,90% 报错均源于此:

  1. CJS 模块可以引入 ESM 模块(需动态 import()),无法直接 require ESM;
  2. ESM 模块可以直接引入 CJS 模块,但 CJS 的默认导出会被统一挂载到 module.exports
  3. 文件模块类型由 package.jsontype 字段决定。

三、type 字段:决定文件模块类型的核心配置

Node.js 通过 package.jsontype 字段,判定当前项目下 .js 文件的模块规范,这是混合使用的核心开关

3.1 type 两种取值规则

默认无 type / type: “commonjs”

type: “module”

四、两种混合场景完整适配方案(实战核心)

实际项目只有两种混用场景,下文提供可直接落地的配置与代码

场景一:项目是 ESM(type:module),需要引入老旧 CJS 包

场景描述:新项目使用 ESM 规范,但是依赖大量 CommonJS 第三方包(axios、lodash 等旧版包)。

适配结论完全原生兼容,无需额外配置,ESM 可直接 import CJS 模块。

4.1 正确导入写法

// ESM 项目中引入 CJS 包(直接用)
import axios from 'axios';
// 具名导入 CJS 模块(兼容写法)
import * as lodash from 'lodash';

4.2 ESM 兼容 CJS 缺失变量方案

ESM 无 __dirname、__filename、require,如需使用 CJS 原生变量,手动兼容:

import { fileURLToPath } from 'url';
import { dirname, join } from 'path';
import { createRequire } from 'module';

// 兼容 __dirname
const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename);

// 兼容 require 方法(ESM 中手动启用 CJS 导入)
const require = createRequire(import.meta.url);
const oldModule = require('./commonjs-old.js');

场景二:项目是 CJS(默认规范),需要引入新版 ESM 包

场景描述:老项目基于 CommonJS,升级部分依赖后,第三方包升级为纯 ESM,直接 require 报错:require() of ES Module not supported

核心原因:CJS 运行时不支持加载静态 ESM 模块,Node 禁止同步 require ESM。

解决方案:CJS 中使用 动态 import() 异步加载

4.3 CJS 引入 ESM 标准写法

// 老 CJS 项目中,加载纯 ESM 新包
async function loadESM() {
  // 异步动态导入 ESM 模块
  const esmModule = await import('new-esm-package');
  console.log(esmModule.default);
}
loadESM();

五、全局混合兼容配置(项目通用方案)

针对中大型项目CJS 旧业务 + ESM 新功能长期并存 的场景,提供一套零报错、可长期维护的混合配置方案。

5.1 统一 package.json 核心配置

推荐新项目、迭代中项目统一开启 type:module,通过兼容代码适配老旧 CJS 依赖,而非反向降级。

{
  "type": "module",
  "main": "./index.js",
  "module": "./index.js",
  "exports": {
    ".": "./index.js"
  }
}

5.2 解决后缀名报错配置

ESM 强制要求文件后缀,不想修改源码可配置别名兼容(vite / webpack / node 均可适配),Node 原生可通过自定义 resolve 规避。

5.3 目录隔离方案(最佳工程实践)

为彻底规避混用混乱,推荐目录隔离规范:

六、TS 项目混合模块适配配置(高频场景)

TypeScript 项目是模块混用重灾区,只需修改tsconfig.json 即可完美适配双规范。

{
  "compilerOptions": {
    // 输出 ESM 规范代码
    "module": "ESNext",
    // 编译后语法兼容 Node18+
    "target": "ES2022",
    // 解析规则适配 ESM
    "moduleResolution": "NodeNext",
    // 允许引入 CommonJS 模块
    "allowSyntheticDefaultImports": true,
    "esModuleInterop": true
  }
}

核心作用:开启 esModuleInterop 后,TS 自动抹平 CJS 与 ESM 默认导出差异,杜绝导出 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

解决:根目录添加 "type":"module"

报错3:导入 CJS 模块,默认导出为 undefined

原因:CJS 模块导出挂载在 module.exports,ESM 解析规则差异

解决:开启 esModuleInterop 或使用 import * as xxx 整体导入

报错4:相对路径导入提示模块找不到

原因:ESM 必须补全 .js/.json 后缀

解决:统一添加文件后缀,或配置构建工具自动补全

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

  1. 新项目统一 ESM:所有 Vite/TS/Vue3/React 项目强制开启 type:module,跟随现代规范
  2. 老项目渐进式迁移:CJS 老项目不整体重构,新页面新逻辑全部使用 ESM,通过动态 import 互通
  3. 禁止混用语法:单个文件内不允许同时出现 require 和 import,语法统一
  4. 版本兜底:混合模块开发最低 Node16+,推荐 Node18 LTS
  5. TS 必开兼容配置:esModuleInterop 开启,抹平双模块导出差异
  6. 目录隔离:新旧模块目录分离,降低维护成本与报错概率

九、总结

Node.js 模块混合使用的核心本质:ESM 向下兼容 CJS,CJS 无法向上同步兼容 ESM

所有混合场景只需记住两套万能规则:

通过本文的 type 配置、TS 兼容方案、目录隔离规范,可以彻底解决 Node.js 双模块混用的所有报错,实现新旧项目平稳迭代、无缝兼容。

以上就是Node.js模块化混合开发之CommonJS/ESM混用适配方案与落地配置的详细内容,更多关于Node.js CommonJS/ESM混用适配方案的资料请关注脚本之家其它相关文章!

您可能感兴趣的文章:
阅读全文