聊一聊原生浏览器中的模块

简介: 自从ES2015定稿以来,我们通过 Babel 等转换工具可以在项目中直接使用【模块】。前端模块化开发已经是不可逆转,在 ECMAScript module 之前我们通过 requirejs、seajs、LABjs,甚至最早的时候我们通过闭包来实现模块化开发。

自从ES2015定稿以来,我们通过 Babel 等转换工具可以在项目中直接使用【模块】。前端模块化开发已经是不可逆转,在 ECMAScript module 之前我们通过 requirejsseajsLABjs,甚至最早的时候我们通过闭包来实现模块化开发。目前一些主流的的浏览器厂商已经在他们新版的浏览器中原生支持了【模块】,今天我们就来原生浏览器中的模块到底如何。

目前原生支持模块用法的浏览器有:

  • 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

不支持裸导入(不能通过模块名直接导入)

一个合格的模块标识符必须满足下列条件之一:

  • 一个完整的非相对URL。通过 new URL(moduleSpecifier) 使用时不会报错。
  • 以 / 开头。
  • 以 ./ 开头。
  • 以 ../ 开头。

保留其他说明符供将来使用,如导入内置模块。

// 支持:
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

默认延迟加载

当网络状况不好的时候,脚本加载会阻塞浏览器解析 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

内联模块也是延迟加载的

唱过脚本会忽略 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

内联/外联 模块都支持异步加载

在普通脚本中,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

模块只执行一次

如果你使用过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

遵循 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

不需要凭证

针对同源请求,大部分基于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

这里还有一个关于 模块只执行一次 的坑。当你通过一个URL引入一个模块时,如果一开始你以无凭证的方式请求,然后又以有凭证的方式再请求一次,你得到的返回都是无凭证请求的那次。这就是我为什么会在第二次请求时在URL后加上,用于区分两次请求。

更新:以上可能很快会改变。 默认情况下,fetch()和模块脚本都会向相同来源的URL发送凭据。

Mime-types

与普通脚本不同,模块脚本必须提供有效的JavaScript MIME类型,否则它们将不会执行。 HTML标准建议使用 text/javascript 。


原文发布时间为:2018年07月02日

作者:掘金
本文来源:掘金 如需转载请联系原作者

相关文章
|
18天前
|
JavaScript
【归总】原生js操作浏览器hash、url参数参数获取/修改方法合集
【归总】原生js操作浏览器hash、url参数参数获取/修改方法合集
|
5月前
|
编译器 Linux C语言
Qt浏览器模块的几点说明
Qt浏览器模块的几点说明
|
7月前
|
Web App开发 JavaScript 前端开发
在 SAP UI5 应用中使用浏览器原生的 Fetch API 发起网络请求试读版
在 SAP UI5 应用中使用浏览器原生的 Fetch API 发起网络请求试读版
35 0
|
8月前
|
前端开发 开发者
将近20年,CSS终于在所有现代浏览器实现了原生嵌套语法!!!-3
使用&进行精确控制和灵活性 假设您想要选择.demo元素,并使用:not()选择器。这时就需要使用&:
127 0
|
8月前
|
前端开发
将近20年,CSS终于在所有现代浏览器实现了原生嵌套语法!!!
选择任何三角形和正方形 这个任务需要选择多个嵌套元素,也称为组选择器。 不使用嵌套,现在的CSS有两种方式:
68 0
|
8月前
|
Web App开发 前端开发 数据可视化
将近20年,CSS终于在所有现代浏览器实现了原生嵌套语法!!!
引言 将近20年,CSS终于在所有现代浏览器实现了原生嵌套语法,是时候淘汰less/sass等预处理器了
91 0
|
JavaScript
js:在浏览器中使用原生的 ESM
js:在浏览器中使用原生的 ESM
150 0
|
前端开发 JavaScript 编译器
使用Skypack在浏览器上直接导入ES模块
使用Skypack在浏览器上直接导入ES模块
258 0
使用Skypack在浏览器上直接导入ES模块
|
安全 JavaScript 前端开发
浏览器内核之WebKit 架构与模块
此文章是我最近在看的【WebKit 技术内幕】一书的一些理解和做的笔记。 而【WebKit 技术内幕】是基于 WebKit 的 Chromium 项目的讲解。
471 0
浏览器内核之WebKit 架构与模块
|
Web App开发 数据采集 JavaScript
16、web爬虫讲解2—PhantomJS虚拟浏览器+selenium模块操作PhantomJS
【http://bdy.lqkweb.com】 【http://www.swpan.cn】 【转载自:http://www.lqkweb.com】 PhantomJS虚拟浏览器 phantomjs 是一个基于js的webkit内核无头浏览器 也就是没有显示界面的浏览器,利用这个软件,可以获取到网址js加载的任何信息,也就是可以获取浏览器异步加载的信息 下载网址:http://phantomjs.
1274 0

热门文章

最新文章