ECMAScript 模块(ESM)是编写和共享 JavaScript 的官方现代方式。浏览器、边缘环境以及 Deno 等现代运行时都支持它,并提供更好的开发体验,例如异步加载,以及不借助全局变量进行导出。虽然 CommonJS 多年来一直是标准,但如今继续支持 CommonJS 正在伤害 JavaScript 社区。
为了面向未来,所有新 JavaScript 都应使用 ESM。不过,旧代码库往往需要现代化改造,才能兼容更新的软件包。本文介绍如何将旧 CommonJS 项目的语法迁移为支持 ESM 的形式,以及帮助迁移顺利进行的工具。
模块导入与导出
可以用以下方式把 CommonJS 导入、导出语法改成 ESM。先看导出端:
- function addNumbers(num1, num2) {
+ export function addNumbers(num1, num2) {
return num1 + num2;
};
- module.exports = {
- addNumbers,
- }
再看导入端:
- const { addNumbers } = require("./add_numbers");
+ import { addNumbers } from "./add_numbers.js");
console.log(addNumbers(2, 2));
在 ESM 中,模块路径必须包含文件扩展名。完整指定的导入能够消除歧义,确保模块解析始终导入正确文件。它也与浏览器处理模块导入的方式一致,让同构代码更可预测、更容易维护。
条件导入怎么办?Node.js v14.8 或更新版本,以及 Deno,支持顶层 await;可以用它等待 import 完成:
- const module = boolean ? require("module1") : require("module2");
+ const module = await (boolean ? import("module1") : import("module2"));
更新 package.json
如果项目使用 package.json,需要作以下调整以支持 ESM:
{
"name": "my-project",
+ "type": "module",
- "main": "index.js",
+ "exports": "./index.js",
// ...
}
注意前导 "./":ESM 中的路径引用需要完整写出目录和文件扩展名。
"main" 与 "exports" 都用于定义项目入口。"exports" 是更现代的选择,允许作者通过多个入口、按环境进行条件入口解析,并阻止访问未在 "exports" 中声明的其他入口,从而清晰定义包的公共接口。
{
"name": "my-project",
"type": "module",
"exports": {
".": "./index.js",
"./other": "./other.js"
}
}
让 Node 按 ESM 执行文件的另一种方式是使用 .mjs 扩展名。只迁移单个文件时很合适;如果要转换整个代码库,修改 package.json 中的 type 更方便。
其他变化
ESM 内的 JavaScript 自动以严格模式运行,因此可以删除代码库中所有 "use strict";:
- "use strict";
CommonJS 还提供了一些 ESM 中不存在的内置全局变量,例如 __dirname、__filename。一种简单做法是使用下面的兼容代码取得这些值:
// Node 20.11.0+, Deno 1.40.0+
const __dirname = import.meta.dirname;
const __filename = import.meta.filename;
// Previously
const __dirname = new URL(".", import.meta.url).pathname;
import { fileURLToPath } from "node:url";
const __filename = fileURLToPath(import.meta.url);
迁移工具
以上介绍了将 CommonJS 代码库转换为 ESM 所需的变化,还可以使用工具辅助迁移。
VSCode 可以快速转换所有 CommonJS 导入与导出语句:悬停在 require 关键字上,选择“快速修复”,文件中的这些语句就会更新为 ESM:
VSCode 能替换导入和导出的关键字,但模块说明符可能仍缺少文件扩展名。运行 deno lint --fix 可以快速补全。Deno 的 linter 提供 no-sloppy-imports 规则,在导入路径未包含扩展名时报告 lint 错误。
如果希望更完整地自动转换 CommonJS 为 ESM,可以考虑转译工具。CLI 工具 ts2esm 提供 CJS 到 ESM 转换,并包含逐步指南和视频演示。
另有 cjs2esm、cjstoesm,以及 Babel 插件 babel-plugin-transform-commonjs。原文提醒,这些工具并未积极维护,评估时应考虑这一点。
接下来
ESM 是 JavaScript 共享代码的标准方式,所有新 JavaScript 都应支持它。继续支持 CommonJS 会让模块作者,以及不愿为旧兼容问题排障的开发者承受很大成本。Deno 的开源现代 JavaScript 注册表 JSR 明确禁止使用 CommonJS 的模块。作者呼吁大家共同提升 JavaScript 生态。











暂无评论内容