在瀏覽器中也可以使用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並不支持。但在Firefox nightly版里已經修復了這個問題。nomodule
屬性 (issue)- 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在一個頁面上只會執行一次。
瀏覽器問題
- 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頭信息。
瀏覽器問題
沒有憑證信息(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能夠登陸瀏覽器,已經讓我興奮不已了。你呢?