原文鏈接: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 瀏覽器不支持。已在 Firefox nightly 中修復!nomodule
(issue) -
Edge 瀏覽器不支持。已在 Edge 16 中修復!nomodule
(issue) -
Safari 瀏覽器不支持。已在 Safari 11 中修復!對於 10.1,這裏有一個非常聰明的替代辦法。nomodule
默認情況下推遲
<!-- 這個腳本的執行會晚於… -->
<script type="module" src="1.mjs"></script>
<!-- …這個腳本… -->
<script src="2.js"></script>
<!-- …但是會在這個腳本之前執行。 -->
<script defer src="3.js"></script>
執行的順序是:2.js
,1.mjs
,3.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 瀏覽器不支持內聯模塊腳本上的。已在 Firefox 59 中修復!async
(issue)
模塊僅執行一次
<!-- 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: *
。
瀏覽器問題
沒有憑據
<!-- 使用憑據獲取(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 即使添加了。已在 Edge 16 中修復!crossorigin
屬性,也不使用憑據請求同源模塊(issue) - Edge 使用憑據請求同源模塊(issue)。
MIME 類型
不同於常規腳本,模塊腳本必須是有效的 JavaScript MIME 類型中的一種類型,否則模塊就不會執行。HTML 標準建議使用 text/javascript
。
瀏覽器問題
- Edge 使用無效的 MIME 類型執行腳本(issue)
這就是我目前學到的內容啦。毋庸置疑,我對 ES 模塊登陸瀏覽器感到非常興奮!
性能建議,動態導入等等!
請查閱有關 Web Fundamentals 的文章,深入瞭解模塊使用情況。