如何把 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" 是更现代的选择,允许作者通过多个入口、按环境进行条件入口解析,并阻止访问未在 "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 快速修复将 CommonJS require 转换为 ESM import。

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 生态。

原文:How to convert CommonJS to ESM,Andy Jiang,2024年10月16日,Deno Blog。中文翻译,另加原文纠错和平台核对提示;源码按原文保留。Copyright © Deno Land Inc.,原文及演示权利归原权利人。

© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享
评论 抢沙发

请登录后发表评论

    暂无评论内容