Node.js 模块加载 - 2 - cjs & esm 不同模块规范

47 阅读2分钟

Node.js 的 cjs(CommonJS)和 esm(ECMAScript Module)是两种不同的模块规范,它们的路径解析规则存在核心差异,本质是为适配不同时期的模块化需求而设计,以下从核心规则、关键区别两方面简要解析:

1. 核心解析规则

(1)CommonJS(cjs)路径解析

cjs 是 Node.js 早期默认的模块规范,主要通过 require() 加载模块,解析逻辑围绕“文件查找优先级”和“便捷性”设计:

  • 优先识别文件/目录
    若传入路径是具体文件名(如 require('./utils.js')),直接查找该文件;若传入目录(如 require('./utils')),会依次查找目录下的 index.js(默认入口)、package.jsonmain 字段指定的文件。
  • 无扩展名时的“猜测”
    若未指定文件扩展名(如 require('./utils')),Node.js 会自动尝试补全 .js.json.node(C++ 扩展模块)三种扩展名,按顺序查找存在的文件。
  • 依赖查找范围
    加载非相对路径模块(如 require('lodash'))时,会从当前文件所在目录的 node_modules 开始向上遍历,直到找到对应模块,或到达系统根目录。

(2)ECMAScript Module(esm)路径解析

esm 是 ES6 标准模块规范,Node.js 从 v14.3.0 起稳定支持,通过 import/export 加载模块,解析逻辑更严格,更贴近浏览器端规范:

  • 必须指定完整路径/扩展名
    不支持“自动补全扩展名”,若加载相对路径模块,必须写全文件名(如 import './utils.js',不能写 ./utils);若加载目录,需通过 package.jsonexports 字段明确指定入口(如 ./utils 需在 exports 中映射到具体文件),不默认查找 index.js
  • 依赖查找的“严格映射”
    加载非相对路径模块(如 import 'lodash')时,同样从 node_modules 查找,但优先读取模块 package.jsonexports 字段(若存在),按字段定义的映射规则加载,而非直接找 main 字段(cjs 优先),避免路径歧义。

2. 关键区别总结

维度CommonJS(cjs)ECMAScript Module(esm)
路径扩展名可省略(自动补全 .js/.json/.node)必须写全(如 ./utils.js,不可省略)
目录入口查找默认找目录下 index.js 或 package.json main需通过 package.json exports 字段指定入口
依赖解析优先级优先用 package.json main 字段优先用 package.json exports 字段
灵活性 vs 严格性更灵活(允许省略扩展名、默认 index)更严格(避免路径歧义,适配浏览器规范)

这种差异的核心原因是:

  1. cjs 是 Node.js 原生设计的“服务器端模块规范”,侧重开发便捷性;
  2. esm 是 ES 标准规范,需兼顾浏览器端和 Node.js 跨环境一致性,因此设计更严格,减少解析不确定性。