Node.js ES Module 为什么必须加文件扩展名
解释 Node.js 中 ES Module 需要完整路径和扩展名的原因
问题
为什么 Node.js 在使用 ES Module 时必须加上文件扩展名?
解答
这个问题涉及两个方面:如何识别 ES Module,以及为什么需要完整路径。
无法从语法上区分 ES Module
TC39 在设计 ES Module 时,无法从语法上严格区分一段代码是 ES Module 还是传统 Script(CommonJS 本质上仍是传统 Script)。
虽然从开发者角度看,有 import/export 语句就是 ES Module,但没有这些语句也不代表就不是 ES Module。Node 社区曾在 TC39 提出过几种方案:
方案 1:引入 “use module” 指令
"use module";
// 模块代码
优点是容易理解和实现,缺点是对已有 export 语句的模块显得多余。
方案 2:通过 export 语句判断
对于不需要导出的模块,使用 export {} 标记:
// 没有实际导出,但标记为 ES Module
export {};
优点是大多数模块不需要额外标记,缺点是解析器需要预扫描整个文件寻找 export 语句。TypeScript 就是使用这种方案。
方案 3:引入新语法标记
这些方案在 TC39 都未通过,将来也不太可能引入。
通过外部信息识别
既然无法从代码内容判断,就需要外部信息:
- Web 平台:通过
<script type="module">标明,或在其他 API 中指定,如new Worker(path, {type: 'module'}) - Node.js:通过文件扩展名
.mjs或 package.json 中的"type": "module"字段标明
为什么需要完整路径
Node.js 的 CommonJS 模块有复杂的 fallback 机制。对于 require('./my-module'),会依次查找:
my-module文件(无扩展名)my-module.js文件my-module/index.js文件
这种机制基于文件系统访问成本低的假设。但在浏览器端,这会导致多次网络请求,成本无法接受。因此浏览器端的 import 语句使用标准 URL,通常包含完整扩展名:
// 浏览器端
import MyModule from './my-module.js';
Node.js 加入 ES Module 时需要考虑与浏览器的一致性,因此也要求完整路径。
浏览器与 Node.js 的差异
浏览器端:import "./file.js" 总是将 file.js 作为 ES Module 解析。
Node.js:根据外部信息解析。.js 后缀默认按 CommonJS 解析,除非 package.json 设置了 "type": "module"。
{
"type": "module"
}
此外,浏览器端不能直接使用裸名字导入:
// 需要通过 import maps 定义
import "my-module"; // 不能直接使用
Node.js 虽然支持裸名字导入,但也加入了类似 import maps 的机制。
关键点
- TC39 无法从语法上区分 ES Module 和传统 Script,需要外部信息(扩展名或配置)来标识
- 浏览器端 import 使用标准 URL,避免多次网络请求,Node.js 为保持一致性也要求完整路径
- Web 平台通过
<script type="module">标识,Node.js 通过.mjs扩展名或 package.json 的"type": "module"标识 - Node.js 中
.js文件默认按 CommonJS 解析,除非明确指定为 module 类型
目录