浏览器中的 ES6 module 实现

转载 2018年04月17日 09:20:57

ES6 的模块特性(module) 开始在浏览器端实现啦!一切正在路上...

浏览器备注
Safari 10.1(无)
Chrome Canary 60打开 chrome:flags 启用“实验性网络平台功能”
Firefox 54打开 about:config 启用 dom.moduleScripts.enabled
Edge 15打开 about:flags 启用“实验性 JavaScript 功能”
<script type="module">
  import {addTextToBody} from './utils.js';

  addTextToBody('Modules are pretty cool.');
</script>
// utils.js
export function addTextToBody(text) {
  const div = document.createElement('div');
  div.textContent = text;
  document.body.appendChild(div);
}

Live demo

只需为 script 元素添加 type=module 属性,浏览器就会把该元素对应的内联脚本或外部脚本当成 ECMAScript 模块进行处理。

目前已经有一些 很棒的关于 ECMAScript 模块的文章了,不过我还是想分享一些和浏览器相关的东西,它们都是我在测试代码、阅读规范的过程中学习到的。

尚未得到支持的 import 路径符号

// 支持
import {foo} from 'https://jakearchibald.com/utils/bar.js';
import {foo} from '/utils/bar.js';
import {foo} from './bar.js';
import {foo} from '../bar.js';

// 不支持
import {foo} from 'bar.js';
import {foo} from 'utils/bar.js';

有效的路径符号应当符合以下条件规则之一:

  • 完整的非相对路径。这样在将其传给new URL(moduleSpecifier)的时候才不会报错。
  • 以 / 开头。
  • 以 ./ 开头。
  • 以 ../ 开头。

其他形式的符号被保留下来,未来将用于其他功能(如引入[import]内置模块)。

使用 nomodule 属性向后兼容

<script type="module" src="module.js"></script>
<script nomodule src="fallback.js"></script>

Live demo

支持 type=module 的浏览器将会忽略带有 nomodule 属性的 script 标签。这意味着我们可以为支持模块的浏览器提供模块形式的代码,同时为那些不支持模块的浏览器提供降级处理。

浏览器 issue

  • Firefox 暂不支持 nomodule (issue)。
  • Edge 暂不支持 nomodule (issue)。
  • Safari 10.1 暂不支持 nomodule,但在最新的技术预览版中已经解决了此问题。对于 10.1 来说,有一个相当棒的解决方案

默认延迟执行

<!-- 这个脚本会在… -->
<script type="module" src="1.js"></script>

<!-- …这个脚本之后、… -->
<script src="2.js"></script>

<!-- …这个脚本之前执行。 -->
<script defer src="3.js"></script>

Live demo。脚本执行顺序为 2.js1.js3.js

获取脚本会导致 HTML parser 阻塞,这简直太太太太恶心了。对正常的脚本,我们可以使用 defer 属性来防止阻塞,脚本将延迟至文档解析完毕后执行,同时保持与其他使用 defer 的脚本之间的执行顺序。模块脚本的默认行为与 defer 相同 —— 无法使模块脚本阻塞 HTML parser。

模块脚本与使用 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>

Live demo。执行顺序依次为 1.js、内联脚本、内联模块、2.js

正常的内联脚本会忽略 defer 属性,而内联模块则总是延迟执行,无论是否引入其他内容。

async 对内联、外部模块同样适用

<!-- 脚本将会在其引入的模块加载完成后立即执行 -->
`<script async type="module">`
  import {addTextToBody} from './utils.js';

  addTextToBody('Inline module executed.');
</script>

<!-- 脚本及其引入的模块加载完成后立即执行 -->
`<script async type="module" src="1.js">`</script>

Live demo。先完成加载的脚本先执行。

与正常脚本相同,带有 async 属性的脚本在下载时不会阻塞 HTML parser,一旦加载完毕,立即执行。不同的是,async 对内联模块也同样适用。

使用 async 时,脚本的执行顺序可能会和它们在 DOM 中出现的顺序不尽相同。

浏览器 issue

  • Firefox 不支持内联模块使用 async(issue)。

模块只执行一次

<!-- 1.js 只执行一次 -->
<script type="module" src="1.js"></script>
<script type="module" src="1.js"></script>
<script type="module">
  import "./1.js";
</script>

<!-- 普通脚本会执行多次 -->
<script src="2.js"></script>
<script src="2.js"></script>

Live demo

引入同一个模块多次的时候,模块只会执行一次。这对 HTML 中的模块脚本同样适用 —— 在同一个页面中,URL 相同的模块只会执行一次。

浏览器 issue

  • Edge 会执行多次 (issue)。

总是使用 CORS

<!-- 模块不会执行,因其未通过 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>

Live demo

与正常脚本不同,模块脚本(及其引入的脚本)是通过 CORS 获取的。这意味着,跨域模块脚本必须返回类似 Access-Control-Allow-Origin: * 这样的有效的响应头。

浏览器 issue

  • Firefox 无法加载 demo 页面(issue)。
  • Edge 加载了没有 CORS 响应头的模块(issue)。

不携带凭证信息

<!-- 请求脚本时会携带相关凭证 (如 cookie) -->
<script src="1.js"></script>

<!-- 不会携带相关凭证 -->
<script type="module" src="1.js"></script>

<!-- 会携带相关凭证 -->
<script type="module" crossorigin src="1.js?"></script>

<!-- 不会携带相关凭证 -->
<script type="module" crossorigin src="https://other-origin/1.js"></script>

<!-- 会携带相关凭证-->
<script type="module" crossorigin="use-credentials" src="https://other-origin/1.js?"></script>

Live demo

在请求同源的情况下,多数基于 CORS 的 API 都会发送凭证信息(credentials,如 Cookie)。但 fetch() 和模块脚本恰恰例外 —— 除非手动声明,否则是不会发送相关凭证的。

对于一个同源的模块脚本,可以为其添加 crossorigin 属性(这看起来挺怪的,我已经在规范中提出这个问题了),这样在请求时就可以携带相关凭证了。如果你还想将凭证发给其他域,请使用 crossorigin="use-credentials"。需要注意的是,接收凭证的域必须返回 Access-Control-Allow-Credentials: true 的响应头。

此外,还有一个与“模块只会执行一次”这条规则相关的问题。浏览器是通过 URL 来区别不同模块的,所以如果你先请求了一个模块而不携带任何凭证,紧接着又携带凭证信息去请求该模块,那么第二次得到的依然是不携带凭证的请求所返回的模块。这正是我在上面代码中的 URL 后面加上“?”的原因。

浏览器 issue

  • 请求同源模块时,Chrome 会携带凭证信息(issue)。
  • 即使添加了 crossorigin 属性,Safari 在请求同源脚本时也不会携带凭证信息(issue)。
  • Edge 则完全弄反了。请求同源模块时,Edge 默认会发送凭证信息,但如果手动添加了 crossorigin 属性,则又不会携带 (issue)。

Firefox 是唯一正确实现这一点的浏览器 —— 好样的!

Mime-types

不同于普通脚本,对于通过 module 引入的脚本,服务器必须返回合法的 MIME type,否则脚本将不会执行。

Live demo

浏览器 issue

  • Edge 仍将执行 MIME type 非法的脚本(issue)。

以上就是我目前所学习到的所有内容。浏览器开始支持 ES6 模块,简直太开心啦~

es6 javascript的模块module(上)

ES6 的 Class 只是面向对象编程的语法糖, 升级了 ES5 的构造函数的原型链继承的写法, 并没有解决模块化问题。 Module 功能就是为了解决这个问题而提出的。历史上, JavaScrip...
  • qq_30100043
  • qq_30100043
  • 2016-12-12 18:41:26
  • 1385

ES6之Module 的加载实现(1)

ES6
  • tian361zyc
  • tian361zyc
  • 2017-06-01 18:55:24
  • 499

从零开始学_JavaScript_系列(68)——es6模块的使用

1、es6模块用于浏览器中标准写法是: console.log('module') 按照预期效果是会非堵塞加载,然后再执行。事实上是里面的代码根本不会执行。又比如说: 你以为会...
  • qq20004604
  • qq20004604
  • 2017-09-17 23:05:19
  • 716

ES6之Module 的加载实现(2)

ES6
  • tian361zyc
  • tian361zyc
  • 2017-06-01 19:12:08
  • 182

ES6的模块Module

ES6的Class只是面向对象编程的语法糖,升级了ES5的构造函数的原型链继承的写法,并没有解决模块化问题。Module功能就是为了解决这个问题而提出的。历史上,JavaScript一直没有模块(mo...
  • itpinpai
  • itpinpai
  • 2016-08-26 13:53:08
  • 2409

ES6 初体验 —— gulp+Babel 搭建ES6环境

今天在学ES6,学到ES不定参数和默认参数,发现很好用,完全可以抛弃arguments对象了,arguments是一个类数组对象,虽然有length,而且可以用索引访问各个元素,但是绝大多数开发者也许...
  • Esther_Heesch
  • Esther_Heesch
  • 2016-10-31 00:20:09
  • 4709

初步探究ES6之module模块化

多人开发javascript时伴随着命名冲突等问题,先后有了模拟块级作用域、命名空间、模块化开发等方法。之前,模块化开发一直是由第三方库来模拟的,比较知名的有AMD规范和CMD规范。两个规范分别对应r...
  • mevicky
  • mevicky
  • 2015-11-25 16:42:30
  • 2799

ES6 Class Module模块化 案例

前言这篇通过简单说明ES6 Class和Module这个两个成员。并且用这两个成员制造模块化。Class说明Class(类)作用对象模块,也就是一个方法集合,像以前的prototype父类一样,也一样...
  • rth362147773
  • rth362147773
  • 2017-08-14 00:42:18
  • 302

解决让浏览器兼容ES6特性

转载地址:http://www.rockyxia.com/?p=669 为什么ES6会有兼容性问题? 由于广大用户使用的浏览器版本在发布的时候也许早于ES6的定稿和发布,而到了...
  • wangaiheng
  • wangaiheng
  • 2017-02-22 11:24:29
  • 19034

angular1.x + ES6开发风格记录

angular1.x和ES6开发风格 一、Module ES6有自己的模块机制,所以我们要通过使用ES6的模块机制来淡化ng的框架,使得各业务逻辑层的看不出框架的痕迹,具体的做法是: 把各功能模块...
  • qq_28506819
  • qq_28506819
  • 2017-07-24 14:56:03
  • 590
收藏助手
不良信息举报
您举报文章:浏览器中的 ES6 module 实现
举报原因:
原因补充:

(最多只允许输入30个字)