原文
ECMAScript modules in browsers
1. 序
es module 现在可以在大多数的浏览器中执行,但是有版本限制。只需要在 script 标签上加上 type=module
就可以。
对于 module 概念的介绍可以看这一篇 es6-modules-in-depth,但我会从另一方面讲解我所理解到的内容。
2. import 的内容必须是有效的路径或者包名称
javascript
// 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,符合 URL 规范,也就是说,可以通过
new URL(modulrURL)
解析不报错 - 以
/
,或./
开头,相对于当前目录 - 以
../
开头 相对于上一级目录
3. 使用 nomodule 字段适配不兼容的浏览器
html
<script type="module" src="module.mjs"> </script>
<script nomodule src="fallback.js"></script>
浏览器如果可以解析 type=module
,则会忽略带有 nomodule
属性的脚本。这说明,你可以在支持模块方案的浏览器提供模块化的代码,不支持的提供兼容的回退方案。
4. type=module 默认支持 defer
html
<!-- 1-->
<script type="module" src="1.mjs"></script>
<!-- 2-->
<script src="2.js"></script>
<!-- 3 -->
<script defer src="3.js"></script>
上述三个脚本的执行顺序是,2 > 3 > 1
.
通常来说,脚本的加载阻塞 HTML 的解析是个很不好的体验,用户会看到很久的白屏或者卡顿现象。所以对于一般的脚本,都应该使用 defer 属性,来实现脚本的异步加载。defer 属性会异步加载脚本,并且会在文档解析完之后再执行脚本,每个含有 defer 属性的脚本之间,按照顺序依次执行。
带有 type=module
属性的脚本默认就是按照 defer 行为执行的,不会阻塞 HTML 的解析,并且它和带有 defer 属性的脚本按照顺序依次执行。
5. 内联的脚本也支持设置 defer ????
html
<!-- A -->
<script type="module">
addTextToBody("Inline module executed");
</script>
<!-- B -->
<script src="1.js"></script>
<!-- C -->
<script defer>
addTextToBody("Inline script executed");
</script>
<!-- D -->
<script defer src="2.js"></script>
上面的执行顺序依次是 B > C > A > D
普通的内联脚本,会忽略 defer
属性,并且立即加载执行,阻塞 HTML 解析。 带有 type=module
的内联脚本,则是永远是 defer
的,即使没有任何内容,也会被延迟执行,在文档解析后。
6. async 在 inline 和 src 脚本上的表现
html
<!-- import 加载完就执行 -->
<script async type="module">
import {addTextToBody} from './utils.mjs';
addTextToBody('Inline module executed.');
</script>
<!-- 加载完就执行 -->
<script async type="module" src="1.mjs"></script>
先下载下来的先执行。
和常规的脚本一样,async 也会异步加载,不会阻塞 HTML 解析。但是因为它加载完就执行,所以,不会按照各个脚本之间的在 DOM 中的顺序有序的执行,而是先加载完的先执行。
7. type=module 的脚本只会执行一次
html
<!-- 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 module 中,你可以引入很多次,但是它们只会执行一次。所以,这个规则放到 HTML 里面依然有效,每个 module 的脚本只会在每个 html page 执行一次。
8. type=module 的脚本默认是 CORS
html
<!-- 不会执行,因为不同源 -->
<script type="module" src="https://....now.sh/no-cors"></script>
<!-- 不会执行,因为 import 的不是同源内容-->
<script type="module">
import 'https://....now.sh/no-cors';
addTextToBody("This will not execute.");
</script>
<!-- 会执行,因为同源 -->
<script type="module" src="https://....now.sh/cors"></script>
和普通脚本不同,带有模块标识的脚本和它们内部的 import 资源,都必须是同源的才会被加载。这也说明非同源的j脚本必须返回有效的 CORS header,比如 Access-Control-Allow-Origin: *
9. 默认发送身份信息
大多数符合 CORS 的 API 加载时候都会发送类似 cookie 之类的认证信息,但是曾经有一段时间,fetch()
和 module 是不在其中的。但是现在,这一点有所变化,fetch()
的和 module 脚本的表现和支持 CORS 的 API 表现一致。
- 旧浏览器,不符合当时规范的,默认会发送身份信息给同源的地址。
- 旧浏览器,支持当时的规范的,默认不会发送凭证信息给同源的地址。
- 最新的浏览器,一般都支持规范,会默认发送凭证信息给同源的地址。 如果你遇到了这个问题,你可以添加
crossorigin
属性,它会给同源的请求带上身份信息,不会给跨域的请求加上 cookie 等身份信息。如果浏览器支持新的规范,则这个属性不会产生任何效果,很安全。
html
<!-- Fetched with credentials (cookies etc) -->
<script src="1.js"></script>
<!-- Fetched with credentials, except in old browsers that follow the old spec -->
<script type="module" src="1.mjs"></script>
<!-- Fetched with credentials, in browsers that follow the old & new spec -->
<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>
10. MIME 类型
和常规的脚本不一样,module 脚本必须使用有效的 javascript MIME
类型之一,否则,它们将无法执行, 一般推荐使用 text/javascript
。以上就是我学到的关于 module 的内容,
11. 更多
可以看看这个文章What are JS modules?
补充 crossorigin (from gpt)
crossorigin
属性用于指定如何处理跨域资源加载的请求。
当加载外部资源(如 <script>
、<link>
或 <img>
标签中的资源)时,浏览器会根据 crossorigin
属性的设置来确定如何处理跨域请求。
crossorigin
属性有以下几种取值:
anonymous
:表示资源请求不包含凭证信息(如 cookies),并且不会触发浏览器的 CORS 机制。这通常用于加载匿名资源,不需要进行身份验证的情况。use-credentials
:表示资源请求会包含凭证信息(如 cookies),并且会触发浏览器的 CORS 机制。这通常用于加载需要进行身份验证的资源。- 空字符串或未设置:表示浏览器会根据请求的 URL 和当前页面的协议、域名和端口等信息自动确定是否发送凭证信息。如果请求的 URL 与当前页面具有相同的协议、域名和端口,则会发送凭证信息;否则,不会发送凭证信息。
使用 crossorigin
属性可以控制跨域资源加载的行为,确保在需要发送凭证信息或进行身份验证的情况下能够正确处理跨域请求。这可用于保护用户的敏感信息,并提供更精细的控制跨域资源共享的机制。