作者:@Andy Jiang
原文:https://deno.com/blog/convert-cjs-to-esm
背景
Deno 2 正式发布,这是一个现代的 JavaScript 和 TypeScript 运行时环境。与此同时,Deno 团队在其官方博客上发布了一篇关于如何将 CommonJS 模块转换为 ECMAScript 模块(ESM)的指南。这篇文章详细介绍了从 CommonJS 迁移到 ESM 的步骤和工具,旨在帮助开发者更好地适应现代 JavaScript 生态系统。
为什么要将 CommonJS 代码转换为 ESM?
ESM(ECMAScript 模块)是编写和共享 JavaScript 的官方现代标准。与传统的 CommonJS 相比,ESM 拥有以下优势:
更广泛的支持: ESM 被许多环境支持,例如浏览器、边缘计算和现代运行时(如 Deno)。
更好的开发体验: ESM 支持异步加载和无全局变量导出,提供更流畅的开发流程。
面向未来:为了与新软件包兼容,旧代码库需要现代化。ESM 是 JavaScript 的未来,所有新代码都应该使用 ESM 编写以确保其未来的适用性。
要点
CommonJS 到 ESM 的转换:随着 ESM 成为 JavaScript 的官方标准,越来越多的环境(如浏览器、边缘计算和现代运行时如 Deno)支持 ESM。因此,将现有的 CommonJS 项目转换为 ESM 是必要的。
转换步骤:文章详细介绍了如何更新模块的导入和导出语法、调整
package.json
文件、处理其他必要的代码变更,以及使用工具辅助转换。工具支持:VSCode 提供了快速修复功能,可以自动将 CommonJS 的
require
语句转换为 ESM 的import
语句。此外,Deno 的 linter 提供了no-sloppy-imports
规则,帮助确保导入路径包含文件扩展名。
分析
模块导入和导出语法的变化
1、导出:从 module.exports = { addNumbers }
转换为 export function addNumbers(num1, num2) { ... }
。
2、导入:从 const addNumbers = require("./add_numbers")
转换为 import { addNumbers } from "./add_numbers.js"
。
如何处理条件导入?
如果使用的是 Node.js v14.8 或更高版本(或 Deno),则可以使用顶级 await 使 import 同步:
- const module = boolean ? require("module1") : require("module2");
+ const module = await (boolean ? import("module1") : import("module2"));
3、文件扩展名:ESM 要求在导入路径中明确包含文件扩展名,这有助于减少歧义并提高代码的可维护性。
注意:在 ESM 中,模块路径必须包含文件扩展名。完全指定的导入可以减少歧义,确保模块解析过程始终导入正确的文件。此外,它与浏览器处理模块导入的方式一致,使编写可预测和易于维护的同构代码更加容易。
package.json
的调整
添加
"type": "module"
以支持 ESM。使用
"exports"
字段替代"main"
,以定义更清晰的包接口,支持多个入口点和条件解析。
其他代码变更
1、移除 "use strict";
,因为 ESM 默认启用严格模式。
{
"name": "my-project",
+ "type": "module",
- "main": "index.js",
+ "exports": "./index.js",
// ...
}
注意:在 ESM 中,
./
前缀是必需的,因为每个引用都必须使用完整路径名,包括目录和文件扩展名。
此外, exports 是 main 的现代替代方案,它允许作者通过允许多个入口点、支持环境之间的条件入口解析以及防止在 exports 中定义的入口点之外的其他入口点来明确定义其包的公共接口。
2、处理 CommonJS 特有的全局变量(如 __dirname
和 __filename
),使用 import.meta
替代。
如何处理 CommonJS 中的全局变量?
由于 ESM 中的 JavaScript 会自动在严格模式下运行,因此您可以从代码库中删除所有 "use strict"; 实例。
对于 __dirname
和 __filename
等 CommonJS 支持的内置全局变量,可以使用简单的填充代码来填充这些值:
// Node 20.11.0+, Deno 1.40.0+
const __dirname = import.meta.dirname;
const __filename = import.meta.filename;
// 以前
const __dirname = new URL(".", import.meta.url).pathname;
import { fileURLToPath } from "node:url";
const __filename = fileURLToPath(import.meta.url);
工具辅助
VSCode: 可以快速将所有导入和导出语句从 CommonJS 转换为 ESM。将鼠标悬停在 require 关键字上,点击 “快速修复”,该文件中的所有语句都将更新为 ESM。
Deno lint: 可以通过运行
deno lint --fix
快速添加文件名扩展名。Deno 的 linter 带有no-sloppy-imports
规则,当导入路径不包含文件扩展名时,会显示 linting 错误。cjs2esm
和cjstoesm
: 一些 npm 包可以帮助进行转换,但可能没有得到积极维护,并且功能不完整。Babel 插件
babel-plugin-transform-commonjs
: 也是一个转换选项,但需要注意其维护和功能完整性。
为什么 JSR 禁止使用 CommonJS 模块?
JSR(JavaScript Registry)是一个开源的现代 JavaScript 注册表,它明确禁止使用 CommonJS 模块。这是因为支持 CommonJS 会给模块作者和不想解决遗留兼容性问题的开发人员带来极大的痛苦。JSR 鼓励每个人都为提升 JavaScript 生态系统做出贡献。
Deno 在支持 ESM 方面有哪些优势?
Deno 是一个 “内置电池” 的 JavaScript 开发一体化工具链,具有原生 TypeScript 和 Web 标准 API 支持。它提供了与 Node/npm 的向后兼容性、内置包管理、一体化零配置工具链、原生 TypeScript 支持等功能,使其成为现代 JavaScript 开发的绝佳选择。
影响
生态系统标准化:ESM 作为 JavaScript 的官方标准,将推动整个生态系统向更现代、更高效的方向发展。
开发者体验提升:ESM 提供了更好的开发体验,如异步加载和避免全局变量,这将提高代码的可读性和可维护性。
工具链整合:Deno 作为一个 “开箱即用” 的工具链,提供了内置的 TypeScript 支持和 Web 标准 API,将进一步简化开发流程。
结论
随着 Deno 2 的发布和 ESM 的普及,JavaScript 生态系统正在加速向现代化转型。开发者应积极采用 ESM,并利用现有工具和指南进行代码迁移。这不仅有助于提升开发效率和代码质量,还能确保项目在未来保持兼容性和可扩展性。未来,随着更多工具和框架对 ESM 的支持,JavaScript 生态系统将变得更加统一和高效。
AI 阅:了解技术资讯的一种方式。