自从ES2015定稿以来,我们通过 Babel 等转换工具可以在项目中直接使用【模块】。前端模块化开发已经是不可逆转,在 ECMAScript module 之前我们通过 requirejs、seajs、LABjs,甚至最早的时候我们通过闭包来实现模块化开发。目前一些主流的的浏览器厂商已经在他们新版的浏览器中原生支持了【模块】,今天我们就来原生浏览器中的模块到底如何。
目前原生支持模块用法的浏览器有:
-
Safari 10.1
-
Chrome 61
-
Firefox 60
-
Edge 16
要使用原生浏览器的模块,你只需要在 script 标签上添加一个 type=module 属性, 浏览器就会把这个脚本(内联脚本或者外联脚本)当作模块来处理。
<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
);
在线Demo(https://cdn.rawgit.com/jakearchibald/a298d5af601982c338186cd355e624a8/raw/aaa2cbee9a5810d14b01ae965e52ecb9b2965a44/)
不支持裸导入(不能通过模块名直接导入)
一个合格的模块标识符必须满足下列条件之一:
保留其他说明符供将来使用,如导入内置模块。
// 支持:
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'
;
通过 nomodule 向后兼容
如果当前浏览器支持 type=module 标签的话会自动忽略 nomodule 标签。这意味着你可以将模块暴露给支持模块的浏览器,同时可以给不支持模块的浏览器提供兼容方案。
<script
type
=
"module"
src
=
"module.mjs"
>
</script>
<script
nomodule src
=
"fallback.js"
>
</script>
在线Demo(https://cdn.rawgit.com/jakearchibald/6110fb6df717ebca44c2e40814cc12af/raw/7fc79ed89199c2512a4579c9a3ba19f72c219bd8/)
默认延迟加载
当网络状况不好的时候,脚本加载会阻塞浏览器解析 HTML。通常我们可以通过在 script 标签上使用 defer 属性来解决阻塞问题,但是这也会造成脚本只有在文档解析完成后才执行,同时还要兼顾其他延迟脚本的执行顺序。默认情况下模块脚本的表现类似于 defer — 它不会阻塞 HTML 的解析。
模块脚本的执行队列与使用了 defer 的常规脚本一致。
<!--
这个脚本执行滞后于…
-->
<script
type
=
"module"
src
=
"1.mjs"
>
</script>
<!--
…这个脚本…
-->
<script
src
=
"2.js"
>
</script>
<!--
…但是先于这个脚本
-->
<script
defer src
=
"3.js"
>
</script>
在线Demo(https://cdn.rawgit.com/jakearchibald/d6808ea2665f8b3994380160dc2c0bc1/raw/c0a194aa70dda1339c960c6f05b2e16988ee66ac/)
内联模块也是延迟加载的
唱过脚本会忽略 defer 然而内联模块总是 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>
在线Demo(https://cdn.rawgit.com/jakearchibald/7026f72c0675898196f7669699e3231e/raw/fc7521aabd9485f30dbd5189b407313cd350cf2b/)
内联/外联 模块都支持异步加载
在普通脚本中,async 能让脚本的下载不阻塞HTML的解析并在下载完成后尽快执行。和普通脚本不同,内联模块脚本支持异步加载的。
同样的,异步加载的模块可能不会按照它们在DOM中出现的顺序执行。
<!--
这个会在它引入的脚本加载完成后立即执行
-->
<script
async type
=
"module"
>
import
{
addTextToBody
}
from
'./utils.mjs'
;
addTextToBody
(
'Inline module executed.'
);
</script>
<!--
这个会在其本身以及其引入的脚本加载完成后立即执行
-->
<script
async type
=
"module"
src
=
"1.mjs"
>
</script>
在线Demo(https://module-script-tests-sreyfhwvpq.now.sh/async)
模块只执行一次
如果你使用过ES6的模块, 那么你肯定知道你可以多次引入同一模块但是他们只会执行一次。在Html中也一样, 一个URL模块脚本在一个页面中只会执行一次。
<!--
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>
在线Demo(https://cdn.rawgit.com/jakearchibald/f7f6d37ef1b4d8a4f908f3e80d50f4fe/raw/1fcedde007a2b90049a7ea438781aebe69e22762/)
遵循 CORS
不同于普通脚本,跨站引用模块脚本(及其引入)需要遵循CORS。 这意味着跨源模块脚本必须返回有效的CORS头,例如Access-Control-Allow-Origin:*。
<!--
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>
在线Demo(https://cdn.rawgit.com/jakearchibald/2b8d4bc7624ca6a2c7f3c35f6e17fe2d/raw/fe04e60b0b7021f261e79b8ef28b0ccd132c1585/)
不需要凭证
针对同源请求,大部分基于CORS的API需要请求带上凭证(如:cookie),但是 fetch() 和模块脚本是个例外,他们默认不会带上相关凭证除非你明确指定。
如果你想在同源请求模块脚本时带上凭证,可以设置 crossorigin 属性。如果跨站请求也想带上的话,可以设置 crossorigin="use-credentials",需要注意的是跨站的站点需要在请求返回头中加上 Access-Control-Allow-Credentials: true。
<!--
有凭证
(
cookies
)
-->
<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>
在线Demo(https://module-script-tests-sreyfhwvpq.now.sh/cookie-page)
这里还有一个关于 模块只执行一次 的坑。当你通过一个URL引入一个模块时,如果一开始你以无凭证的方式请求,然后又以有凭证的方式再请求一次,你得到的返回都是无凭证请求的那次。这就是我为什么会在第二次请求时在URL后加上?,用于区分两次请求。
更新:以上可能很快会改变。 默认情况下,fetch()和模块脚本都会向相同来源的URL发送凭据。
Mime-types
与普通脚本不同,模块脚本必须提供有效的JavaScript MIME类型,否则它们将不会执行。 HTML标准建议使用 text/javascript 。
【关于投稿】
如果大家有原创好文投稿,请直接给公号发送留言。
① 留言格式:
【投稿】+《 文章标题》+ 文章链接
② 示例:
【投稿】《不要自称是程序员,我十多年的 IT 职场总结》:http://blog.jobbole.com/94148/
③ 最后请附上您的个人简介哈~
jquery.repo
##jQuery 插件允许使用 cdn.rawgit.com 获取 github 存储库的最新提交
github 不会让你直接链接到他们的网站; raw.githubusercontent.com 使用X-Content-
Type
-Options:nosniff标头发送其内容,因此现代
浏览器
不会将其作为 java
script
接受。
通过提取原始文件并使用更宽松的标头重新提供它来解决这个问题,但速率受到限制,因此您无法在公共站点上使用它。 没有被限制,而是被永久缓存。 获取给定 URL 后,它会保留在缓存
中
,如果文件在 github 上更新,则不会在 cdn.rawgit.com 上。 所以有一个脚本标签[removed]可以让你从 github 获取脚本,但
来源:https://jakearchibald.com/2017/es-
module
s-in-browsers/
https://wolfx.cn/ecma
script
-
module
s-in-browsers/
在
浏览器
中
也可以使用 Java
Script
module
s(
模块
功能)了。目前支持这一特性的
浏览器
包括:
Safari 10.1.
谷歌
浏览器
(Canary 6...