❝
原文链接:All you need to know to move from CommonJS to ECMAScript Modules (ESM) in Node.js[1],2021.05.05,by Paweł Grzybek
❞
模块(ESM)是 ECMAScript 2015 规范引入的最具革命性的功能之一。第一个浏览器实现是在 2017 年 4 月发布的 Safari 10.1。我发表了一篇关于这个历史时刻的文章《Native ECMAScript modules in the browser》[2]。几个月后,在 2017 年 9 月,Node v8.5.0[3] 增加了 ESM 的实验性支持。
这个特性在实验阶段经历了大量的迭代。几年后,在 2020 年 4 月 Node v14.0.0[4] 移除了实验警告后开始正常使用,并在 Node v14.17.0[5] 版本中模块实现标记为稳定。

历史已经讲得够多了,所以让我们开始动手,深入了解 Node.js 中的 ECMAScript 模块。我们有很多东西要讲。
在 Node.js 中启用 ECMAScript 模块(ESM)
为了保持向后兼容性,Node.js 默认 JavaScript 代码使用 CommonJS 模块语法组织。要启用 ESM,有三种方式:
-
使用 .mjs
扩展(花名迈克尔·杰克逊模块(Michel’s Jackson’s modules)) -
package.json
文件添加"type": "module"
字段 -
使用命令行参数 –input-type=module[6]: node --input-type=module --eval "import { sep } from 'node:path'; console.log(sep);"
语法
ECMAScript 模块引入了新的语法。下面来看看用 CommonJS 编写的示例以及对应的 ESM 等效代码。
// util.js
module.exports.logger = (msg) => console.log(`👌 ${msg}`);
// index.js
const { logger } = require("./util");
logger("CommonJS");
// 👌 CommonJS
// util.js
const logger = (msg) => console.log(`👌 ${msg}`);
export { logger };
// index.js
import { logger } from "./util.js";
logger("ECMAScript modules");
// 👌 ECMAScript modules
ESM 还是有一些语法要学习的,Node.js 也是按照官方的 ESCMAScript 模块语法[7]来实现的。这里不多赘述,留给各位同学私下学习。另外,加载 ESM 时需要明确指定文件扩展名的(.js
或 .mjs
),这同样适用于目录索引的场景(例如 ./routes/index.js
)。
默认严格模式
ECMAScript 模块代码默认在严格模式(use strict)[8]下运行,避免松散模式(sloppy mode)[9]下的潜在的 BUG。
浏览器兼容性
由于 Node.js 和浏览器中的 ESM 实现遵循的是同一个官方规范,因此我们可以在服务器和客户端运行时之间共享代码。在我看来,统一的语法是使用 ESM 最吸引人的好处之一。
<srcipt type="module" src="./index.js"> </srcipt>
Sindre Sorhus[10] 的“Get Ready For ESM”[11]深入讨论了统一语法的其他好处,并鼓励包创建者转向 ESM 格式。我是再赞同不过了。
ESM 中缺少一些在 CommonJS 中存在的变量
ECMAScript 模块在运行时会缺少一些在 CommonJS 中存在的变量:
-
exports
-
module
-
__filename
-
__dirname
-
require
console.log(exports);
// ReferenceError: exports is not defined
console.log(module);
// ReferenceError: module is not defined
console.log(__filename);
// ReferenceError: __filename is not defined
console.log(__dirname);
// ReferenceError: __dirname is not defined
console.log(require);
// ReferenceError: require is not defined
其实在使用 ESM 时,exports
和 module
不再需要了 。另外,其它变量我们也能额外创建。
// Recreate missing reference to __filename and __dirname
import { fileURLToPath } from "url";
import { dirname } from "path";
const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename);
console.log(__dirname);
console.log(__filename);
// Recreate missing reference to require
import { createRequire } from "module";
const require = createRequire(import.meta.url);
this
关键字的行为
值得一提的是, 两种模块语法中,this
关键字的行为在全局范围内有所不同。ESM 中, this
是 undefine
,但在 CommonJS 中, this
关键字指向 exports
。
// this keyword in ESM
console.log(this);
// undefined
// this keyword in CommonJS
console.log(this === module.exports);
// true
CommonJS 的动态解析到 ESM 的静态解析
CommonJS 模块在执行阶段被动态解析。这个特性就允许块作用域内使用 require
函数(例如在 if
语句中),因为依赖关系图是在程序执行期间才构建的。
ECMAScript 模块要复杂得多——在实际运行代码之前,解释器会构建一个依赖图,然后再去执行程序。预定义的依赖关系图可以让引擎执行优化,例如 tree shaking(死代码消除)等。
ESM 的顶层 await
支持
Node.js 在版本 14 中启用了对顶级 await
的支持。这稍微改变了依赖图规则,使模块像一个大的 async
函数一样。
import { promises as fs } from "fs";
// Look ma, no async function wrapper!
console.log(JSON.parse(await fs.readFile("./package.json")).type);
// module
导入 JSON
导入 JSON 是 CommonJS 中常用的功能。但在 ESM 导入 JSON 会抛出错误,我们可以通过重新创建 require
函数来克服这个限制。
import data from "./data.json";
// TypeError [ERR_UNKNOWN_FILE_EXTENSION]: Unknown file extension ".json"
import { createRequire } from "module";
const require = createRequire(import.meta.url);
const data = require("./data.json");
console.log(data);
// {"that": "works"}
拥抱 ESM 的最佳时机就是现在
我希望这篇文章能帮助你理解 Node.js 中 CommonJS 和 ECMAScript 模块之间的区别。我期待着有一天我们不再需要在意这些差异。整个生态系统将根据 ECMAScript 规范工作,而不管运行时(客户端或服务器)。如果你还没有,我强烈建议你现在就加入 ESM 阵营,为一致和统一的 JavaScript 生态系统做出贡献。
References
All you need to know to move from CommonJS to ECMAScript Modules (ESM) in Node.js: https://pawelgrzybek.com/all-you-need-to-know-to-move-from-commonjs-to-ecmascript-modules-esm-in-node-js/
[2]
《Native ECMAScript modules in the browser》: https://pawelgrzybek.com/native-ecmascript-modules-in-the-browser/
[3]
Node v8.5.0: https://nodejs.org/en/blog/release/v8.5.0/
[4]
Node v14.0.0: https://nodejs.org/en/blog/release/v14.0.0/
[5]
Node v14.17.0: https://nodejs.org/en/blog/release/v14.17.0
[6]
命令行参数 –input-type=module: https://nodejs.org/api/packages.html#–input-type-flag
[7]
官方的 ESCMAScript 模块语法: https://tc39.es/ecma262/#sec-modules
[8]
严格模式(use strict): https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Strict_mode
[9]
松散模式(sloppy mode): https://developer.mozilla.org/en-US/docs/Glossary/Sloppy_mode
[10]
Sindre Sorhus: https://twitter.com/sindresorhus
[11]
“Get Ready For ESM”: https://blog.sindresorhus.com/get-ready-for-esm-aa53530b3f77
原文始发于微信公众号(写代码的宝哥):在 Node.js 中从 CommonJS 迁移到 ECMAScript 模块(ESM)
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/243840.html