翻译自:ECMAScript modules in browsers
目前大部分浏览器已支持ES module:
- Safari 10.1.
- Chrome 61.
- Firefox 60.
- 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
属性,浏览器就会该script(行内或外部)识别为ECMAScript module.
关于es module这里已有很多很好的文章,但是我还是想分享一些关于浏览器特性的内容:
# 目前import不支持加载内部模块的写法
// Supported: 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';
// Not supported: import {foo} from 'bar.mjs';
import {foo} from 'utils/bar.mjs';
目前加载的模块路径只支持以下形式:
- 完整的URL. As in, it doesn’t throw an error when put through new URL(moduleSpecifier).
- 以
/.
开头 - 以
./.
开头 - 以
../.
开头
其他路径符保留用于以后使用,比如用于支持加载内部模块。
# 向后兼容
<script type="module" src="module.mjs"></script>
<script nomodule src="fallback.js"></script>
对于支持type=module
的浏览器,会忽略指定nomodule
属性的标签。从而对于不支持es module的浏览器提供一种回退的方案。
# 默认延迟加载
<!-- This script will execute after… -->
<script type="module" src="1.mjs"></script>
<!-- …this script… -->
<script src="2.js"></script>
<!-- …but before this script. -->
<script defer src="3.js"></script>
执行顺序为: 2.js
,1.mjs
,3.js
。
一般的script标签在加载的时候会阻塞html的解析,可以通过defer
属性使script在文档解析完成之后再加载,从而避免阻塞,同时执行顺序为出现包含defer
属性的标签顺序。
module script默认行为类似defer
属性,并且没有办法让module script像普通的script那样在加载时阻塞html解析。
module script和存在defer
属性的普通script使用相同的执行队列。
译者注: defer在 parse html完成之后,DOMContentLoaded之前,按出现顺序执行。
# 行内script也会延迟加载
<!-- This script will execute after… -->
<script type="module">
addTextToBody("Inline module executed");
</script>
<!-- …this script… -->
<script src="1.js"></script>
<!-- …and this script… -->
<script defer>
addTextToBody("Inline script executed");
</script>
<!-- …but before this script. -->
<script defer src="2.js"></script>
执行顺序为: 1.js
,inline script, inline module, 2.js
。
一般的行内script标签会忽略defer
属性,而行内module script则总是延迟加载的,不管有没有import相关资源。
# 在外部module和行内module上使用async
<!-- This executes as soon as its imports have fetched -->
<script async type="module">
import {addTextToBody} from './utils.mjs';
addTextToBody('Inline module executed.');
</script>
<!-- This executes as soon as it & its imports have fetched -->
<script async type="module" src="1.mjs"></script>
执行顺序: 下载快的先执行,下载慢的后执行。
和一般的script一样,async
使script不会阻塞html的解析,以及一旦下载完成就开始执行。
和一般的script的区别在于,async
可以作用在行内module上。
译者注: 对于一般的script,async
只能作用在外部脚本上;
# 模块仅执行一次
<!-- 1.mjs only executes once -->
<script type="module" src="1.mjs"></script>
<script type="module" src="1.mjs"></script>
<script type="module">
import "./1.mjs";
</script>
<!-- Whereas classic scripts execute multiple times -->
<script src="2.js"></script>
<script src="2.js"></script>
如果你已经了解了ES module,那你应该知道一个模块可以被import多次,但是只执行一次。同样的,对于module script,相同的url只会执行一次。
Browser issues
Edge executes modules multiple times (issue). Fixed, but not yet shipped (expect Edge 17 to ship with the fix).
# CORS限制
<!-- This will not execute, as it fails a CORS check -->
<script type="module" src="https://….now.sh/no-cors"></script>
<!-- This will not execute, as one of its imports fails a CORS check -->
<script type="module">
import 'https://….now.sh/no-cors';
addTextToBody("This will not execute.");
</script>
<!-- This will execute as it passes CORS checks -->
<script type="module" src="https://….now.sh/cors"></script>
和一般的script不同,加载module script(以及import的脚本)会检查CORS,所以对于跨域的module script必须返回有效的CROS请求头,例如Access-Control-Allow-Origin: *
。
# No credentials
<!-- Fetched with credentials (cookies etc) -->
<script src="1.js"></script>
<!-- Fetched without credentials -->
<script type="module" src="1.mjs"></script>
<!-- Fetched with credentials -->
<script type="module" crossorigin src="1.mjs?"></script>
<!-- Fetched without credentials -->
<script type="module" crossorigin src="https://other-origin/1.mjs"></script>
<!-- Fetched with credentials-->
<script type="module" crossorigin="use-credentials" src="https://other-origin/1.mjs?"></script>
对于大部分基于CORS的API,如果请求来自同源则会发送凭证信息(例如cookie),但是fetch()
和module script除外–默认它们不会发送凭证信息。
对于同源的模块可以通过添加crossorigin
属性发送凭证信息(虽然属性名看起来有点奇怪)。如果想要发送凭证到其他域,则需要设置crossorigin="use-credentials"
,同时其他域的响应的头部应该带有Access-Control-Allow-Credentials: true
。
目前我们已经了解了”模块只会执行一次”的规则。URL作为key来区分不同模块,所以如果先请求了一个不带凭证信息的模块,然后又请求了带凭证信息的模块,那么代码得到的还是不带凭证信息的模块。这就是为什么在上面的示例中,我在URL的后面添加了一个?
。
Update: 目前已作调整,在同源的情况下,fetch()
和module script会带上凭证信息。Issue
# Mime类型
和一般的script不同,module script必须是合法的javascript MIME类型,否则不会被执行。 The HTML Standard推荐使用text/javascript
。
这是目前我所了解到的。我对ES module登录浏览器真真感到贼拉兴奋。
# Performance recommendations, dynamic import & more!
查看 article on Web Fundamentals ,学习更多关于模块的用法。