[譯] 瀏覽器中的 ECMAScript 模塊

原文鏈接:ECMAScript modules in browsers
作者:Jake Archibald

瀏覽器現在可以使用 ES 模塊(module)了!它們是:

  • Safari 10.1
  • Chrome 61
  • Firefox 60
  • Microsoft Edge 16
<script type="module">
  import {addTextToBody} from './utils.mjs';

  addTextToBody('Modules are pretty cool.');
</script>
// utils.mjs
export function addTextToBody(text) {
  const div = document.createElement('div');
  div.textContent = text;
  document.body.appendChild(div);
}

在線演示

您只需要在 script 元素上添加 type=module,瀏覽器就會將內聯腳本或外部腳本作爲 ECMAScript module 處理。

關於模塊(module)已經有一些很棒的文章,但是我想分享一些在我測試和閱讀規範的時候學到的瀏覽器特有的內容。

目前還不支持的某些 import 用法

// 已支持:
import {foo} from 'https://jakearchibald.com/utils/bar.mjs';
import {foo} from '/utils/bar.mjs';
import {foo} from './bar.mjs';
import {foo} from '../bar.mjs';

// 不支持:
import {foo} from 'bar.mjs';
import {foo} from 'utils/bar.mjs';

有效的模塊路徑說明符必須符合下列條件之一:

  • 一個完整的非相對URL,同樣地,通過 new URL(moduleSpecifier) 得到的URL也不會報錯。
  • / 開頭的。
  • ./ 開頭的。
  • ../ 開頭的。

其他形式的說明符保留供將來使用,例如導入內置模塊。

使用 nomodule 來向後兼容

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

在線演示

理解 type=module 的瀏覽器會忽略屬性爲 nomodule 的腳本。這意味着您可以給支持模塊的瀏覽器提供模塊樹,同時給其他瀏覽器提供一個回退版本。

瀏覽器問題

  • Firefox 瀏覽器不支持 nomodule (issue)。已在 Firefox nightly 中修復!
  • Edge 瀏覽器不支持 nomodule (issue)。已在 Edge 16 中修復!
  • Safari 瀏覽器不支持 nomodule。已在 Safari 11 中修復!對於 10.1,這裏有一個非常聰明的替代辦法

默認情況下推遲

<!-- 這個腳本的執行會晚於… -->
<script type="module" src="1.mjs"></script>

<!-- …這個腳本… -->
<script src="2.js"></script>

<!-- …但是會在這個腳本之前執行。 -->
<script defer src="3.js"></script>

在線演示

執行的順序是:2.js1.mjs3.js

script 在獲取期間會阻塞 HTML 解析器的方式是非常糟糕的。對於常規腳本,您可以使用 defer 來避免阻塞,當然這也會推遲腳本的執行,直到文檔完成解析,並與其他延遲腳本一起維護執行順序。模塊腳本的默認表現行爲就像 defer ——當它正在獲取時,沒有辦法讓一個模塊腳本阻塞 HTML 解析器。

模塊腳本使用和添加了 defer 的常規腳本相同的執行隊列。

內聯腳本也是延時的

<!-- 這個腳本的執行會晚於… -->
<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 ,內聯腳本,內聯腳本,2.js

常規的內聯腳本會忽略 defer ,然而內聯模塊腳本卻總是被延遲,無論它們有沒有導入任何東西。

異步適用於外部和內聯模塊

<!-- 一旦獲取了導入,就會執行此操作 -->
<script async type="module">
  import {addTextToBody} from './utils.mjs';

  addTextToBody('Inline module executed.');
</script>
<!-- 一旦獲取了腳本和它的導入,就會執行此操作 -->
<script async type="module" src="1.mjs"></script>

在線演示

快速下載的腳本會在慢速下載的腳本之前執行。

與常規腳本一樣,async 會讓腳本在下載過程中不會阻塞 HTML 解析器,並且儘快地執行。與常規腳本不同,async 也適用於內聯模塊。

與往常的 async 一樣,腳本不會按照它們出現在 DOM 中的順序執行。

瀏覽器問題

  • Firefox 瀏覽器不支持內聯模塊腳本上的 async (issue)。已在 Firefox 59 中修復!

模塊僅執行一次

<!-- 1.mjs 僅執行一次 -->
<script type="module" src="1.mjs"></script>
<script type="module" src="1.mjs"></script>
<script type="module">
  import "./1.mjs";
</script>

<!-- 然而,普通的腳本卻執行多次 -->
<script src="2.js"></script>
<script src="2.js"></script>

在線演示

如果您理解 ES 模塊,您就會知道您雖然可以引入它們很多次,但是它們卻僅僅會執行一次。當然,這同樣適用於HTML中的腳本模塊 - 特定URL的模塊腳本每頁只執行一次。

瀏覽器問題

  • Edge 執行多次模塊 (issue)。已修復,但是還沒發佈(希望 Edge 17 會帶上這個修復內容)。

總是 CORS

<!-- 該腳本不會執行, 因爲它不能通過 CORS 檢查 -->
<script type="module" src="https://….now.sh/no-cors"></script>

<!-- 該腳本不會執行, 因爲它引入的腳本之一不能通過 CORS 檢查 -->
<script type="module">
  import 'https://….now.sh/no-cors';

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

<!-- 該腳本會執行,因爲它通過了 CORS 檢查 -->
<script type="module" src="https://….now.sh/cors"></script>

在線演示

與常規腳本不同,模塊腳本(和它引入的內容)是通過 CORS 獲取的。這就意味着跨域的模塊腳本必須返回有效的 CORS header ,比如 Access-Control-Allow-Origin: *

瀏覽器問題

  • Firefox 加載 Demo 頁面失敗 (issue)
  • Edge 加載沒有 CORS header 的模塊腳本 (issue)。 已在 Edge 16 中修復!

沒有憑據

<!-- 使用憑據獲取(cookie 等) -->
<script src="1.js"></script>

<!-- 不使用憑據獲取 -->
<script type="module" src="1.mjs"></script>

<!-- 使用憑據獲取 -->
<script type="module" crossorigin src="1.mjs?"></script>

<!-- 不適用憑據獲取 -->
<script type="module" crossorigin src="https://other-origin/1.mjs"></script>

<!-- 使用憑據獲取 -->
<script type="module" crossorigin="use-credentials" src="https://other-origin/1.mjs?"></script>

在線演示

如果請求來自相同的源,大多數基於 CORS 的 API 會發送憑據(cookie 等),但是 fetch() 和模塊腳本卻是例外的——非您要求它們,否則它們不會發送憑據除。

您可以通過添加 crossorigin 屬性來向同源模塊添加憑據(這對我來說似乎有點奇怪,我在規範中對此提出質疑)。如果您打算向其他的源也發送憑據,使用 crossorigin="use-credentials"。注意其他源必須使用 Access-Control-Allow-Credentials:true 的 header 來響應。

此外,還有一個與“模塊只執行一次”規則相關的問題。模塊由其URL標記,因此如果您請求沒有憑據的模塊,然後使用憑據請求它,您將獲得相同的無憑證模塊。 這就是爲啥我在上面的URL中使用 問號 ? 的原因,使它們成爲唯一的。

更新: 上面的情況可能很快就會發生改變。fetch() 和模塊腳本默認都會向同源的 URL 發送憑據。Issue

瀏覽器問題

  • Chrome 使用憑據請求同源模塊(issue。已在 Chrome 61 中修復!
  • Safari 即使添加了 crossorigin 屬性,也不使用憑據請求同源模塊(issue)。
  • Edge 即使添加了 crossorigin 屬性,也不使用憑據請求同源模塊(issue。已在 Edge 16 中修復!
  • Edge 使用憑據請求同源模塊(issue)。

MIME 類型

不同於常規腳本,模塊腳本必須是有效的 JavaScript MIME 類型中的一種類型,否則模塊就不會執行。HTML 標準建議使用 text/javascript
瀏覽器問題

  • Edge 使用無效的 MIME 類型執行腳本(issue

這就是我目前學到的內容啦。毋庸置疑,我對 ES 模塊登陸瀏覽器感到非常興奮!

性能建議,動態導入等等!

請查閱有關 Web Fundamentals 的文章,深入瞭解模塊使用情況。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章