[译] CommonJS 转换为 ESM
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"
是 "main"
的现代替代品,因为它允许多个入口点,支持环境间有条件的入口解析,并防止 "exports"
定义之外的其他入口点,从而使作者能够明确定义其软件包的公共接口。
{
"name": "my-project",
"type": "module",
"exports": {
".": "./index.js",
"./other": "./other.js"
}
}
最后,告诉 Node 在 ESM 中运行文件的另一种方法是使用 .mjs
文件扩展名。 如果你想将单个文件更新为 ESM,这种方法就很不错。 但如果你的目标是转换整个代码库,更新 package.json
中的 type
会更容易。
其他变更
由于 ESM 中的 JavaScript 会自动以 严格模式 运行,因此你可以从代码库中删除所有 "use strict;"
实例:
"use strict";
CommonJS 还支持一些在 ESM 中不存在的内置全局项,如 __dirname
和 __filename
等。 解决这个问题的一个简单方法是使用快速 shim 来填充这些值:
// 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 的所有导入和导出语句转换为 ESM。 只需将鼠标悬停在 require
关键字上,点击 "快速修复",该文件中的所有语句都将更新为 ESM:
你会注意到,VSCode 可以为导入和导出交换适当的关键字,但指定符缺少文件名扩展名。 你可以通过运行 deno lint --fix
快速添加它们。 Deno 的 linter 自带了 no-sloppy-import
规则,当导入路径不包含文件扩展名时,它会显示 linting 错误。
要想以更端到端的方式将 CommonJS 转换为 ESM,有几种转译选项可供选择。 有一种 CLI 工具 ts2esm 可以将 CJS 转换为 ESM,其中包括 分步说明,甚至还有 一段有趣的视频演示。
还有一些工具,如 cjs2esm 和 cjstoesm,以及 Babel 插件 babel-plugin-transform-commonjs,不过这些工具并没有得到积极维护,因此在评估它们时要注意这一点。
下一步
ESM 是共享代码的标准 JavaScript 方式,所有新 JavaScript 都应支持它。 如今选择支持 CommonJS 可能会让模块作者和开发人员感到非常痛苦,因为他们不想再为遗留兼容性问题排忧解难。 事实上,我们的开源现代 JavaScript 注册表 JSR 明确禁止模块使用 CommonJS。 我们呼吁大家为提升 JavaScript 生态系统的水平尽一份力。