瀏覽器中的 ES6 module 實現

原文鏈接:https://www.zcfy.cc/article/ecmascript-modules-in-browsers-2744.html

瀏覽器中的 ES6 module 實現

原文鏈接: jakearchibald.com

ES6 的模塊特性(module) 開始在瀏覽器端實現啦!一切正在路上...

瀏覽器 備註
Safari 10.1 (無)
Chrome Canary 60 打開 chrome:flags 啓用“實驗性網絡平臺功能”
Firefox 54 打開 about:config 啓用 dom.moduleScripts.enabled
Edge 15 打開 about:flags 啓用“實驗性 JavaScript 功能”
<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);
}

Live demo

只需爲 script 元素添加 type=module 屬性,瀏覽器就會把該元素對應的內聯腳本或外部腳本當成 ECMAScript 模塊進行處理。

目前已經有一些 很棒的關於 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';

有效的路徑符號應當符合以下條件規則之一:

  • 完整的非相對路徑。這樣在將其傳給new URL(moduleSpecifier)的時候纔不會報錯。
  • 以 / 開頭。
  • 以 ./ 開頭。
  • 以 ../ 開頭。

其他形式的符號被保留下來,未來將用於其他功能(如引入[import]內置模塊)。

使用 nomodule 屬性向後兼容

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

Live demo

支持 type=module 的瀏覽器將會忽略帶有 nomodule 屬性的 script 標籤。這意味着我們可以爲支持模塊的瀏覽器提供模塊形式的代碼,同時爲那些不支持模塊的瀏覽器提供降級處理。

瀏覽器 issue

  • Firefox 暫不支持 nomodule (issue)。
  • Edge 暫不支持 nomodule (issue)。
  • Safari 10.1 暫不支持 nomodule,但在最新的技術預覽版中已經解決了此問題。對於 10.1 來說,有一個相當棒的解決方案

默認延遲執行

<!-- 這個腳本會在… -->
<script type="module" src="1.js"></script>

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

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

Live demo。腳本執行順序爲 2.js1.js3.js

獲取腳本會導致 HTML parser 阻塞,這簡直太太太太噁心了。對正常的腳本,我們可以使用 defer 屬性來防止阻塞,腳本將延遲至文檔解析完畢後執行,同時保持與其他使用 defer 的腳本之間的執行順序。模塊腳本的默認行爲與 defer 相同 —— 無法使模塊腳本阻塞 HTML parser。

模塊腳本與使用 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>

Live demo。執行順序依次爲 1.js、內聯腳本、內聯模塊、2.js

正常的內聯腳本會忽略 defer 屬性,而內聯模塊則總是延遲執行,無論是否引入其他內容。

async 對內聯、外部模塊同樣適用

<!-- 腳本將會在其引入的模塊加載完成後立即執行 -->
`<script async type="module">`
  import {addTextToBody} from './utils.js';

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

<!-- 腳本及其引入的模塊加載完成後立即執行 -->
`<script async type="module" src="1.js">`</script>

Live demo。先完成加載的腳本先執行。

與正常腳本相同,帶有 async 屬性的腳本在下載時不會阻塞 HTML parser,一旦加載完畢,立即執行。不同的是,async 對內聯模塊也同樣適用。

使用 async 時,腳本的執行順序可能會和它們在 DOM 中出現的順序不盡相同。

瀏覽器 issue

  • Firefox 不支持內聯模塊使用 async(issue)。

模塊只執行一次

<!-- 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>

Live demo

引入同一個模塊多次的時候,模塊只會執行一次。這對 HTML 中的模塊腳本同樣適用 —— 在同一個頁面中,URL 相同的模塊只會執行一次。

瀏覽器 issue

  • Edge 會執行多次 (issue)。

總是使用 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>

Live demo

與正常腳本不同,模塊腳本(及其引入的腳本)是通過 CORS 獲取的。這意味着,跨域模塊腳本必須返回類似 Access-Control-Allow-Origin: * 這樣的有效的響應頭。

瀏覽器 issue

  • Firefox 無法加載 demo 頁面(issue)。
  • Edge 加載了沒有 CORS 響應頭的模塊(issue)。

不攜帶憑證信息

<!-- 請求腳本時會攜帶相關憑證 (如 cookie) -->
<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>

Live demo

在請求同源的情況下,多數基於 CORS 的 API 都會發送憑證信息(credentials,如 Cookie)。但 fetch() 和模塊腳本恰恰例外 —— 除非手動聲明,否則是不會發送相關憑證的。

對於一個同源的模塊腳本,可以爲其添加 crossorigin 屬性(這看起來挺怪的,我已經在規範中提出這個問題了),這樣在請求時就可以攜帶相關憑證了。如果你還想將憑證發給其他域,請使用 crossorigin="use-credentials"。需要注意的是,接收憑證的域必須返回 Access-Control-Allow-Credentials: true 的響應頭。

此外,還有一個與“模塊只會執行一次”這條規則相關的問題。瀏覽器是通過 URL 來區別不同模塊的,所以如果你先請求了一個模塊而不攜帶任何憑證,緊接着又攜帶憑證信息去請求該模塊,那麼第二次得到的依然是不攜帶憑證的請求所返回的模塊。這正是我在上面代碼中的 URL 後面加上“?”的原因。

瀏覽器 issue

  • 請求同源模塊時,Chrome 會攜帶憑證信息(issue)。
  • 即使添加了 crossorigin 屬性,Safari 在請求同源腳本時也不會攜帶憑證信息(issue)。
  • Edge 則完全弄反了。請求同源模塊時,Edge 默認會發送憑證信息,但如果手動添加了 crossorigin 屬性,則又不會攜帶 (issue)。

Firefox 是唯一正確實現這一點的瀏覽器 —— 好樣的!

Mime-types

不同於普通腳本,對於通過 module 引入的腳本,服務器必須返回合法的 MIME type,否則腳本將不會執行。

Live demo

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