网络埋伏纪事

模块标识符:ES 模块的新功能

网络埋伏纪事 · 2017-05-16翻译 · 531阅读 原文链接

本文描述了模块标识符(类似路径一样的模块 ID)如何随 ECMAScript 模块(ESM)而改变。它与熟悉的CommonJS模块(CJS)风格相比,有一些微妙的差异。

更新 2017-05-11:完全重写“为什么 ES模块要用新的文件扩展名?” 这一节。

为什么 ES模块要用新的文件扩展名?

Node.js 将通过用新的文件扩展名.mjs来支持 ES 模块(也戏称为 “Michael Jackson Script” )。很多 Web 开发人员会喜欢坚持使用 .js。下面我来举例说明为什么.mjs是更好的方法。

要记住的核心是:为区分 CommonJS 模块和 ES 模块,我们需要某种元数据。浏览器也面临类似的问题,必须区分脚本和模块。它们的做法通过属性module将元数据附加到<script>元素中。

下面我们从能让我们保持用.js做扩展名的方法开始。

保持.js的方法

  1. 通过package.json指定哪些文件是ESM。该方法的一个有趣的地方是其文件系统布局与所有其他方法不同:CJS文件的ESM版本保存在单独的目录中,其结构反映了CJS目录的结构。
    • 缺点:它不适用于独立的文件。为了查明给定的文件是什么,你必须到处去找。
  2. 通过解析进行检测:可以使用涵盖这两个脚本的语法来解析文件(包括CommonJS模块和ES模块)。解析后,会检查所得到的抽象语法树,以找出处理的文件类型。
    • 缺点:有的文件可以既可以是CJS,也可以是ESM - 既不导入也不导出的文件。例如,将东西安装到全局变量中的polyfills。另外,在CJS和ESM中,通过import()动态导入ES模块看起来都是一样的。
  3. 在文件开头的加一个编译指示(类似于严格模式,例如“use module”;)。您可以通过解析将这种方法与检测结合起来,只在文件不明确的地方用编译指示。
    • 缺点:编译指示的先决条件是严格模式。我知道我应该使用严格模式,但我通常太懒了,并且也不喜欢它给我的文件添加乱七八糟的东西。因此,我的结论是编译指示的用户体验很糟糕。我们现在只是用编译指示来区分CJS和ESM。不过,从长远来看,CJS终将会消失,只会给遗留的文件(也可能是当前的文件)留下一堆无意义的乱七八糟的东西。我可以对CJS(即旧的文件)用编译指示,但这可能没啥用。

方法#2和#3的一个问题是你没法将一个ESM版本的文件和一个CJS版本的文件相邻(除非你还用方法#1)。

对我来说,如果人或机器都得费很大劲才找得出正处理的是什么类型的文件,那就是不对的。

文件名扩展名.mjs

使用不同的文件扩展名是将元数据附加到文件的简单方法。

将稍有不同的JavaScript放在具有不同文件名扩展名的文件中是有很多先例的。例如:

  • *.jsx用于使用JSX的React代码
  • *.ts用于TypeScript文件
  • *.es6用于通过Babel编辑的文件(开始很流行,现在不流行了)

为什么保持用.js是合适的?

无论我们最终以什么做模块的文件扩展名,将来.js都会是JavaScript的主要扩展名。因此,抛弃.js在视觉上是不受欢迎的。

支持保持用.js做扩展名的另一个争论是,为了将.mjs文件识别为包含JavaScript,需要改写工具。不过,我认为大部分工具需要大幅改变才能有效处理ESM。

模块标识符#

模块标识符是模块的类似于路径一样的标识符。例如:

import lodash from 'lodash';

这里,字符串'lodash'是一个模块标识符。

模块标识符如何随着ESM而改变? #

ES模块的关键变化是:

所有ES模块标识符必须是有效的URL

特别是,如果模块的文件名有一个扩展名,那么它的标识符也必须有一个。这就和目前非模块的 <script> 元素是完全一样的。

以下import语句都有合法的模块标识符:

import * as foo from 'http://example.com/lib/foo.mjs';
import * as foo from '/lib/foo.mjs';
import * as foo from './foo.mjs';
import * as foo from '../foo.mjs';

为了与CommonJS兼容,并为将来的特性做准备,不以./../开头的相对路径是不允许的:

// Not allowed:
import * as foo from 'foo.mjs';
import * as foo from 'lib/foo.mjs';

最佳实践

如下是编写模块标识符的两种最佳实践:

  1. 引用存储在本地文件中的ES模块:要包括文件扩展名。这对浏览器和Node.js都适用.
    • 浏览器不关心文件扩展名,仅关心MIME类型。鉴于npm对于客户端开发的重要性,在浏览器中切换到.mjs将有助于保持JavaScript代码跨平台兼容。
    • 请注意,模块标识符'./foo.mjs'可能会(除其他外)引用:foo.mjsfoo.mjs / index.mjs<font color="#333333">、</font>``foo.mjs / index.js
  2. 引用node_modules中的ES模块:
    • Node.js:可以省略文件扩展名。无论模块是ESM还是CJS,标识符都可以工作。
    • 浏览器:看看未编译的代码将如何处理node_modules会很有趣(对于由webpack等工具编译的代码来说,不会有多大变化,因为这样的工具可以在编译时解析路径)。一个选项是通过配置文件将node_modules路径映射到实际路径(类似于AMD所做的)。另一个选项是链接到node_modules目录。

目前还不清楚node_modules的标识符最终是否也会有文件扩展名。

在工具上支持.mjs

对工具添加对.mjs的支持正在进行中。例如:

对于Visual Studio Code,我将如下内容添加到工作区设置中:

"files.associations": {
  "*.mjs": "javascript"
}

致谢:感谢Bradley Farias回答我与模块标识符相关的问题。

进一步阅读

相关文章