跳到主要内容
Deno 2.4 发布,带来 deno bundle、字节/文本导入、OTel 稳定版等新特性
了解更多
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),您将可以使用顶层 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。一个简单的解决方法是使用一个快速垫片来填充这些值。

// 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 的 require 转换为 ESM 的 import。

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

对于更全面的将 CommonJS 转换为 ESM 的方法,有几种转译选项。有一个 CLI 工具 ts2esm,它可以将 CJS 转换为 ESM,并包含分步说明,甚至还有一个巧妙的视频演练

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

下一步

ESM 是 JavaScript 共享代码的标准方式,所有新的 JavaScript 都应支持它。如今选择支持 CommonJS 对于不愿解决旧版兼容性问题的模块作者和开发者来说可能非常痛苦。事实上,JSR,我们开源的现代 JavaScript 注册表,明确禁止使用 CommonJS 的模块。我们敦促大家尽自己的一份力量,提升 JavaScript 生态系统。

🚨️ 立即试用 Deno 2。 🚨️

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