导入属性

注意:此提案的先前版本使用assert关键字而不是with。断言功能现在是非标准的。有关详细信息,请查看浏览器兼容性表

导入属性功能指示运行时如何加载模块,包括模块解析、获取、解析和评估的行为。它在import声明、export...from声明和动态import()中受支持。

语法

属性可以附加到任何类型的import/export from语句,包括默认导入、命名空间导入等。它们位于模块说明符字符串之后,并以with关键字为前缀。

js
import { names } from "module-name" with {};
import { names } from "module-name" with { key: "data" };
import { names } from "module-name" with { key: "data", key2: "data2" };
import { names } from "module-name" with { key: "data", key2: "data2", /* …, */ keyN: "dataN" };

export { names } from "module-name" with {};
export { names } from "module-name" with { key: "data" };
export { names } from "module-name" with { key: "data", key2: "data2" };
export { names } from "module-name" with { key: "data", key2: "data2", /* …, */ keyN: "dataN" };

参数

keyN

属性键。可以是标识符或字符串字面量。所有键都必须唯一,并且运行时必须知道。

"dataN"

属性值。必须是字符串字面量。

描述

导入属性告诉运行时如何加载特定模块。

主要用例是加载非 JS 模块,例如 JSON 模块和 CSS 模块。考虑以下语句

js
import data from "https://example.com/data.json";

在 Web 上,每个导入语句都会导致一个 HTTP 请求。然后,响应被准备成 JavaScript 值,并由运行时提供给程序。例如,响应可能如下所示

http
HTTP/1.1 200 OK
Content-Type: application/json; charset=utf-8
...
{"name":"John"}

模块仅根据其提供的MIME 类型进行识别和解析 - URL 中的文件扩展名不能用于识别文件类型。在这种情况下,MIME 类型为application/json,它告诉浏览器该文件是 JSON 并且必须作为 JSON 解析。如果由于某种原因(例如服务器被劫持或伪造),服务器响应中的 MIME 类型设置为text/javascript(用于 JavaScript 源代码),则该文件将被解析并作为代码执行。如果“JSON”文件实际上包含恶意代码,则import声明会意外地执行外部代码,从而构成严重的安全威胁。

导入属性通过允许作者明确指定如何验证模块来解决此问题。例如,上面缺少属性的导入语句实际上会失败

Failed to load module script: Expected a JavaScript module script but the server responded with a MIME type of "application/json". Strict MIME type checking is enforced for module scripts per HTML spec.

相反,您必须提供一个属性来告诉运行时此文件必须包含 JSON。要验证模块的类型(通过 MIME 类型),请使用名为type的属性键。要验证模块是 JSON 模块,则值为"json"

注意:实际的type属性值并不直接对应于 MIME 类型。它由HTML 规范单独指定。

因此,上面的代码应重写为

js
import data from "https://example.com/data.json" with { type: "json" };

type属性更改了模块的获取方式(浏览器使用Accept: application/json标头发送请求),但更改模块的解析或评估方式。鉴于响应 MIME 类型,运行时已经知道将模块解析为 JSON。它仅使用该属性来进行事后检查,以确保data.json模块实际上是 JSON 模块。例如,如果响应标头更改为Content-Type: text/javascript,则程序将失败,并出现与上述类似的错误。

规范明确指出支持type: "json" - 如果断言模块为type: "json"并且运行时未使此导入失败,则必须将其解析为 JSON。但是,否则没有行为要求:对于没有type: "json"属性的导入,如果此环境中没有安全问题,运行时可能仍然将其解析为 JSON。另一方面,浏览器隐式地假设该模块是 JavaScript,如果该模块不是 JavaScript(例如 JSON),则会失败。这确保了始终严格验证模块类型,并防止任何安全风险。实际上,Node 和 Deno 等非浏览器运行时与浏览器语义保持一致,并对 JSON 模块强制执行type

type属性还支持其他模块类型。例如,HTML 规范还定义了css类型,它导入CSSStyleSheet对象

js
import styles from "https://example.com/styles.css" with { type: "css" };

属性语法旨在可扩展 - 尽管语言仅指定了type,但运行时可以读取和处理其他属性。属性可以在模块加载过程的每个阶段更改运行时的行为

  • 解析:属性是模块说明符(from子句中的字符串)的一部分。因此,在给定相同的字符串路径的情况下,不同的属性可能导致加载完全不同的模块。例如,TypeScript 支持resolution-mode属性
    ts
    import type { TypeFromRequire } from "pkg" with { "resolution-mode": "require" };
    
  • 获取:例如,CSS 模块使用destination设置为"style"来获取,而 JSON 模块使用destination: "json"来获取。这意味着在给定相同的目标 URL 的情况下,服务器仍然可以返回不同的内容。
  • 解析和评估:运行时可以使用属性来确定如何解析和评估模块。

但是,您不能使用未知属性 - 如果运行时遇到未知属性,它会抛出错误。

示例

使用 type 属性导入 JSON 模块

data.json

json
{
  "name": "John"
}

index.html

html
<!doctype html>
<html lang="en-US">
  <head>
    <meta charset="utf-8" />
    <script type="module">
      import data from "./data.json" with { type: "json" };
      const p = document.createElement("p");
      p.textContent = `name: ${data.name}`;
      document.body.appendChild(p);
    </script>
  </head>
  <body></body>
</html>

启动本地 HTTP 服务器(请参阅故障排除)并转到index.html页面。您应该在页面上看到John

注意:JSON 模块只有一个默认导出。您不能从中进行命名导入(例如import { name } from "data.json")。

规范

规范
导入属性
# prod-WithClause

浏览器兼容性

BCD 表仅在浏览器中加载

另请参阅