模块系统:CommonJS vs ES Modules

Node.js 的模块系统是其核心功能之一,它允许开发者将代码组织成可重用的模块。在 Node.js 的发展历程中,主要经历了两种模块系统:CommonJS 和 ES Modules(ESM)。本文将深入探讨这两种模块系统的特点、差异以及如何在 Node.js 中使用它们。

CommonJS 模块系统

CommonJS 是 Node.js 最初采用的模块系统,它采用同步加载的方式,非常适合服务器端环境。

基本语法

javascript 复制代码
// 导出模块
module.exports = {
  function1,
  function2
};

// 或者
exports.function1 = function1;
exports.function2 = function2;

// 导入模块
const myModule = require('./myModule');

特点

  1. 同步加载:模块在 require 时同步加载和执行
  2. 运行时解析:模块路径在运行时解析
  3. 动态导入:可以在代码的任何位置使用 require
  4. 缓存机制:模块在第一次加载后会被缓存

ES Modules (ESM)

ES Modules 是 ECMAScript 标准的一部分,随着 Node.js 的发展,逐渐被引入并成为官方支持的模块系统。

基本语法

javascript 复制代码
// 导出模块
export function function1() { /* ... */ }
export const constant1 = 'value';

// 或者
export default {
  function1,
  constant1
};

// 导入模块
import { function1, constant1 } from './myModule.js';
import myModule from './myModule.js';

特点

  1. 静态分析:导入导出语句必须在顶层作用域,可以被静态分析
  2. 异步加载:支持异步加载模块
  3. 严格模式:默认在严格模式下执行
  4. 浏览器兼容:与浏览器端的 ES Modules 保持一致

主要差异对比

特性 CommonJS ES Modules
加载方式 同步 异步
解析时机 运行时 静态分析(编译时)
导入语法 require() import
导出语法 module.exports/exports export/export default
动态导入 支持 通过 import() 函数支持
顶层 await 不支持 支持
文件扩展名 可省略 必须包含(.js/.mjs)
严格模式 默认非严格 默认严格

在 Node.js 中使用 ES Modules

要在 Node.js 中使用 ES Modules,有以下几种方式:

  1. 使用 .mjs 文件扩展名
  2. package.json 中设置 "type": "module"
  3. 使用 --input-type=module 标志
json 复制代码
// package.json
{
  "type": "module"
}

互操作性

Node.js 提供了两种模块系统之间的互操作性:

  1. 在 ES Modules 中导入 CommonJS 模块:可以直接使用 import 语法
  2. 在 CommonJS 中导入 ES Modules:需要使用动态 import() 函数
javascript 复制代码
// 在 CommonJS 中导入 ES Module
(async () => {
  const esModule = await import('./es-module.mjs');
})();

最佳实践

  1. 新项目:推荐使用 ES Modules,因为它是 ECMAScript 标准且功能更强大
  2. 现有项目:如果项目基于 CommonJS,可以逐步迁移或保持现状
  3. 库开发:考虑同时支持两种模块系统以最大化兼容性

总结

Node.js 的模块系统从 CommonJS 发展到 ES Modules,反映了 JavaScript 生态系统的演进。了解这两种模块系统的特点和差异,有助于开发者根据项目需求做出合理选择。随着 Node.js 对 ES Modules 支持的不断完善,ES Modules 正逐渐成为 Node.js 模块系统的未来方向。