原文連接: ECMAScript modules in browsers做者:Jake Archibaldjavascript
瀏覽器如今可使用 ES 模塊(module)了!它們是:html
<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); }
在線演示java
您只須要在 script 元素上添加 type=module
,瀏覽器就會將內聯腳本或外部腳本做爲 ECMAScript module 處理。git
關於模塊(module)已經有一些很棒的文章,可是我想分享一些在我測試和閱讀規範的時候學到的瀏覽器特有的內容。es6
// 已支持: 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';
有效的模塊路徑說明符必須符合下列條件之一:github
new URL(moduleSpecifier)
的時候纔不會報錯。/
開頭的。./
開頭的。../
開頭的。其餘形式的說明符保留供未來使用,例如導入內置模塊。web
<script type="module" src="module.mjs"></script> <script nomodule src="fallback.js"></script>
在線演示跨域
支持 type=module
的瀏覽器會忽略屬性爲 nomodule
的腳本。這意味着您能夠給支持模塊的瀏覽器提供模塊樹,同時給其餘瀏覽器提供一個降級版本。瀏覽器
nomodule
(issue)nomodule
(issue)nomodule
<!-- 這個腳本的執行會晚於… --> <script type="module" src="1.mjs"></script> <!-- …這個腳本… --> <script src="2.js"></script> <!-- …可是會在這個腳本以前執行。 --> <script defer src="3.js"></script>
在線演示cookie
執行的順序是: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 中的順序執行。
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的模塊腳本每頁只執行一次。
<!-- 該腳本不會執行, 由於它不能經過 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 響應頭 ,好比 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
的響應頭來響應。
此外,還有一個與「模塊只執行一次」規則相關的問題。模塊由其URL標記,所以若是首次請求了一個模塊而不攜帶憑據,而後再次攜帶憑據請求該模塊,那麼第二次得到的依然是不攜帶憑證的模塊。 這就是爲啥我在上面的URL中使用 問號 ?
的緣由,使它們成爲惟一的。
更新: 上面的狀況可能很快就會發生改變。fetch()
和模塊腳本默認都會向同源的 URL 發送憑據。Issue
crossorigin
屬性,也不使用憑據請求同源模塊(issue)。crossorigin
屬性,也不使用憑據請求同源模塊(issue)不一樣於常規腳本,模塊腳本必須是有效的 JavaScript MIME 類型中的一種類型,不然模塊就不會執行。HTML 標準建議使用 text/javascript
。
這就是我目前學到的內容啦。毋庸置疑,我對 ES 模塊登錄瀏覽器感到很是興奮!
請查閱有關 Web Fundamentals 的文章,深刻了解模塊使用狀況。