【node.js】node.js 两种模块规范 CommonJS 和 ESM 如何选择?

在 Node.js 的生态系统中,存在两种主要的模块规范:CommonJS (CJS)ES Modules (ESM)

理解它们的区别对于现代前端和后端开发至关重要,因为 Node.js 正在从 CJS 全面转向 ESM。


1. CommonJS (CJS)

CommonJS 是 Node.js 诞生的默认模块系统,已经存在了十多年。

  • 语法 :使用 require() 导入模块,使用 module.exports 导出模块。
  • 加载方式同步加载 。这意味着当你调用 require() 时,Node.js 会阻塞后续代码的执行,直到模块加载完成。
  • 适用场景:传统 Node.js 项目、服务器端脚本。

示例代码:

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

// 导入 (main.js)
const { add } = require('./math.js');
console.log(add(2, 3));

2. ES Modules (ESM)

ESM 是 JavaScript 在 2015 年(ES6)确立的标准官方模块系统,旨在让浏览器和服务器通用一套标准。

  • 语法 :使用 import 导入,使用 export 导出。
  • 加载方式异步加载。它分为三个阶段:构建(解析)、链接、求值。这种机制支持"静态分析"。
  • 适用场景:现代前端框架(Vue, React)、浏览器环境、现代 Node.js 项目。

示例代码:

javascript 复制代码
// 导出 (math.js)
export const add = (a, b) => a + b;

// 导入 (main.js)
import { add } from './math.js';
console.log(add(2, 3));

3. 核心区别对比

特性 CommonJS (CJS) ES Modules (ESM)
关键字 require / module.exports import / export
加载时机 运行时加载。 编译时加载(静态分析)。
加载方式 同步。 异步。
顶层变量 __dirname, __filename 没有这些变量(需用 import.meta.url 模拟)。
this 指向 指向当前模块。 undefined
Tree Shaking 不支持(因为是动态加载)。 支持(可以删除未使用的代码)。
严格模式 默认不开启。 强制开启 ("use strict")。

4. 关键差异深度解析

(1) 静态分析 vs 动态加载
  • ESM 是静态的import 语句必须放在文件顶部。因为它是静态的,打包工具(如 Webpack, Rollup, Vite)可以在代码运行前知道哪些代码被使用了,从而进行 Tree Shaking(剔除无效代码),缩小包体积。
  • CJS 是动态的 :你可以在 if 语句或函数内部写 require()。这使得它很灵活,但也无法在编译阶段进行优化。
(2) 值的拷贝 vs 值的引用
  • CJS :导出的是值的拷贝。一旦输出一个值,模块内部的变化不会影响到已加载的值。
  • ESM :导出的是值的动态只读引用(Live Bindings)。如果模块内部修改了变量,外部引用的地方也会同步更新(但外部不能修改它)。
(3) 环境变量(__dirname)

在 CJS 中,你可以直接使用 __dirname 获取当前目录。在 ESM 中,这些全局变量不存在,你需要这样操作:

javascript 复制代码
// ESM 获取 __dirname 的方法
import { fileURLToPath } from 'url';
import { dirname } from 'path';

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

5. 如何在 Node.js 中选择?

现在 Node.js 同时支持两者,判断规则如下:

  1. 文件名后缀
    • .cjs 总是被视为 CommonJS。
    • .mjs 总是被视为 ES Modules。
    • .js 取决于最近的 package.json
  2. package.json
    • 设置 "type": "module",则 .js 文件被视为 ESM
    • 设置 "type": "commonjs" 或不设置,则 .js 文件被视为 CJS

6. 互操作性(能不能混用?)

  • ESM 导入 CJS :可以。import cjs from './file.cjs' 通常能工作,但只能默认导入,不能通过解构导入(因为 CJS 是运行时生成的)。

  • CJS 导入 ESM不可以 使用 require()。因为 ESM 是异步的,而 require 是同步的。如果必须在 CJS 中用 ESM,只能使用异步的 import() 函数:

    javascript 复制代码
    // 在 CJS 文件中
    import('./esm-file.mjs').then(module => {
        // 使用模块
    });

总结

  • 如果你正在开发新项目 ,建议优先使用 ESM,因为它是未来的标准,且支持更好的性能优化。
  • 如果你在维护老旧项目 或使用大量仅支持 CJS 的老旧库,则继续使用 CommonJS
相关推荐
FungLeo6 小时前
成为全栈·Node 后端篇·全文搜索:从 LIKE 到全文索引
node.js·全文索引·模糊搜索·全文搜索·成为全栈·like 搜索
倾颜1 天前
AI Chat 长会话性能实践:消息虚拟化、动态高度与流式滚动设计
前端·react.js·node.js
FungLeo1 天前
成为全栈·Node 后端篇·接口文档自动化:让 OpenAPI 与代码不脱节
node.js·openapi·成为全栈·接口文档自动化
右耳朵猫AI1 天前
Node.js周刊2026W36 | NestJS 12发布、Remix 3 RC、pnpm 12 Rust重写、Node.js 26.8.0
开发语言·rust·node.js
www_aiyuanma_vip1 天前
实验室设备借用系统
node.js
右耳朵猫AI1 天前
Web前端周刊2026W36 | pnpm 12 Rust 重写、Remix 3 RC、Node.js 26.8.0、htmx 4.0 大版本
前端·rust·node.js
FungLeo2 天前
成为全栈·Node 后端篇·文件上传:R2 / 本地磁盘双实现与签名直传
node.js·文件上传·成为全栈·cloudflare r2·文件去重
FungLeo2 天前
成为全栈·Node 后端篇·认证方案:JWT 还是 Session
node.js·jwt·session·认证方案
FungLeo2 天前
成为全栈·Node 后端篇·文章 CRUD 与投稿状态机
node.js·状态机·crud·成为全栈
晴天162 天前
Webpack3/4/5 核心差异、性能升级与迁移实战指南-Day38
webpack·node.js