原文连接: 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 的文章,深刻了解模块使用状况。