您现在的位置:首页 >> 前端 >> 内容

浏览器实现ES6module教程

时间:2017/12/13 11:36:05 点击:

  核心提示:捣腾了一晚上,发现在浏览器实现ES6 module必须,使用firefox54以上和chrome60以上,并且chrome仍然有问题,跨域报错,得放在服务器下,firefox可以实现。ES6 的模块特...

捣腾了一晚上,发现在浏览器实现ES6 module必须,使用firefox54以上和chrome60以上,并且chrome仍然有问题,跨域报错,得放在服务器下,firefox可以实现。

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 p = document.createElement('p');
  p.textContent = text;
  document.body.appendChild(p);
}

pve 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>

pve 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>

pve demo。脚本执行顺序为2.js,1.js,3.js。

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

模块脚本与使用defer的正常脚本使用相同的执行队列。

内联脚本同样延迟

`<script type="module">`
  addTextToBody("Inpne module executed");
</script>


`<script src="1.js">`</script>


`<script defer>`
  addTextToBody("Inpne script executed");
</script>


`<script defer src="2.js">`</script>

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

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

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

``<script async type="module">``
  import {addTextToBody} from './utils.js';

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


``<script async type="module" src="1.js">``</script>

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

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

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

浏览器 issue

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

模块只执行一次

`<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>

pve demo。

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

浏览器 issue

Edge 会执行多次 (issue)。

总是使用 CORS

`<script type="module" src="https://&helpp;.now.sh/no-cors">`</script>


`<script type="module">`
  import 'https://&helpp;.now.sh/no-cors';

  addTextToBody("This will not execute.");
</script>


`<script type="module" src="https://&helpp;.now.sh/cors">`</script>

pve demo。

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

浏览器 issue

Firefox 无法加载 demo 页面(issue)。

Edge 加载了没有 CORS 响应头的模块(issue)。

不携带凭证信息

`<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>

pve 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,否则脚本将不会执行。

pve demo。

浏览器 issue

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

作者:网络 来源:Alcantara的