在现代开发模式中, 我们经常会想直接在 JavaScript 代码中导入其它类型的文件,比如 .css .json 等,代码打包工具可以通过配置之类的方法帮我们把这些非 JS 的资源转换成 JS 能够理解的内容,例如 webpack 可以在 import 的时候指定相关的 loader

import Styles from 'style-loader!css-loader?modules!./styles.css';

然而有的时候我们可能会从网络上导入一些第三方的文件,例如:

import data from 'https://third-party.com/data.json'

这个时候我们假设返回的就是一份 JSON 数据直接拿去用的话可能就会出现安全隐患,因为基于文件名去决定文件内容的类型是不靠谱的。

浏览器会基于返回文件的 Content-Type 对内容进行解析,如果第三方服务器出错或者恶意返回错误的 MIME type ,例如我们导入一个 JSON 文件却返回一个 application/javascript 类型并且包含的也是可执行的 JS 内容,那么可能就会导致代码被意外执行。所以导入非 JS 模块需要一个更加可靠的方法

JS 的最新特性 Import Assertions 提供一个新的方式,让我们可以在导入一个模块时指定额外的模块信息。


// foo.json
{ "name": "Meowu" }

// index.js
import foo from './foo.json' assert { type: 'json' };
console.log(foo.name);

跟在 assert 后面的是一个对象,这样便于后面语言的扩展,支持更多的属性。目前只有 type 属性是有效的。

现在我们可以这样 import 一个第三方文件:

import data from 'https://third-party.com/data.json' assert { type: 'json' }

如果返回的文件 MIME type 不符合要求就会导入失败。

类似导入,我们从另外一个模块导出时也可以进行断言:


// foo.js
export const name = 'foo';

// index.js
export { name } from './foo.js' assert { type: 'javascript' } 

动态 import

我们还可以在动态的 import 函数里通过第二个参数来进行类型断言,同样考虑到向前兼容,第二个参数也是一个对象字面量,它目前只有 assert 一个字段。

import('foo.json', { assert: { type: 'json' } })

除了以上方式,未来也许还可以通过以下方式在 wasm 或者 html 中导入模块。

// web worker
new Worker("foo.wasm", { type: "module", assert: { type: "webassembly" } });

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

总结

目前只支持断言文件的类型,但在语言设计上使用了便于前向兼容的对象字面量,也许在未来我们还可以断言更多的属性。

通过 import assertions 来导入 JSON 模块已经默认在 Chromium 91 上可用。

TypeScript 4.5 起也已经支持该特性。