简单谈谈目前 (2017 年 10 月), Node.js 对 ESM (ES6 Module) 的支持情况,并且发表一点自己的看法。
主流的方案
- 在文件开头添加
"use module";
或类似字段 - 新的文件后缀名,如
.mjs
- 通过源代码内容自动区分
- 在
package.json
指明采用ESM
的文件
这四个方案都已经满足了一些基本要求:
- 对现有的
package
(几乎全是 CJS) 不造成影响 - 不需要知道
package
采用的是哪种形式就可以直接导入
在这个基础上,第 2 个方案接受的比较多,而且在当前版本 8.7.0
中,可以通过在命令行中增加 --experimental-modules
运行。
比如 node --experimental-modules main.mjs
:
1 | import _ from 'ramda' |
方案一
其实我最初想到的也是这种方案, 因为我以前在其它语言中接触过一些类似的做法。
在一些脚本语言中,会采用类似与 #!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 | { |
或
1 | { |
另一个比较实在的问题,就是如果要运行 ESM
的文件的话,那岂不是要把 js 文件和 package.json 一起使用?也就是说,单个 ESM
文件是不是不能运行。
同样这个对现有的某些工具链不友好,需要阅读 package.json
才能确定是不是 ESM
。
总结
总的而言,虽然方案二有些缺点,但确实是方案二更优一点。另外,有一篇对方案四的正名文章,值得读一读。