跳至主要内容
Deno 2 终于发布了 🎉️
了解更多
How to convert CommonJS to ESM

如何将 CommonJS 转换为 ESM

ECMAScript 模块(“ESM”)是编写和共享 JavaScript 的官方现代方式——它在许多环境中得到支持(例如浏览器、边缘和 Deno 等现代运行时),并提供了更好的开发体验(例如异步加载和能够在没有全局变量的情况下导出)。虽然 CommonJS 多年来一直是标准,但支持当今的 CommonJS 正在损害 JavaScript 社区

所有新的 JavaScript 应该用 ESM 编写以进行未来验证。但是,在许多情况下,由于与较新软件包的兼容性原因,需要对旧版代码库进行现代化改造。在这篇博文中,我们将向您展示如何将旧版 CommonJS 项目的语法迁移到支持 ESM 的语法,以及帮助您顺利完成此过程的工具。

想要编写现代 JavaScript 和 TypeScript,无需繁琐的配置或样板代码?

查看Deno,一个“包含电池的”,默认安全的一体式工具链,用于 JavaScript 开发,具有原生TypeScriptWeb 标准 API 支持。

模块导入和导出

以下是如何将导入和导出语法从 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),那么您将可以使用顶层等待,它可以用于使 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。一个简单的解决方法是使用快速垫片来填充这些值

// 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 提供了一个快速修复,用于将 CommonJS 要求转换为 ESM 导入。

您会注意到,VSCode 可以为导入和导出替换正确的关键字,但说明符中缺少文件名扩展名。您可以通过运行deno lint --fix 来快速添加它们。Deno 的 linter 带有 no-sloppy-imports 规则,当导入路径不包含文件扩展名时,该规则会显示一个 linting 错误。

对于将 CommonJS 转换为 ESM 的端到端方法,有一些 transpilation 选项。有一个 CLI 工具ts2esm,它将 CJS 转换为 ESM,并包含分步指南,甚至一个漂亮的视频演练

还有像cjs2esmcjstoesm 这样的工具,以及 Babel 插件babel-plugin-transform-commonjs,但这些工具没有得到积极维护,因此在评估它们时请牢记这一点。

下一步

ESM 是共享代码的标准 JavaScript 方式,所有新的 JavaScript 都应该支持它。选择今天支持 CommonJS 可能会给模块作者和不想排查旧版兼容性问题的开发人员带来极大的痛苦。事实上,JSR,我们开源的现代 JavaScript 注册表明确禁止使用 CommonJS 的模块。我们敦促每个人尽自己的一份力量提升 JavaScript 生态系统。

🚨️ 立即尝试 Deno 2。 🚨️

Deno 提供了与 Node/npm 的向后兼容性、内置的包管理、一体式零配置工具链原生 TypeScript 支持 等等。