来源:https://jakearchibald.com/2017/es-modules-in-browsers/
https://wolfx.cn/ecmascript-modules-in-browsers/
在浏览器中也可以使用 JavaScript modules(模块功能)了。目前支持这一特性的浏览器包括:
// utils.js
export function addTextToBody(text) {
const div = document.createElement("div");
div.textContent = text;
document.body.appendChild(div);
}
我们需要做的只是在 script 标签元素上声明 type=module 就可以了,这样,浏览器就能解析代码中的 module 语法了。
网上已经有很多关于 modules 的好文章了,但我在这里想单独分享一下专门针对浏览器里的 ECMAScript modules 的知识,这些都是我在阅读和测试 ECMAScript 规范时学到的。
// 可以这样使用:
import { foo } from "https://jakearchibald.com/utils/bar.js";
import { foo } from "/utils/bar.js";
import { foo } from "./bar.js";
import { foo } from "../bar.js";
// 这些写法不支持:
import { foo } from "bar.js";
import { foo } from "utils/bar.js";
下面几种 module 语法是有效的:
另外一些语法是保留为将来使用的,比如,导入内部的(built-in) modules。
能够认识 type=module 语法的浏览器会忽略具有 nomodule 属性的 scripts。也就是说,我们可以使用一些脚本服务于支持 module 语法的浏览器,同时提供一个 nomodule 的脚本用于哪些不支持 module 语法的浏览器,作为补救。
执行的顺序将会是 2.js, 1.js, 3.js.
如果 script 代码块阻止 HTML 分析器下载其他代码,这是非常糟糕的事情,通常我们会使用 defer 属性来防止这种解析阻塞,但同时这样也会延迟 script 脚本的执行——直到真个文档解析完成。而且还要参考其它 deferred script 脚本的执行顺序。Module scripts 缺省行为状态很像 defer 属性的作用 – 一个 module script 不会妨碍 HTML 分析器下载其它资源。
Module scripts 队列的执行顺序跟使用了 defer 属性的普通脚本队列的执行顺序一样。
执行的顺序是 1.js, inline script, inline module, 2.js.
普通的 inline scripts 会忽略 defer 属性,而 inline module scripts 永远是 deferred 的,不管它是否有 import 行为。
这个快速下载 script 会率先执行。
跟普通的 scripts 一样, async 属性能让 script 加载的同时并不阻碍 HTML 解析器的工作,而且在加载完成后立即执行。跟普通的 scripts 不同的是, async 属性在 inline modules 脚本上也有效。
因为永远都是 async, 所以这些 scripts 的执行顺序也许并不会像它们出现在 DOM 里的顺序。
Firefox 并不支持 inline module scripts 上的 async 特性。(issue).
如果你知道 ES modules,你就应该知道,modules 可以 import 多次,但只会执行一次。这种原则同样适用于 HTML 里的 script modules – 一个确定的 URL 上的 module script 在一个页面上只会执行一次。
跟普通的 scripts 不同, module scripts (以及它们的 imports 行为) 受 CORS 跨域资源共享限制。也就是说,跨域的 module scripts 必须返回带有有效 Access-Control-Allow-Origin: \*
的 CORS 头信息。
当请求在同一安全域下,大多数的 CORS-based APIs 都会发送凭证信息 (cookies 等),但 fetch() 和 module scripts 例外 – 它们并不发送凭证信息,除非我们要求它们。
我们可以通过添加 crossorigin 属性来让同源 module 脚本携带凭证信息,如果你也想让非同源 module 脚本也携带凭证信息,使用 crossorigin="use-credentials" 属性。需要注意的是,非同源脚本需要具有 Access-Control-Allow-Credentials: true 头信息。
同样,“Modules 只执行一次”的规则也会影响到这一特征。URL 是 Modules 的唯一标志,如果你先请求的 Modules 没有携带凭证信息,然后你第二次请求希望携带凭证信息,你仍然会得到一个没有凭证信息的 module。这就是为什么上面的有个 URL 上我使用了一个?号,是让 URL 变的不同。
跟普通的 scripts 不一样,modules scripts 必须指定一个有效的 JavaScript MIME 类型,否则将不会执行。