在浏览器中使用 JavaScript module

来源:https://jakearchibald.com/2017/es-modules-in-browsers/

         https://wolfx.cn/ecmascript-modules-in-browsers/

在浏览器中也可以使用 JavaScript modules(模块功能)了。目前支持这一特性的浏览器包括:

  • Safari 10.1.
  • 谷歌浏览器(Canary 60) – 需要在 chrome:flags 里开启 “实验性网络平台功能(Experimental Web Platform)”。
  • Firefox 54 – 需要在 about:config 里开启 dom.moduleScripts.enabled 选项。
  • Edge 15 – 需要在 about:flags 里开启 Experimental JavaScript Features 选项。
<script type="module">
  import {addTextToBody} from './utils.js';
  addTextToBody('Modules are pretty cool.');
</script>
// 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 语法

// 可以这样使用:
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 语法是有效的:

  • 绝对路径的 URL。也就是说,使用 new URL(模块地址) 也不会报错。
  • 地址开头是 /。
  • 地址开头是 ./。
  • 地址开头是 ../。

另外一些语法是保留为将来使用的,比如,导入内部的(built-in) modules。

用来保持向后兼容的 nomodule

<script type="module" src="module.js"></script>
<script nomodule src="fallback.js"></script>

能够认识 type=module 语法的浏览器会忽略具有 nomodule 属性的 scripts。也就是说,我们可以使用一些脚本服务于支持 module 语法的浏览器,同时提供一个 nomodule 的脚本用于哪些不支持 module 语法的浏览器,作为补救。

浏览器问题

  • Firefox 并不支持 nomodule 属性 (issue)。但在 Firefox nightly 版里已经修复了这个问题。
  • Edge 并不支持 nomodule 属性。 (issue).
  • Safari 10.1 并不支持 nomodule 属性,但在最新的技术预览版中修复了这个问题。对于 10.1 版, 有一个很聪明的变通技巧

缺省设置为 Defer

<!-- 这个脚本的执行将会晚于… -->
<script type="module" src="1.js"></script>

<!-- …这个脚本… -->
<script src="2.js"></script>

<!-- …但是会先于这个脚本. -->
<script defer src="3.js"></script>

执行的顺序将会是 2.js, 1.js, 3.js.

如果 script 代码块阻止 HTML 分析器下载其他代码,这是非常糟糕的事情,通常我们会使用 defer 属性来防止这种解析阻塞,但同时这样也会延迟 script 脚本的执行——直到真个文档解析完成。而且还要参考其它 deferred script 脚本的执行顺序。Module scripts 缺省行为状态很像 defer 属性的作用 – 一个 module script 不会妨碍 HTML 分析器下载其它资源。

Module scripts 队列的执行顺序跟使用了 defer 属性的普通脚本队列的执行顺序一样。

Inline scripts 同样是 deferred

<!-- 这个脚本的执行将会晚于… -->
<script type="module">
  addTextToBody("Inline module executed");
</script>

<!-- …这个脚本… -->
<script src="1.js"></script>

<!-- …和这个脚本… -->
<script defer>
  addTextToBody("Inline script executed");
</script>

<!-- …但会先于这个脚本。-->
<script defer src="2.js"></script>

执行的顺序是 1.js, inline script, inline module, 2.js.

普通的 inline scripts 会忽略 defer 属性,而 inline module scripts 永远是 deferred 的,不管它是否有 import 行为。

外部 & inline modules script 脚本上的 Async 属性

<!-- 这个脚本将会在imports完成后立即执行 -->
<script async type="module">
  import {addTextToBody} from './utils.js';

  addTextToBody('Inline module executed.');
</script>

<!-- 这个脚本将会在脚本加载和imports完成后立即执行 -->
<script async type="module" src="1.js"></script>

这个快速下载 script 会率先执行。

跟普通的 scripts 一样, async 属性能让 script 加载的同时并不阻碍 HTML 解析器的工作,而且在加载完成后立即执行。跟普通的 scripts 不同的是, async 属性在 inline modules 脚本上也有效。

因为永远都是 async, 所以这些 scripts 的执行顺序也许并不会像它们出现在 DOM 里的顺序。

浏览器问题

Firefox 并不支持 inline module scripts 上的 async 特性。(issue).

Modules 只执行一次

<!-- 1.js 只执行一次 -->
<script type="module" src="1.js"></script>
<script type="module" src="1.js"></script>
<script type="module">
  import "./1.js";
</script>

<!-- 而普通的脚本会执行多次 -->
<script src="2.js"></script>
<script src="2.js"></script>

如果你知道 ES modules,你就应该知道,modules 可以 import 多次,但只会执行一次。这种原则同样适用于 HTML 里的 script modules – 一个确定的 URL 上的 module script 在一个页面上只会执行一次。

CORS 跨域资源共享限制

<!-- 这个脚本不会执行,因为跨域资源共享限制 -->
<script type="module" src="https://….now.sh/no-cors"></script>

<!-- 这个脚本不会执行,因为跨域资源共享限制-->
<script type="module">
  import 'https://….now.sh/no-cors';

  addTextToBody("This will not execute.");
</script>

<!-- 这个没问题 -->
<script type="module" src="https://….now.sh/cors"></script>

跟普通的 scripts 不同, module scripts (以及它们的 imports 行为) 受 CORS 跨域资源共享限制。也就是说,跨域的 module scripts 必须返回带有有效 Access-Control-Allow-Origin: \* 的 CORS 头信息。

没有凭证信息(credentials)

<!-- 有凭证信息 (cookies等) -->
<script src="1.js"></script>

<!-- 没有凭证信息 -->
<script type="module" src="1.js"></script>

<!-- 有凭证信息 -->
<script type="module" crossorigin src="1.js?"></script>

<!-- 没有凭证信息 -->
<script type="module" crossorigin src="https://other-origin/1.js"></script>

<!-- 有凭证信息-->
<script type="module" crossorigin="use-credentials" src="https://other-origin/1.js?"></script>

当请求在同一安全域下,大多数的 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 变的不同。

Mime-types 文档类型

跟普通的 scripts 不一样,modules scripts 必须指定一个有效的 JavaScript MIME 类型,否则将不会执行。

猜你喜欢

转载自blog.csdn.net/jyb123/article/details/81207264