Node.js 模块化规范详解

在 Node.js 中,模块化是开发应用程序的核心概念,它使得代码可以按照功能模块进行分割,易于维护、复用和扩展。Node.js 支持两种模块化规范:

  • CommonJS(CJS):这是 Node.js 最初使用的模块化规范。

  • ECMAScript Modules(ESM):这是现代 JavaScript 的官方模块化规范,自 ECMAScript 2015(ES6)引入。

1. CommonJS 模块化规范

CommonJS 是 Node.js 早期就支持的模块化标准,允许在服务端使用模块。在 CommonJS 中,每个文件都被视为一个独立的模块。你可以通过 module.exports 导出模块内容,并使用 require() 函数引入模块。

1.1. CommonJS 基本用法

  • 导出模块:使用 module.exports 或 exports。

  • 引入模块:使用 require()。

定义模块:

javascript 复制代码
// math.js - 定义模块
function add(a, b) {
    return a + b;
}

function subtract(a, b) {
    return a - b;
}

// 使用 module.exports 导出模块
module.exports = {
    add,
    subtract,
};

// 或者也可以使用 exports(两者作用相同)
exports.add = add;
exports.subtract = subtract;

引入模块:

javascript 复制代码
// main.js - 引入并使用模块
const math = require('./math');

console.log(math.add(5, 3));  // 输出:8
console.log(math.subtract(5, 3));  // 输出:2

1.2. CommonJS 特点

  • 同步加载:require() 是同步的,这意味着模块会在需要时同步加载。这在服务端是可以接受的,但在浏览器中不够高效。

  • 模块缓存:加载的模块会被缓存,因此多次 require() 同一个模块时,模块只会被加载一次。

2. ECMAScript Modules 规范

随着 JavaScript 语言的发展,ECMAScript Modules(ESM)被引入,成为 JavaScript 官方标准的模块系统。Node.js 从版本 12.17 开始支持 ESM,Node.js 通过引入 .mjs 文件扩展名和 package.json 中的 "type": "module" 字段来实现对 ESM 的支持。

2.1. ECMAScript Modules 基本用法

  • 导出模块:使用 export 关键字。

  • 引入模块:使用 import 关键字。

定义模块:

javascript 复制代码
// math.mjs - 定义模块
export function add(a, b) {
    return a + b;
}

export function subtract(a, b) {
    return a - b;
}

导入模块:

javascript 复制代码
// main.mjs - 引入并使用模块
import { add, subtract } from './math.mjs';

console.log(add(5, 3));  // 输出:8
console.log(subtract(5, 3));  // 输出:2

2.2. ESM 特点

  • 静态解析:ESM 是静态的,这意味着在代码编译时就能确定模块的依赖关系(相较于 CommonJS 的动态加载)。

  • 异步加载:import 支持异步加载,这在浏览器中更高效,特别是当你需要懒加载模块时。

  • 严格模式:所有的 ECMAScript 模块都默认处于严格模式("use strict")。

  • 不允许动态导入和导出:ESM 不支持像 CommonJS 那样的动态 require,必须在顶层进行 import/export。

3. 文件后缀与模块加载规则

3.1. CommonJS 文件后缀

对于 CommonJS 模块,文件通常使用 .js 后缀。如果你在 Node.js 中编写 CommonJS 模块,直接使用 .js 文件即可。

3.2. ECMAScript 模块的文件后缀

在 Node.js 中,可以通过以下两种方式启用 ESM:

  • 使用 .mjs 文件后缀:如果文件扩展名是 .mjs,Node.js 会将其视为 ESM。

  • 配置 package.json:在项目的 package.json 中设置 "type": "module",这样即使文件是 .js 后缀,Node.js 也会将其作为 ESM 处理。

3.3. 如何区分 CommonJS 和 ESM

Node.js 根据以下规则来确定模块系统:

  • CommonJS:默认情况下,Node.js 中所有 .js 文件都被视为 CommonJS 模块,除非另有指定。

  • ESM:当你使用 .mjs 扩展名,或者在 package.json 中指定 "type": "module",所有的 .js 文件都会被视为 ESM 模块。

4. require 和 import 互操作性

尽管 Node.js 支持 CommonJS 和 ESM 两种规范,但两者在一起使用时需要注意一些限制:

4.1. 从 CommonJS 中引入 ESM

从 CommonJS 文件中引入 ESM 模块是有一定限制的。require() 无法直接加载 ESM 模块,必须使用 import() 函数(它是一个异步函数)。

javascript 复制代码
// 在 CommonJS 模块中动态引入 ESM 模块
(async () => {
    const math = await import('./math.mjs');
    console.log(math.add(5, 3));
})();

4.2. 从 ESM 中引入 CommonJS

ESM 模块可以直接通过 import 引入 CommonJS 模块,因为 Node.js 会将 CommonJS 模块包装成 ESM 格式以供使用。

javascript 复制代码
// 在 ESM 中引入 CommonJS 模块
import math from './math.js';  // 假设 math.js 是一个 CommonJS 模块
console.log(math.add(5, 3));

5. 选择 CommonJS 还是 ESM?

在实际开发中,选择使用 CommonJS 还是 ESM 取决于几个因素:

  • 兼容性:如果你要支持旧的 Node.js 版本(12 之前)或使用大量依赖的第三方库(特别是历史遗留的),CommonJS 可能更合适。

  • 未来发展:ESM 是 JavaScript 官方标准,未来会有更多的支持,因此如果是新项目,推荐使用 ESM。

  • 生态系统:目前 Node.js 生态中的大多数库仍然使用 CommonJS,但越来越多的库开始迁移到 ESM。

相关推荐
小小怪KO2 天前
分布式锁解决集群下一人一单超卖问题
java·分布式·tomcat·后端开发·实习·黑马点评
叫我阿柒啊4 天前
Java全栈开发工程师的实战面试经历:从基础到微服务
java·微服务·typescript·vue·springboot·前端开发·后端开发
红鼻子时代7 天前
Day5-中间件与请求处理
中间件·fastapi·后端开发
叫我阿柒啊7 天前
从Java全栈到前端框架:一位程序员的实战之路
java·spring boot·微服务·消息队列·vue3·前端开发·后端开发
叫我阿柒啊11 天前
从Java全栈开发到微服务架构:一次真实的面试实录
java·微服务·vue3·springboot·前端开发·后端开发·项目经验
叫我阿柒啊12 天前
Java全栈开发工程师的实战面试:从技术到业务场景
java·微服务·vue3·springboot·前端开发·后端开发·数据库优化
小哈里13 天前
【Game-Infra】游戏开发的流程,游戏发布的打包与构建(硬件选型,SDK与操作系统,包体管理,弹性构建,构建调优)
游戏·游戏开发·后端开发·基础设施·打包构建·游戏发布·弹性构建
小凡敲代码18 天前
2025年金九银十Java面试场景题大全:高频考点+深度解析+实战方案
java·程序员·java面试·后端开发·求职面试·java场景题·金九银十