Node.js 模块系统详解之CommonJS 与 ES Module概念和配置选择

以下是针对初学者的 ​​Node.js 模块系统详解​​,用通俗语言和代码示例解释 CommonJS 与 ES Module(ESM)的核心概念、区别及使用场景,帮你快速理清思路:


一、什么是模块系统?

模块系统是代码组织的规则,用于​​拆分代码为独立文件​ ​(模块),并通过特定语法​​导入/导出​​功能。Node.js 早期用 CommonJS,现代前端和 Node.js 推荐用 ES Module(ESM)。


二、CommonJS:Node.js 的"老伙计"

​核心特点​

  • ​同步加载​:模块加载时阻塞代码执行(Node.js 服务端场景友好,因为文件多在本地)。
  • ​运行时动态​:模块的导入/导出在代码运行时确定(动态性强,但无法静态分析)。
  • ​语法简单​ :用 require() 导入,module.exportsexports 导出。

​基本用法​

1. 导出模块(math.js

ini 复制代码
// math.js
const add = (a, b) => a + b;
const PI = 3.14;

// 导出单个值(覆盖默认导出)
exports.add = add;

// 导出多个值(对象形式)
module.exports = { PI };

2. 导入模块(app.js

javascript 复制代码
// app.js
const math = require('./math.js'); // 导入整个模块
console.log(math.add(2, 3)); // 输出 5
console.log(math.PI); // 输出 3.14(注意:这里会报错!因为上面 exports.add 覆盖了 module.exports)

​注意​ ​:exportsmodule.exports 的引用,若直接赋值 exports = { ... } 会断开引用,导致导出失败。正确做法是统一用 module.exports

javascript 复制代码
// 正确导出(math.js)
module.exports = {
  add: (a, b) => a + b,
  PI: 3.14
};

​适用场景​

  • ​旧项目迁移​:早期 Node.js 项目(如 Express 4.x 及之前)普遍使用 CommonJS。
  • ​依赖 CommonJS 的库​ :部分第三方库(如 lodash 早期版本)仅支持 CommonJS。
  • ​简单脚本​:无需复杂静态分析的小工具(如本地脚本)。

三、ES Module(ESM):现代 JavaScript 的"新标准"

​核心特点​

  • ​异步加载​:模块加载不阻塞代码执行(浏览器端原生支持,Node.js 中需配置)。
  • ​静态分析​:导入/导出在编译时确定(支持 Tree Shaking,减少打包体积)。
  • ​语法更严谨​ :用 import/export 声明式导入导出,支持命名导出、默认导出。

​基本用法​

1. 导出模块(math.mjsmath.js + type: "module"

javascript 复制代码
// math.js(需在 package.json 中配置 "type": "module")
// 命名导出(多个功能)
export const add = (a, b) => a + b;
export const PI = 3.14;

// 默认导出(一个主要功能)
export default function multiply(a, b) {
  return a * b;
}

2. 导入模块(app.js

javascript 复制代码
// app.js(同样需配置 "type": "module")
// 导入命名导出(需用大括号)
import { add, PI } from './math.js';
console.log(add(2, 3)); // 输出 5
console.log(PI); // 输出 3.14

// 导入默认导出(无需大括号)
import multiply from './math.js';
console.log(multiply(2, 3)); // 输出 6

// 导入所有导出(用 * 接收)
import * as MathUtils from './math.js';
console.log(MathUtils.PI); // 输出 3.14

​Node.js 中使用 ESM 的条件​

  • ​文件扩展名​ :使用 .mjs(明确声明为 ESM)。
  • ​package.json 配置​ :在项目根目录的 package.json 中添加 "type": "module"(所有 .js 文件视为 ESM)。type默认是commonjs,即允许 require

四、CommonJS 与 ESM 的核心区别

​特性​ ​CommonJS​ ​ES Module(ESM)​
​加载方式​ 同步(运行时阻塞) 异步(编译时加载,不阻塞)
​语法​ require() / module.exports import / export
​动态性​ 动态(运行时可修改导出) 静态(编译时确定,无法动态修改)
​作用域​ 每个文件独立模块作用域 每个文件独立模块作用域
​Tree Shaking​ 不支持(无法静态分析依赖) 支持(仅打包实际使用的代码)
​浏览器支持​ 不支持(仅 Node.js) 原生支持(现代浏览器)
​Node.js 支持​ 原生支持(无需配置) 需配置 .mjspackage.json

五、推荐使用场景

1. ​​优先选 ESM 的场景​

  • ​新项目​:现代 JavaScript 项目(如 React、Vue、Vite 构建的项目)默认用 ESM。
  • ​前端开发​:浏览器原生支持 ESM,与打包工具(Webpack、Vite)兼容更好。
  • ​需要 Tree Shaking​:减少打包体积(如生产环境优化)。
  • ​静态分析工具​:如 TypeScript、ESLint 能更精准分析 ESM 依赖。

2. ​​可选 CommonJS 的场景​

  • ​旧项目迁移​:已有大量 CommonJS 代码的项目(逐步迁移更稳妥)。
  • ​依赖 CommonJS 的库​ :部分第三方库(如 express 早期版本)仅支持 CommonJS。
  • ​简单脚本​:无需复杂打包的小工具(如本地自动化脚本)。

3. ​​混合使用(Node.js 中)​

Node.js 支持在 ESM 中导入 CommonJS 模块(反之不行):

javascript 复制代码
// ESM 文件(app.js)
import cjsModule from './commonjs-module.cjs'; // 导入 CommonJS 模块(需 .cjs 扩展名)
console.log(cjsModule.add(2, 3));

六、常见问题与避坑指南

1. 报错:"Cannot use import statement outside a module"

  • ​原因​ ​:在 ESM 中使用了 require(),或在普通 .js 文件中用了 import(未配置 type: "module")。

  • ​解决​​:

    • 改用 import 语法,或在 .js 文件所在目录的 package.json 中添加 "type": "module"
    • 若需混合使用,CommonJS 模块用 .cjs 扩展名(Node.js 识别为 CommonJS)。

2. 报错:"module.exports is not defined"

  • ​原因​ :在 ESM 文件中用了 module.exports(ESM 不支持此语法)。
  • ​解决​ :改用 export 语法(如 export defaultexport const)。

3. 动态导入(import()

  • ​场景​​:需要根据条件动态加载模块(如按需加载)。

  • ​用法​​:

    javascript 复制代码
    // 动态导入(返回 Promise)
    const loadModule = async () => {
      const module = await import('./dynamic-module.js');
      module.run();
    };
    loadModule();

七、总结:一句话选择建议

  • ​新项目/前端项目​ → 用 ESM(import/export)。
  • ​旧项目/依赖 CommonJS 的库​ → 用 CommonJS(require/module.exports)。
  • ​Node.js 中混合使用​ → ESM 导入 CommonJS(.cjs 文件),或配置 type: "module"

通过理解两者的核心差异和适用场景,你可以更高效地选择模块系统,避免开发中的"语法坑"! 🚀

相关推荐
江湖人称小鱼哥20 分钟前
react接口防抖处理
前端·javascript·react.js
GISer_Jing30 分钟前
腾讯前端面试模拟详解
前端·javascript·面试
萌萌哒草头将军1 小时前
🚀🚀🚀 Webpack 项目也可以引入大模型问答了!感谢 Rsdoctor 1.2 !
前端·javascript·webpack
小白的代码日记1 小时前
Springboot-vue 地图展现
前端·javascript·vue.js
teeeeeeemo1 小时前
js 实现 ajax 并发请求
开发语言·前端·javascript·笔记·ajax
清秋2 小时前
全网最全 ECMAScript 攻略( 更新至 ES2025)
前端·javascript·ecmascript 6
Juchecar3 小时前
Node.js package.json 配置详解 + TypeScript + ES Module 集成指南
javascript
李明卫杭州3 小时前
深入理解CSS变量(Custom Properties)
前端·javascript
一枚前端小能手3 小时前
💫 回调套回调写到崩溃,异步编程其实可以很优雅
前端·javascript
用户47949283569153 小时前
深入理解JavaScript:手写实现Array.prototype.push方法
前端·javascript