关于 Node.js 里 ES6 Modules 的一次更新说明

更新日期: 2017-11-14阅读量: 1837标签: node

几个月前,我写了一篇文章来描述 Node.js 现存的 Commonjs 模块和新的 ES6 模块系统的许多不同,也说明了在 Node.js 内核中实现这个新模型的内在的一些挑战。现在,我想分享一下关于这件事情的进展情况。


明白你什么时候该知道你需要知道的东西

在这之前,如果你还没准备好,你可以花一点时间来看一下我之前的描述这两个模块架构上存在许多根本区别的文章。总结来说就是:CommonJS 与 ES6 Modules 之间的关键不同在于代码什么时候知道一个模块的结构和使用它。

举个栗子,假如我现在有一个简单的 CommonJS 模块(模块名叫'foobar'):

function foo() {
  return 'bar';
}
function bar() {
  return 'foo';
}
module.exports.foo = foo;
module.exports.bar = bar;


现在我们在一个叫 app.js 的 JS 文件中引用它

const {foo, bar} = require('foobar');
console.log(foo(), bar());


当我执行 $node app.js 的时候,Node.js 已二进制的形式加载 app.js 文件,解析它,并且开始执行里面的代码。在执行过程中,里面的 require() 方法被调用,然后它会同步的去加载 foobar.js 的内容进内存,同步的解析编译里面的 JavaScript 代码,同步的执行里面的代码,然后返回 module.exports 的值当做 app.js 里的 require('foobar') 的返回值。当 app.js 里的 require() 方法返回的时候,foobar 模块的结构就已经知道了,并且可以被使用。所有的这些事情都发生在 Node.js 进程事件循环的同一个周期里。

要理解 CommonJS 与 ES6 Modules 之间的不同至关重要的是,一个 CommonJS 的模块在没有被执行完之前,它的结构(API)是不可知的 — 即使在它被执行完以后,它的结构也可以随时被其他代码修改。

现在我们用 ES6 的写法来写同样的模块:

export function foo() {
  return 'bar';
}
export function bar() {
  return 'foo';
}


并且在代码中引用它:

import {foo, bar} from 'foobar';
console.log(foo());
console.log(bar());


从 ECMAScript 统一的标准来看,ES6 Modules 的步骤与 CommonJS 里已经实现的有很大的不同。第一步从硬盘上加载文件内容大致上是相同的,但是可能是异步的。当内容加载完成后,会解析它。在解析的同时,模块里被 export 声明定义的结构会在组件内容被执行之前就探知出来。一旦结构被探知出来,组件的代码就会被执行。这里重要的是记住所有的 import 和 export 语句都会在代码执行之前被解析出来。另一点是在 ES6 中是允许这个解析的步骤异步执行的。这就意味着,在 Node.js 的机制中,加载脚本内容、解析模块的 import 和 export 、执行模块代码将发生在多个事件循环里。


时机很重要

在评估 ES6 Modules 的可实现性之前,我们关注的重点是怎么样无缝衔接的实现它。比如我们希望它可以可以实现同时对两种模块的支持,这样可以很大程度上对用户是透明的。

可惜,事情并不是这么简单…

尤其是 ES6 Modules 的加载、解析和执行都是异步的,这就导致不能通过 require() 来引用一个 ES6 模块。原因是 require() 是一个完全同步的函数。如果我们去修改 require() 的语义让它可以进行异步加载的话,那对于现有的生态系统将会产生巨大的破坏。所以我们有考虑在 ES6 的 import() 函数提议(详情)通过之后建模实现一个 require.import() 函数。这个函数会返回一个 Promise 在 ES6 模块加载完成后标记完成。这不是最好的方案,但是它可以让你在现有的 Node.js 里以 CommonJS 的格式来使用。

有一点好消息是在 ES6 模块里可以很方便地使用 import 来引用一个 CommonJS 模块。因为在 ES6 模块里异步加载不是必须的。ECMAScript 规范进行一些小修改就可以更好地支持这种方式。但是所有这些工作过后,还有一个重要的事情…


命名引用

命名引用是 ES6 Modules 里的一个基本的特性。举个例子:

import {foo, bar} from 'foobar';


变量 foo 和 bar 在解析阶段就从 foobar 中被引用进来 —— 在所有代码被执行之前。因为 ES6 Modules 的结构是之前就可以被探知到的。

另一方面,在 CommonJS 里模块结构在代码没有执行之前是不能被探知的。也就是说,如果不对 ECMAScript 规范做重大更改的话,在 CommonJS 模块里是不能使用命名引用的。开发者会引用到 ES6 Modules 里面的名为 “default” 的导出。比如,上面的例子在 CommonJS 里是这样的:

import foobar from 'foobar';
console.log(foobar.foo(), foobar.bar());


区别很小但是很重要。所以当你想使用 import 来引用一个 CommonJS 模块的时候,下面这种写法是根本行不通的:

import {foo, bar} from 'foobar';


这里的 foo 和 bar 不会直接被解析成 CommonJS 模块里导出的 foo() 和 bar() 方法。


但是在 Babel 里可以!

使用过像 Babel 这种的 ES6 Modules 语法转换工具的人应该很熟悉命名引用。Babel 的工作原理是把 ES6 的写法转换成可以在 Node.js 里运行的 CommonJS 的形式。虽然语法看起来很像 ES6,但是实际上并不是。这一点很重要,Babel 里的 ES6 命名引用与完全按照规范实现的 ES6 命名引用有本质的不同。


Michael Jackson Script

实际上CommonJS 和 ES6 Modules 之间还有另外一个重要的不同就是,ECMAScript 编译器必须提前知道它加载的代码是 CommonJS 的还是 ES6 Modules 的。原因是之前说的 ES6 Modules 必须在代码执行前就解析出模块中的 import 和 export 声明。

这就意味着 Node.js 需要某些机制来预先识别它在加载那种类型的文件。在探索了很多方案以后,我们回归到了以前最糟糕的方案,就是引入一个新的 *.mjs 文件后缀来表示一个 ES6 Modules 的 JavaScript 文件。(之前我们亲切的叫它 “Michael Jackson Script”)


时间线

在目前的时间点上,在 Node.js 可以开始处理支持实现 ES6 Modules 之前,还有很多关于规范现实的问题和虚拟机方面的问题。相关工作还在进行,但是需要一些时间 —— 我们目前估计至少需要一年左右。


原文链接: An Update on ES6 Modules in Node.js 
译文来源:aotu.io
站长推荐

1.云服务推荐: 国内主流云服务商,各类云产品的最新活动,优惠券领取。地址:阿里云腾讯云华为云

链接: https://www.fly63.com/article/detial/104

Node.js v13.2.0 开始支持ES modules了

Node.js 前不久发布了v13.2.0,宣布开始支持ES modules。在此之前,想要在node中使用ES modules,需要添加--experimental-module。v13.2.0版本后,可以直接使用ES modules了。

Node.js中的Stream

Stream主要用于序列化地数据处理(read or write input into output sequentially),比如文件读写,网络数据传输, 或任何端到端的数据交换。Stream在处理数据的时候,与传统方式有所不同

Node.js到底是什么?

接触前端也有一段时间了,逐渐开始接触Node.js,刚刚接触Node.js的时候一直都以为Node.js就是JavaScript,当对Node.js有一定的了解之后,其实并不然两者之间有关系,其中的关系又不是必然的,对Node.js进行的一些了解

Node.js创建服务器和模拟客户端请求

服务器是某种长期运行,等待请求资源的应用程序,Web服务器是使用HTTP协议,等待客户端连接后请求资源的驻守应用程序;HTTP协议是应用层的协议,在传输层依然是使用TCP或者UDP协议

mocha自动化测试程序:node.js测试框架Mocha源码分析

Mocha.js是被广泛使用的Javascript测试框架,在浏览器和Node环境都可以使用。Mocha提供TDD和BDD的测试接口。 Mocha提供了:断言单元测试,可以进行功能性测试,同步代码测试,异步代码测试

node有哪些弊端及优点?

Node 是一个让 JavaScript 运行在服务端的开发平台,它让JavaScript成为与PHP、Python、Perl、Ruby 等服务端语言平起平坐的脚本语言。使用node有很多好处但也存在一些弊端,下面就然我们来看一下nodejs有哪些弊端。

Node.js 前端开发指南

我们经常跟Node.js打交道,即使你是一名前端开发人员 -- npm脚本,webpack配置,gulp任务,程序打包 或 运行测试等。即使你真的不需要深入理解这些任务,但有时候你会感到困惑,会因为缺少Node.js的一些核心概念而以非常奇怪的方式来编码。

Node.js FS模块方法速查

所有文件操作提供同步和异步的两种方式,本笔记只记录异步的API,异步方式其最后一个参数是回调函数。回调函数的第一个参数往往是错误对象,如果没有发生参数,那么第一个参数可能是null或者undefinded。

nodejs做后端的好处有哪些

Node.js是服务器端的 JavaScript 运行环境,它具有无阻塞(non-blocking)和事件驱动(event-driven)等的特色。Node.js 使用了一个事件驱动、非阻塞式 I/O 的模型,使其轻量又高效。

路径 alias 在 Node 中的最佳姿势

根据当前自己已有的知识体系,先想想有哪几种解决方案,然后再去查找业界的解决方案,这样,我们才能不断完善自己的知识体系,培养足够的思维能力。

点击更多...

内容以共享、参考、研究为目的,不存在任何商业目的。其版权属原作者所有,如有侵权或违规,请与小编联系!情况属实本人将予以删除!