在瀏覽器中高效使用JavaScript module(模塊)


在瀏覽器中也可以使用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.js1.js3.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在一個頁面上只會執行一次。

瀏覽器問題

  • Edge瀏覽器會多次執行 modules (issue).

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頭信息。

瀏覽器問題

  • Firefox沒有成功的加載演示頁面 (issue).
  • Edge瀏覽器加載了沒有 CORS headers 的 module scripts (issue)。

沒有憑證信息(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變的不同。

瀏覽器問題

  • 谷歌瀏覽器在請求同源 modules 時帶有 credentials (issue).
  • Safari 請求同源 modules 時不帶有 credentials,即使你使用了 crossorigin 屬性標志。 (issue).
  • Edge瀏覽器完全相反。它缺省狀態下對同源請求 modules 時帶有 credentials 而當你指定了 crossorigin 屬性后反倒沒了。 (issue).

火狐瀏覽器是唯一做的正確的瀏覽器,干的漂亮!

Mime-types 文檔類型

跟普通的scripts不一樣,modules scripts 必須指定一個有效的 JavaScript MIME 類型,否則將不會執行。

瀏覽器問題

  • Edge瀏覽器在無效MIME types下也執行 (issue).

這些就是目前我學到的。毋庸置疑,ES modules能夠登陸瀏覽器,已經讓我興奮不已了。你呢?


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM