共计 4929 个字符,预计需要花费 13 分钟才能阅读完成。
作者:Brian De Sousa
翻译:疯狂的技术宅
原文:https://blog.logrocket.com/es…
未经允许严禁转载
多年来,在 JavaScript 生态中出现了不同形式的模块化方案。开发人员使用了明确定义的规范(如 AMD 或 CommonJS)以及简单的编码模式(如通过揭示模块模式(revealing module pattern))来得到模块化解决方案的好处。
模块可以在浏览器的客户端使用,也可以在 Node.js 的服务器端使用。有时也使用像 Babel 这样的工具将代码从一种模块格式转换为另一种格式。所有这些都使得混乱的 JavaScript 模块状态变得更加混乱。
提示:本文重点介绍 Node.js 中的 ES 模块。你可以通过查看“CommonJS vs AMD vs RequireJS vs ES6 Modules”来更好的比较模块系统。
一段简短的历史
先让我们来看看 ES 模块支持的关键里程碑:
- 2015 年 6 月:ES 模块在 ECMAScript 的 ES2015 规范中定义。
- 2015 年 6 月 – 2017 年 9 月:主要浏览器为隐藏在开发者标志后面的 ES 模块添加实验性支持。用 ES 模块开发 JavaScript 的主要方法是通过像 Babel 这样的工具来转换代码。
- 2017 年 9 月:Node.js v8.5 包含 ES 模块的实验性支持。
-
2017 年 9 月 – 2018 年 5 月:主要浏览器开始支持 ES 模块规范,没有开发者标志,包括:
- Chrome 61,2017 年 9 月 5 日
- Safari 11,2017 年 9 月 18 日
- Firefox 60,2018 年 5 月 8 日
-
2018 年 10 月:创建了一个新的模块实施计划。该计划包括几个阶段,从一开始就遵循三个指导原则,用新的实施方案取代当前的实验性实施方案:
- 遵守 ES 规范
- Node 应尽可能的以和浏览器相同的方式执行操作
- 不破坏现有的 CommonJS 模块
提示:Node.js 模块团队为新实现提供了更详细的指导原则。
- 2019 年 10 月(暂定):预计 Node 12 将会获得长期支持。根据官方计划,其目标是在此时发布对 ES 模块的完全支持。
为什么完整的 ES 模块支持里程碑对 Node.js 如此重要?
有几个原因。首先所有主流浏览器都支持 ES 模块 —— 你可以自己到这里去查看。在 Node.js 中支持服务器端的 ES 模块开箱即用,将能够允许全栈开发人员非常自然地为客户端和服务器编写模块化、可重用的 JavaScript 代码。
另外,Node.js 中的实验性功能在未来版本中会受到非向后兼容的修改或删除。话虽如此,实验性的 ES 模块支持已经在 Node 中使用了好几年,并且预计在 2019 年 10 月之前不会发生显着变化。
Node.js 模块的当前状态
CommonJS 模块
目前(撰写本文时的 2019 年 7 月)Node.js 中模块的事实标准是 CommonJS。CommonJS 模块在普通的 .js 文件中用 module.exports
进行定义,然后可以用 require()
函数在其他 .js 文件中使用。例如:
// foo.js
module.exports = function() {return 'Hello foo!';}
// index.js
var foo = require('./foo');
console.log(foo()); // Hello foo!
可以使用 node index.js
命令运行此示例。
ES 模块
从 Node v8.5 开始,开发人员已经能够使用 --experimental-modules
标志运行对 ES 模块规范的各种支持了。从 Node v12.4 开始,模块可以在 .mjs 文件中定义(或在某些情况下在.js 文件中)。例如:
// foo.mjs
export function foo() {return 'Hello foo!';}
// index.mjs
import {foo} from './foo.mjs';
console.log(foo()); // Hello foo!
用 node --experimental-modules index.mjs
命令运行此示例。
在同一个应用中同时使用 CommonJS 和 ES 模块
在某些方面,在浏览器中支持 ES 模块可能比在 Node 中更简单,因为 Node 已经有了一个定义良好的 CommonJS 模块系统。幸运的是,开发人员可以同时使用这两种模块,甚至从一种模块导入到另一种模块。社区在在这方面做得非常出色。
假设我们有两个模块。第一个是 CommonJS 模块,第二个是 ES 模块(注意不同的文件扩展名):
// cjs-module-a.js
module.exports = function() {return 'I am CJS module A';};
// esm-module-a.mjs
export function esmModuleA() {return 'I am ESM Module A';};
export default esmModuleA;
在 ES 模块脚本中使用 CommonJS 模块(请注意 .mjs 扩展名和使用 import
关键字):
// index.mjs
import esmModuleA from './esm-module-a.mjs';
import cjsModuleA from './cjs-module-a.js';
console.log(`esmModuleA loaded from an ES Module: ${esmModuleA()}`);
console.log(`cjsModuleA loaded from an ES Module: ${cjsModuleA()}`);
用 node --experimental-modules index.mjs
命令运行此示例。
在标准的 CommonJS 脚本中使用 ES 模块(注意 .js 扩展名和使用 require()
函数):
// index.js
// synchronously load CommonJS module
const cjsModuleA = require('./cjs-module-a');
console.log(`cjsModuleA loaded synchronously from an CJS Module: ${cjsModuleA()}`);
// asynchronously load ES module using CommonJS
async function main() {const {esmModuleA} = await import('./esm-module-a.mjs');
console.log(`esmModuleA loaded asynchronously from a CJS module: ${esmModuleA()}`);
}
main();
这些例子提供了如何在同一个程序中同时使用 CommonJS 和 ES 模块的基本演示。你可以查看 Gil Tayar 在“NodeJS 中的原生 ES 模块:状态和未来方向,第一部分“中深入探讨的 CommonJS 和 ES 模块的互操作性。
Node.js 模块的未来状态
在撰写本文时,新模块的实施计划正处于第三和最后阶段。计划在 Node 12 LTS 发布的同时完成阶段 3,并且在没有 -experimental-modules
标志的情况下可以使用 ES 模块支持。
第 3 阶段可能会带来一些重大改进,以完善 ES 模块的实现。
加载方案
开发人员希望模块加载系统具有灵活性和全功能。以下是 Node.js 模块加载器解决方案中的一些关键功能:
- 代码覆盖 / 检测:使开发人员工具能够检索有关 CJS 和 ESM 模块使用情况的数据。
- 可插入加载器:允许开发人员在他们的包中包含加载程序插件,这些插件可以定义从特定文件扩展名或 mimetypes 加载模块的新行为,甚至是没有扩展名的文件。
- 运行时加载器:允许在运行时转换 import 语句中引用的文件。
- 模块的任意来源:允许从文件系统以外的源加载模块(例如,从 URL 加载模块)。
- 模拟模块:允许在测试时用模拟模块替换。
你可以查看这里的完整列表。
package.json 中的 exports 象
虽然命名和语法不是最终的方案,但这里的想法是在 package.json
文件中的某个地方有一个对象,它允许为包中的不同组件提供“漂亮”的入口点。这个 package.json
是可能的一个实现:
{
"name": "@myorg/mypackage",
"version": "1.0.0",
"type": "module",
"main": "./dist/index.js",
"exports": {
".": "./src/mypackage.mjs",
"./data": "./data/somedir/someotherdir/index.mjs"
}
}
开发人员可以像这样导入 @myorg/mypackage
的数据组件:
import {MyModule} from '@myorg/mypackage/data
用包的名称引用包的根
模块引用当同一个包中的另一个模块时,可能会出现大量的回溯,如下所示:
import coolcomponent from '../../../coolcomponent/module.js
如果实现了这种方案,那么就可以用在 package.json
中定义的包名的引用来替换回溯。新代码如下所示:
import coolcomponent from 'mypackage/coolcomponent/module.js
支持双 ESM/CommonJS 包
允许 npm 包同时包含 CJS 和 ES 模块对于确保从 CommonJS 迁移到 ES 模块后能够保持向后兼容,对开发人员友好的路径非常重要。这通常被称为“双模式”支持。
双模式支持的现状方法是将 package.json
中的现有 main
入口点指向 CommonJS 的入口点。如果 npm 包中包含 ES 模块并且开发人员想要使用它们,则需要使用深度导入来访问这些模块(例如 import'pkg/module.mjs'
)。这是双模式解决方案,可能与 Node.js 12 LTS 一起提供。
还有其他一些双模式支持提案。这个广泛争议的提案中包含一些选项,使开发人员可以更轻松地发送包含两个单独实现(ESM 和 CJS)的包,但此提议未能达成共识。
一个更新的 ESM 的 require
提案提出了一种不同的方法,允许开发人员使用 require()
来解析 ES 模块。该提案仍然是开放的,但是已经沉默了,不太可能包含在 Node 12 LTS 中。
ES 模块你好,CommonJS 再见?
虽然目标是 ES 模块最终取代 Node.js 中的 CommonJS 模块,但没人知道未来究竟会怎样 —— 也不知道 CommonJS 模块支持消失的时间。但有一件事是肯定的:Node 开发人员花费了大量的时间和精力确保在没有 CommonJS 的情况下无缝过渡到未来。
他们在确保两种模块类型彼此互操作之间取得平衡,同时尽量不引入太多新的双模式 API,因为一旦迁移之后就会变得无用,并且需要时间来消除 Node 对 CommonJS 的支持。
那什么时候才会从 Node.js 中删除 CommonJS 呢?让我们做一个大胆而且毫无根据的预测:Node 18 会带有 --experimental-no-commonjs-modules
,并且 Node 20 会彻底抛弃 CommonJS。
能够跨浏览器、服务器和其他 JavaScript 运行环境的模块化 JavaScript 的未来是令人兴奋的!
本文首发微信公众号:前端先锋
欢迎扫描二维码关注公众号,每天都给你推送新鲜的前端技术文章
欢迎继续阅读本专栏其它高赞文章:
- 深入理解 Shadow DOM v1
- 一步步教你用 WebVR 实现虚拟现实游戏
- 13 个帮你提高开发效率的现代 CSS 框架
- 快速上手 BootstrapVue
- JavaScript 引擎是如何工作的?从调用栈到 Promise 你需要知道的一切
- WebSocket 实战:在 Node 和 React 之间进行实时通信
- 关于 Git 的 20 个面试题
- 深入解析 Node.js 的 console.log
- Node.js 究竟是什么?
- 30 分钟用 Node.js 构建一个 API 服务器
- Javascript 的对象拷贝
- 程序员 30 岁前月薪达不到 30K,该何去何从
- 14 个最好的 JavaScript 数据可视化库
- 8 个给前端的顶级 VS Code 扩展插件
- Node.js 多线程完全指南
- 把 HTML 转成 PDF 的 4 个方案及实现
- 更多文章 …