Proposed Grammar change to ES Modules ?
中文标题:ES 模块的拟议语法变更
- 阶段: 未分阶段
- 状态: 不活跃
- ECMAScript 版本: —
- 同步时间: 2026年8月26日
- English original · 官方仓库
该提案解决了 ECMAScript 中 Script 和 Module 解析目标之间的歧义,其中代码可以在任一日标下运行但产生不同的结果。它要求模块至少有一个 import 或 export 声明来消除歧义,并建议对 .js 文件进行回退解析。该提案于 2016 年 7 月被 Node 接受,并使用 package.json 配置和多次解析尝试来确定目标,从而改善工具和性能。
以下 README 来自上游仓库,其中的阶段或状态标注可能滞后;当前信息以提案概览为准。
无歧义 JavaScript 语法
提案已接受!
2016 年 7 月 6 日,此提案被接受为 Node ES6 模块互操作性 草案提案的 第 5.1 节 :bangbang:
概要
- CJS 和 ES 模块直接可用,无需新的扩展、额外仪式或过度脚手架
- 性能通常与现有 CJS 模块加载相当
- 与转译工作流相比,ES 模块的性能显著提高
- 将 Script 和 Module 的 JavaScript 语法改为无歧义
- 通过先按一种语法解析,再回退到其他语法来确定
.js文件的语法
问题
ECMA262 的 Script 和 Module 目标存在语法歧义,某些代码可以在两种目标下运行,具有完全相同的源文本,但产生不同的结果。与 "use strict" 不同,指示特定行为的信号不在代码中,因此代码具有多种可能的效果,而这些效果不受程序员控制。
注意:以下示例突出显示了歧义语法的一些影响,但绝非详尽无遗。
示例
差异:
由于在当前语法下,无法在源文本中强制指定目标,这导致某些构造的行为由程序员未定义,而由宿主环境定义。反过来,现有代码可能在错误的目标下运行,部分功能正常,或者运行无错误但产生不正确的结果。
ECMA262 解决方案
要求 Module 源文本至少包含一个 import 或 export 声明。仅包含 import 声明而没有 export 声明的模块是有效的。不导出任何内容的模块应指定 export {} 以明确意图,并避免在移除 import 声明时出现意外解析错误。export {} 不是新语法,也不导出空对象。它只是指定不导出任何内容的标准方式。
注意:虽然 ES2015 规范 不禁止 此扩展,但 Node 希望避免充当流氓代理。Node 在 TC39 有代表 @bmeck 来推动此提案。欢迎进行规范更改或至少正式认可此 Node 提案。如果无法达成解决方案,此提案将回退到之前的 .mjs 文件扩展名提案。
Script 示例
Module 示例
问题
Node 目前需要一种方法让程序员指示其代码运行的目标。
主要解决方案要么有沉重的生态系统代价、仪式或脚手架。它们缺乏从 ECMA262 标准定义源文本意图的方法。
解决方案
包通过在其 package.json 中指定 "module" 作为解析目标字段 (名称未最终确定) 来选择 Module 目标。包依赖不受此选择影响,可以是 CJS 和 ES 模块包的混合。如果未指定解析目标,则尝试将源文本解析为优先目标 (目前为 Script,因为大多数模块是 CJS)。如果出现可能允许其他目标解析的解析错误,则按其他目标解析,依此类推。此后,目标被无歧义地确定,环境可以安全地执行初始化,而不会使源文本在错误的目标下运行。
算法
Parse (source, goal, throws)
将源文本解析为给定目标的抽象操作。
- 为
goal引导source。 - 将
source解析为goal。 - 如果成功,返回
true。 - 如果
throws,抛出异常。 - 返回
false。
操作
-
如果指定了包解析目标,则
-
令
goal为解析后的解析目标。 -
调用
Parse(source, goal, true)并返回。 -
否则回退到多次解析。
-
如果
Parse(Source, Script, false)为true,则 1. 返回。 -
否则 1. 调用
Parse(Source, Module, true)。
注意:主机可以选择任一目标先解析,并可能随时间或引入新解析目标时更改其顺序。可以随意交换 Script 和 Module 的顺序。
实现
为了提高性能,宿主环境可能希望指定首先解析的目标。这可以通过多种方式完成:
磁盘缓存、命令行标志、清单文件、HTTP 头、文件扩展名等。
工具关注点
Node 之外的一些工具可能无法访问 JS 解析器 (Bash 程序、某些资源管道等)。这些工具通常将文件视为不透明 blob / 纯文本文件,可以使用 实现 下列出的技术来获取解析目标信息。
外部示例和影响
-
Esprima 依赖
--module标志来表示 Module 目标。然而,这对许多用户来说已被证明是不直观的。无歧义的 Script 和 Module 目标将使事情“直接可用”,无需标志。 -
Facebook Flow 执行一系列推断来检测 CJS 和 ES 模块。无歧义的 Script 和 Module 目标将提高其确定模块类型的能力。
-
JSCS 可以通过 stdin 接受输入,因此通过源文本识别解析目标是理想的。
-
像 xo 这样的 linter 可以使用无歧义的 Script 和 Module 目标来启用特定于模块的 lint 规则,而无需额外配置。
-
微软打包的 Web 应用程序可以受益于无歧义的 Script 和 Module 目标。打包的 Web 应用程序的字节码缓存在安装时生成。当应用程序运行时,文件通过脚本标签加载,因此其预期的解析目标是已知的。然而,字节码缓存的生成是在 不 运行应用程序的情况下完成的,因此预期的解析目标是未知的。因此,字节码缓存为 Script 目标生成,而 ES 模块则被忽略。
-
TypeScript 从早期就采取了这样的立场:当脚本至少有一个
import或export声明时,它就成为一个模块。多年来,这种方法几乎没有遇到用户问题。
特别感谢
没有 @bmeck、@dherman 和 @wycats 的不懈努力、坚定信念和协作,本提案不可能实现。
在迭代此提案时,我们联系了受其影响的多个领域的数人。
尽管对此提案的意见各不相同,但我感谢以下人士的反馈:
- @AtOMiCNebula(Microsoft Edge)
- @brendaneich(TC39)
- @bterlson(Microsoft Chakra / TC39)
- @chrisdickinson(Node)
- @hzoo(Babel)
- @indutny(Node)
- @leobalter(jQuery Foundation / TC39)
- @ljharb(TC39)
- @loganfsmyth(Babel)
- @mathiasbynens(Lodash)
- @rvagg(Node)
- @sheerun(Bower)
- @sindresorhus(AVA / Chalk / Yeoman)
- @travisleithead(Microsoft Edge / W3C)
- @trevnorris(Node)
谢谢! :heart:
— @jdalton