Import Attributes S4
中文标题:导入属性
- 阶段: Stage 4
- 状态: 已完成
- ECMAScript 版本: ES2025
- 同步时间: 2026年8月26日
- English original · 官方仓库
导入属性提案为模块导入语句添加内联语法,用于在模块说明符之外传递元数据,从而以标准化方式支持其他模块类型(如 JSON 模块)。它引入了 with 关键字后跟键值列表,例如 import json from "./foo.json" with { type: "json" };,并应用于静态导入、重导出、动态 import() 以及 worker 等宿主环境。
以下 README 来自上游仓库,其中的阶段或状态标注可能滞后;当前信息以提案概览为准。
导入属性
提案负责人:Sven Sauleau (@xtuc)、Daniel Ehrenberg (@littledan)、Myles Borins (@MylesBorins)、Dan Clark (@dandclark) 和 Nicolò Ribaudo (@nicolo-ribaudo)。
状态:第 4 阶段
⚠️ 本提案中的规范可能已过时。tc39/ecma262#3057 是最新版本。
当前规范中的一些更改尚未提交给委员会:#142
请将任何反馈留在 issues 中!
简介
导入属性提案(以前称为导入断言)为模块导入语句添加了一种内联语法,以便在模块说明符之外传递更多信息。此类属性的最初应用将是支持跨 JavaScript 环境的通用方式来支持其他类型的模块,首先从 JSON 模块 开始。
语法如下(此处展示的是导入 JSON 模块的提议方法):
JSON 模块的规范最初是本提案的一部分,但在 2020 年 7 月的会议中决定将其拆分为一个独立的第 3 阶段提案。
动机
标准跟踪的 JSON ES 模块最初被提议以允许 JavaScript 模块轻松导入 JSON 数据文件,类似于在许多非标准 JavaScript 模块系统中的支持方式。这个想法很快得到了 Web 开发者和浏览器的广泛支持,并被合并到 HTML 中,Microsoft 创建了 V8/Chromium 的实现。
然而,在一个问题中,Ryosuke Niwa (Apple) 和 Anne van Kesteren (Mozilla) 提出,如果在导入 JSON 模块和类似无法执行代码的模块类型时需要有某种语法标记,则可以改善安全性,以防止响应服务器意外提供不同的 MIME 类型而导致代码意外执行。解决方案是除了 MIME 类型之外,还要以某种方式表明模块是 JSON,或者通常不执行。
一些开发者直觉认为可以使用文件扩展名来确定模块类型,就像在许多现有的非标准模块系统中那样。然而,Web 架构的一个深层原则是,URL 的后缀(在 Web 之外你可能视为“文件扩展名”)不会导致页面解释方式的语义。实际上,在 Web 上,文件扩展名和 HTTP Content-Type 头之间存在广泛的不匹配。所有这些都表明,依赖模块说明符中包含的文件扩展名/后缀作为检查的基础是不可行的。
还有其他可能的元数据可以与模块关联,进一步讨论见 #8 和 tc39/proposal-import-reflection#18。
除 JSON 模块外,受此安全问题影响的提议 ES 模块类型包括 CSS 模块和,如果 HTML 模块提案被限制为不允许脚本,则可能还包括 HTML 模块。
理由
有三种提供这些数据的位置:
- 作为模块说明符的一部分(例如,作为伪方案)
- 单独带外(例如,单独的资源文件)
- 挑战:如何加载该资源文件;格式应是什么;在开发中跳转文件之间不便捷(进一步讨论:#13)
- 在 JavaScript 源代码中
- 挑战:需要在 JavaScript 语言级别进行更改(本提案)
本提案采用第三种方案,因为我们预计它将带来最佳的开发者体验,并希望可以解决语言设计/标准化问题。
提议的语法
导入属性必须在多种不同的上下文中可用。它们使用键值语法,前面带有 with 关键字,其中键 type 作为示例用来指示模块类型。这种键值语法可以在各种不同的上下文中使用。
导入语句
ImportDeclaration 将允许在 with 关键字后使用任意属性。
例如,type 属性可用于指示模块类型,例如使用以下语法导入 JSON 模块。
ImportDeclaration 语句中的 with 语法使用花括号,原因如下(在 #5 中讨论):
- JavaScript 开发者已经习惯了对象字面量语法,并且由于它允许尾随逗号,复制/粘贴属性将很容易。
- 它清晰地在多行拆分属性时指示属性列表的结束。
重导出语句
与导入语句类似,当从另一个模块重导出时,ExportDeclaration 将允许在 with 关键字后使用任意属性。
动态 import()
import() 伪函数将允许在第二个参数的一个选项对象中指示导入属性。
import() 的第二个参数是一个选项对象,目前定义的唯一选项是 with:其值是一个包含导入属性的对象。还有其他将项放入选项对象的提案:例如,模块源导入 提案引入了 phase 属性。
模块与环境的集成
宿主环境(例如,Web 平台、Node.js)通常提供各种不同的加载模块的方式。可以通过这些加载其他类型模块的方式传递类似的字符串。
Worker 实例化
关于 WebAssembly 模块类型和 Web 的侧栏:目前仍不确定导入 WebAssembly 模块是否需要特殊标记,或者是否会像 JavaScript 一样导入。进一步讨论见 #19。
HTML
虽然 TC39 不会规定对 HTML 的更改,但这里的一个想法是,每个导入属性(前面有 with)都会变成一个 HTML 属性,可以在 script 标签中使用。
(参见上面关于 WebAssembly 的注意事项。)
WebAssembly
在 WebAssembly/ESM 集成提案 的上下文中:对于从 WebAssembly 模块内部导入其他模块类型,本提案将引入一个新的自定义部分(名为 importattributes),该部分将用属性来注释每个导入的模块(这些模块列在导入部分中)。
提议的语义和互操作性
本提案不针对任何特定的属性键或值指定行为。JSON 模块提案 将指定 type: "json" 必须被解释为 JSON 模块,并将指定这样做的通用语义。预计 type 属性将在未来的 TC39 提案以及宿主中用于支持更多模块类型。HTML 和 CSS 模块正在考虑中,它们可能在导入时使用类似的显式 type 语法。
除了 type 之外,还可能为尚未预见的目的引入其他属性。
鼓励 JavaScript 实现拒绝其环境中未实现的属性和类型值(而不是忽略它们)。这是为了在未来的设计空间中提供最大的灵活性——特别是,它使得可以定义新的导入属性来改变模块的解释方式,而不会破坏向后兼容性。
常见问题
为什么不采用带外方式?
为什么不能两者兼而有之?本提案的负责人认为,为各种元数据同时探索带内和带外解决方案是好的。虽然我们倾向于对模块类型采用带内元数据,但我们很高兴看到在某些 JS 环境中提出和实现的带外模块清单的发展:
- import maps 将模块说明符映射到 URL/路径
- Node.js 策略文件,例如,对模块执行完整性检查
本提案并不排除使用带外元数据来进行模块类型。它也绝对不主张所有元数据都应该是带内的。例如,完整性哈希根本无法在带内工作,既因为模块循环性使得计算它们不可能,也因为当深层依赖发生变化时需要“级联”更新。
带外解决方案面临某些缺点;这些不一定是致命的,但在考虑解决方案空间和做出权衡时值得考虑:
- 手动编写经验:虽然带内解决方案有些冗长,但在没有太多工具的情况下编写代码时对开发者来说更直接。对于较小的项目,开发者无需手动创建额外文件。
- 大型项目的工具复杂性:对于具有许多依赖项的大型项目,开发者无需通过编译其所有依赖项的元数据来担心创建大型清单。模块作者也无需为了消费者能够运行其模块而担心发布清单。
- 性能权衡:Node.js 的实验性带外策略文件的经验是,由于加载和解析的某些方面,它们可能会带来显著的启动成本。
如何确保跨 JavaScript 环境的通用行为?
本提案的一个核心目标是尽可能地跨 JavaScript 环境共享语法和行为。为达到同样的目的,我们还提议对 JSON 模块进行尽可能的标准化(仅省略冗余类型检查的内容,这在不同环境之间必然不同,以及预先存在的宿主定义部分,如解释模块说明符和获取模块)。
然而,与此同时,模块的一般行为以及具体的模块类型集合预计在不同 JavaScript 环境中会有所不同。例如,WebAssembly、HTML 和 CSS 模块在某些最小的嵌入式 JavaScript 环境中可能没有意义。我们希望环境可以试验并在合适的情况下进行协作。
我们认为跨环境的兼容性问题管理与元数据是带内还是带外无关。如果没有某种协调,带外解决方案也会面临不同宿主环境之间实现或支持不一致的风险。
属性分歧的主题在 #34 中进一步讨论。
本提案如何与缓存配合使用?
属性是模块缓存键的一部分,并且可以影响模块的加载方式:缓存键从 (引用者,说明符) 扩展为 (引用者,说明符,属性)。
为什么不使用更简洁的语法来指示模块类型,比如 import json from "./foo.json" as "json"?
考虑过的另一个选项是使用单个字符串作为属性来指示类型。该选项未被选择,因为它暗示了任何特定属性是特殊的;尽管本提案仅指定了 type 属性,但意图是对未来更多属性开放。(讨论见 #12)。
是否应该支持不仅仅是字符串作为属性值?
我们可以允许导入属性具有比简单字符串更复杂的值,例如:
这将允许导入属性扩展以支持更多种类的元数据。
我们建议在初始提案中省略这种泛化,因为键/值字符串列表已经提供了足够的起始灵活性,但我们对后续提案提供这种泛化持开放态度。
你们对哪些更改持开放态度?我们什么时候需要确定细节?
我们计划在本提案的特定阶段做出决定并达成共识。以下是我们计划。
第 2 阶段和第 3 阶段之前的原始计划
第 2 阶段之前
我们已经就第 2 阶段中的以下核心决策达成共识,包括:
- 属性形式;键值或单个字符串 (#12)
第 3 阶段之前
在第 2 阶段之后和第 3 阶段之前,我们愿意确定一些不太核心的细节,例如:
- 考虑
with/if/assert关键字的替代方案 (#3)
- 动态导入如何接受导入属性:
为了保持一致性,assert 键同时用于动态导入和静态导入。
另一种方案是移除对象中的 assert 嵌套:
然而,这对于 Worker API 来说是不可能的,因为它已经使用了带有 type 键的对象作为第二个参数。这会使 API 不一致。
第 4 阶段之前
- 将导入属性集成到各种宿主环境中。
- 例如,在 Web 平台上,如何在启动 worker 时启用导入属性(如果要在 Web 上发布的初始版本中支持)或包含在
<script>标签中。
- 例如,在 Web 平台上,如何在启动 worker 时启用导入属性(如果要在 Web 上发布的初始版本中支持)或包含在
这里的标准化将不仅是 TC39 内部达成共识,还要在 WHATWG HTML 以及 Node.js ESM 工作中达成共识,并对各种宿主环境的语义要求进行总体审计(#10、#24 和 #25)。
历史
- 2019-12: 该提案(名为 模块属性)被批准进入第 1 阶段(笔记第 1 部分、笔记第 2 部分、幻灯片)以探索模块导入的元数据,并探索关于不执行代码模块的保证。
- 2020-06: 模块属性 推进到第 2 阶段(笔记第 1 部分、笔记第 2 部分、幻灯片),共识基于导入属性不能成为模块映射中缓存键的一部分的限制。提议的语法是
import { x } from "./mod" with type: "json", something: "else";。 - 2020-09: 该提案更名为 导入断言,推进到第 3 阶段(笔记、幻灯片)。更名更好地描述了已同意的仅断言语义,并且关键字从
with更改为assert。然而,该提案放宽了缓存限制,以便 HTML 仍然可以将模块类型作为缓存键的一部分,同时仍然尊重提案的“精神”。 - 2021-05–2022-02: 该提案使用
import { x } from "./mod" assert { type: "json" };语法,在 Chrome、Node.js 和 Deno 中实现并发布。它们都支持 JSON 模块 提案。 - 2023-01: 由于与非 JavaScript 模块的 HTML 所需语义(特别是关于 HTTP 获取和 CSP)存在不兼容,该提案降级回第 2 阶段(笔记第 1 部分、笔记第 2 部分、幻灯片)以调查满足 Web 平台需求的解决方案。
- 2023-03: 该提案更名为 导入属性 并移回第 3 阶段(TODO: 笔记、幻灯片)。对缓存键的限制被完全移除,关键字从
assert改回为with:import { x } from "./mod" with { type: "json" };。为了与现有实现兼容,assert关键字将会继续支持,直到安全移除它(如果它会被移除的话)。