简单谈谈 Node.js 对 ESM 的几种支持方案

简单谈谈目前 (2017 年 10 月), Node.js 对 ESM (ES6 Module) 的支持情况,并且发表一点自己的看法。

主流的方案

  1. 在文件开头添加 "use module"; 或类似字段
  2. 新的文件后缀名,如 .mjs
  3. 通过源代码内容自动区分
  4. package.json 指明采用 ESM 的文件

这四个方案都已经满足了一些基本要求:

  • 对现有的 package (几乎全是 CJS) 不造成影响
  • 不需要知道 package 采用的是哪种形式就可以直接导入

在这个基础上,第 2 个方案接受的比较多,而且在当前版本 8.7.0 中,可以通过在命令行中增加 --experimental-modules 运行。
比如 node --experimental-modules main.mjs:

1
2
3
import _ from 'ramda'

console.log(_.add(1, 2))

方案一

其实我最初想到的也是这种方案, 因为我以前在其它语言中接触过一些类似的做法。

在一些脚本语言中,会采用类似与 #!foo 这种特殊的注释用于说明执行该脚本的程序,比如 #!/usr/bin/env python3

而在 python2 中,也会在文件的开头注释说明该语言的编码形式: # -*- coding: <encoding name> -*-,比如 # -*- coding: utf-8 -*-

既然其它语言有类似做法,那这种方案对 node 来说也行吧?

虽然这种方式很清晰,不过最后还是被否决了。

其中一个原因就是用户体验太差了。。。因为未来会是 ESM 的天下,既然是 ESM 的天下,那我为什么还要在每个文件开头写 "use modules"; 这样的代码。虽然现在还好,但这在以后势必会显得十分的冗余。

另一个原因是一些工具链为了区分不得不需要有一定的 parse 文件的能力。实现这个的成本明显比检测文件后缀名的成本要高的多。

主要由于以上两点,这个方案被否决了。

方案二

比起另外的方案,采用新的后缀 .mjs, 这个方案也算一个对程序员友好的方案。因为对于每个文件,只需多一个字母 m 在后缀名上 (.js -> .mjs) 即可采用 ESM

同时,不论从实现 node 解释器本身或者相关工具链角度来说,这个方案实现很容易就实现。

至于缺点,同样也是检测后缀名,很多相关工具仅仅把 .js 认为 JavaScript 文件,遇到 .mjs 可能就不认识了。

不过考虑到最近 JavaScript 相关后缀名不断增多,比如近几年出现的 .jsx, .ts。再增加一个 .mjs 似乎也没什么问题吧。ヘ( ̄ー ̄ヘ)

另外,为什么采用 .mjs 而不是 .es, .m.js 呢?据了解,在可能想到的后缀名里,.mjs 和现有其它软件的后缀名的冲突最小。

方案三

这个方案最直接了,如果可以。我当然愿意接受这种方案:全部交给 node 自动根据源码判断,比如有 import 就认为是 ESM,有 require 就认为是 CJS

不过这个实现起来却非常困难。因为 node 源码开发者们不得不对现有 parsing API 进行更改,改进。这个的改动量是巨大的。而且,就算实现了,因为每次都需要对源码内容解析,可能会对性能产生潜在的影响,尤其是在分析大文件的时候。所以由于这个明显的缺点,这个方案也被否决了。

方案四

这个方案的好处,就是不用对采用 ESM 的文件做任何修改就能辨认出是 ESM

不过说实话,这个一听就对开发者有点不友好。要把所有 ESM 都指明,似乎有点累呀。

比如这样子:

1
2
3
4
{
"module": "lib/index.js",
"main": "old/index.js",
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
{
// ...
// files:
"modules": ["lib/hello.js", "bin/hello.js"],

// directories:
"modules": ["lib", "bin"],

// files and directories:
"modules": ["lib", "bin", "special.js"],

// if package never uses CJS Modules
"modules": ["."],
}

另一个比较实在的问题,就是如果要运行 ESM 的文件的话,那岂不是要把 js 文件和 package.json 一起使用?也就是说,单个 ESM 文件是不是不能运行。

同样这个对现有的某些工具链不友好,需要阅读 package.json 才能确定是不是 ESM

总结

总的而言,虽然方案二有些缺点,但确实是方案二更优一点。另外,有一篇对方案四的正名文章,值得读一读。

参考

  1. ES6 Module Detection in Node
  2. Module specifiers: what’s new with ES modules?
  3. node-eps
  4. defense-of-dot-js