For AI agents: the complete documentation index is available at /tc39-atlas/llms.txt, the full documentation bundle is available at /tc39-atlas/llms-full.txt, and this page is available as Markdown at /tc39-atlas/proposals/stage/4/proposal-import-attributes.md.
  • 简体中文
  • Import Attributes S4

    中文标题:导入属性

    提案概览
    提案速览

    导入属性提案为模块导入语句添加内联语法,用于在模块说明符之外传递元数据,从而以标准化方式支持其他模块类型(如 JSON 模块)。它引入了 with 关键字后跟键值列表,例如 import json from "./foo.json" with { type: "json" };,并应用于静态导入、重导出、动态 import() 以及 worker 等宿主环境。

    Note

    以下 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 模块的提议方法):

    import json from "./foo.json" with { type: "json" };
    import("foo.json", { with: { type: "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 头之间存在广泛的不匹配。所有这些都表明,依赖模块说明符中包含的文件扩展名/后缀作为检查的基础是不可行的。

    还有其他可能的元数据可以与模块关联,进一步讨论见 #8tc39/proposal-import-reflection#18

    除 JSON 模块外,受此安全问题影响的提议 ES 模块类型包括 CSS 模块和,如果 HTML 模块提案被限制为不允许脚本,则可能还包括 HTML 模块

    理由

    有三种提供这些数据的位置:

    • 作为模块说明符的一部分(例如,作为伪方案)
      • 挑战:增加了 URL 或其他模块说明符语法的复杂性,并可能导致开发者的困惑(进一步讨论:#11
      • webpack 支持这类构造(文档)。
        • 用户对 Parcel 中类似行为的需求,但部分维护者推回(#3477
    • 单独带外(例如,单独的资源文件)
      • 挑战:如何加载该资源文件;格式应是什么;在开发中跳转文件之间不便捷(进一步讨论:#13
    • 在 JavaScript 源代码中
      • 挑战:需要在 JavaScript 语言级别进行更改(本提案)

    本提案采用第三种方案,因为我们预计它将带来最佳的开发者体验,并希望可以解决语言设计/标准化问题。

    提议的语法

    导入属性必须在多种不同的上下文中可用。它们使用键值语法,前面带有 with 关键字,其中键 type 作为示例用来指示模块类型。这种键值语法可以在各种不同的上下文中使用。

    导入语句

    ImportDeclaration 将允许在 with 关键字后使用任意属性。

    例如,type 属性可用于指示模块类型,例如使用以下语法导入 JSON 模块。

    import json from "./foo.json" with { type: "json" };

    ImportDeclaration 语句中的 with 语法使用花括号,原因如下(在 #5 中讨论):

    • JavaScript 开发者已经习惯了对象字面量语法,并且由于它允许尾随逗号,复制/粘贴属性将很容易。
    • 它清晰地在多行拆分属性时指示属性列表的结束。

    重导出语句

    与导入语句类似,当从另一个模块重导出时,ExportDeclaration 将允许在 with 关键字后使用任意属性。

    export { val } from './foo.js' with { type: "javascript" };

    动态 import()

    import() 伪函数将允许在第二个参数的一个选项对象中指示导入属性。

    import("foo.json", { with: { type: "json" } })

    import() 的第二个参数是一个选项对象,目前定义的唯一选项是 with:其值是一个包含导入属性的对象。还有其他将项放入选项对象的提案:例如,模块源导入 提案引入了 phase 属性。

    模块与环境的集成

    宿主环境(例如,Web 平台、Node.js)通常提供各种不同的加载模块的方式。可以通过这些加载其他类型模块的方式传递类似的字符串。

    Worker 实例化
    new Worker("foo.wasm", { type: "module", with: { type: "webassembly" } });

    关于 WebAssembly 模块类型和 Web 的侧栏:目前仍不确定导入 WebAssembly 模块是否需要特殊标记,或者是否会像 JavaScript 一样导入。进一步讨论见 #19

    HTML

    虽然 TC39 不会规定对 HTML 的更改,但这里的一个想法是,每个导入属性(前面有 with)都会变成一个 HTML 属性,可以在 script 标签中使用。

    <script src="foo.wasm" type="module" withtype="webassembly"></script>

    (参见上面关于 WebAssembly 的注意事项。)

    WebAssembly

    WebAssembly/ESM 集成提案 的上下文中:对于从 WebAssembly 模块内部导入其他模块类型,本提案将引入一个新的自定义部分(名为 importattributes),该部分将用属性来注释每个导入的模块(这些模块列在导入部分中)。

    提议的语义和互操作性

    本提案不针对任何特定的属性键或值指定行为。JSON 模块提案 将指定 type: "json" 必须被解释为 JSON 模块,并将指定这样做的通用语义。预计 type 属性将在未来的 TC39 提案以及宿主中用于支持更多模块类型。HTML 和 CSS 模块正在考虑中,它们可能在导入时使用类似的显式 type 语法。

    除了 type 之外,还可能为尚未预见的目的引入其他属性。

    鼓励 JavaScript 实现拒绝其环境中未实现的属性和类型值(而不是忽略它们)。这是为了在未来的设计空间中提供最大的灵活性——特别是,它使得可以定义新的导入属性来改变模块的解释方式,而不会破坏向后兼容性。

    常见问题

    为什么不采用带外方式?

    为什么不能两者兼而有之?本提案的负责人认为,为各种元数据同时探索带内和带外解决方案是好的。虽然我们倾向于对模块类型采用带内元数据,但我们很高兴看到在某些 JS 环境中提出和实现的带外模块清单的发展:

    本提案并不排除使用带外元数据来进行模块类型。它也绝对不主张所有元数据都应该是带内的。例如,完整性哈希根本无法在带内工作,既因为模块循环性使得计算它们不可能,也因为当深层依赖发生变化时需要“级联”更新。

    带外解决方案面临某些缺点;这些不一定是致命的,但在考虑解决方案空间和做出权衡时值得考虑:

    • 手动编写经验:虽然带内解决方案有些冗长,但在没有太多工具的情况下编写代码时对开发者来说更直接。对于较小的项目,开发者无需手动创建额外文件。
    • 大型项目的工具复杂性:对于具有许多依赖项的大型项目,开发者无需通过编译其所有依赖项的元数据来担心创建大型清单。模块作者也无需为了消费者能够运行其模块而担心发布清单。
    • 性能权衡:Node.js 的实验性带外策略文件的经验是,由于加载和解析的某些方面,它们可能会带来显著的启动成本。

    如何确保跨 JavaScript 环境的通用行为?

    本提案的一个核心目标是尽可能地跨 JavaScript 环境共享语法和行为。为达到同样的目的,我们还提议对 JSON 模块进行尽可能的标准化(仅省略冗余类型检查的内容,这在不同环境之间必然不同,以及预先存在的宿主定义部分,如解释模块说明符和获取模块)。

    然而,与此同时,模块的一般行为以及具体的模块类型集合预计在不同 JavaScript 环境中会有所不同。例如,WebAssembly、HTML 和 CSS 模块在某些最小的嵌入式 JavaScript 环境中可能没有意义。我们希望环境可以试验并在合适的情况下进行协作。

    我们认为跨环境的兼容性问题管理与元数据是带内还是带外无关。如果没有某种协调,带外解决方案也会面临不同宿主环境之间实现或支持不一致的风险。

    属性分歧的主题在 #34 中进一步讨论。

    本提案如何与缓存配合使用?

    属性是模块缓存键的一部分,并且可以影响模块的加载方式:缓存键从 (引用者,说明符) 扩展为 (引用者,说明符,属性)

    为什么不使用更简洁的语法来指示模块类型,比如 import json from "./foo.json" as "json"

    考虑过的另一个选项是使用单个字符串作为属性来指示类型。该选项未被选择,因为它暗示了任何特定属性是特殊的;尽管本提案仅指定了 type 属性,但意图是对未来更多属性开放。(讨论见 #12)。

    是否应该支持不仅仅是字符串作为属性值?

    我们可以允许导入属性具有比简单字符串更复杂的值,例如:

    import value from "module" with { attr: { key1: "value1", key2: [1, 2, 3] } };

    这将允许导入属性扩展以支持更多种类的元数据。

    我们建议在初始提案中省略这种泛化,因为键/值字符串列表已经提供了足够的起始灵活性,但我们对后续提案提供这种泛化持开放态度。

    你们对哪些更改持开放态度?我们什么时候需要确定细节?

    我们计划在本提案的特定阶段做出决定并达成共识。以下是我们计划。

    第 2 阶段和第 3 阶段之前的原始计划
    第 2 阶段之前

    我们已经就第 2 阶段中的以下核心决策达成共识,包括:

    • 属性形式;键值或单个字符串 (#12)
    // 未选择
    import value from "module" as "json";
    
    // 未选择
    import value from "module" with type: "json";
    
    // 首次从第 2 阶段推进到第 3 阶段的提案
    import value from "module" assert { type: "json" };
    第 3 阶段之前

    在第 2 阶段之后和第 3 阶段之前,我们愿意确定一些不太核心的细节,例如:

    • 考虑 with/if/assert 关键字的替代方案 (#3)
    import value from "module" when { type: 'json' };
    import value from "module" given { type: 'json' };
    • 动态导入如何接受导入属性:
    import("foo.wasm", { with: { type: "webassembly" } });

    为了保持一致性,assert 键同时用于动态导入和静态导入。

    另一种方案是移除对象中的 assert 嵌套:

    import("foo.wasm", { type: "webassembly" });

    然而,这对于 Worker API 来说是不可能的,因为它已经使用了带有 type 键的对象作为第二个参数。这会使 API 不一致。

    第 4 阶段之前
    • 将导入属性集成到各种宿主环境中。
      • 例如,在 Web 平台上,如何在启动 worker 时启用导入属性(如果要在 Web 上发布的初始版本中支持)或包含在 <script> 标签中。
    new Worker("foo.wasm", { type: "module", with: { type: "webassembly" } });

    这里的标准化将不仅是 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-052022-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 改回为 withimport { x } from "./mod" with { type: "json" };。为了与现有实现兼容,assert 关键字将会继续支持,直到安全移除它(如果它会被移除的话)。

    规范