在浏览器中也可以使用JavaScript modules(模块功能)了。目前支持这一特性的浏览器包括:

  • Safari 10.1.
  • 谷歌浏览器(Canary 60) – 需要在chrome:flags里开启”实验性网络平台功能(Experimental Web Platform)”
  • Firefox 54 – 需要在about:config里开启dom.moduleScripts.enabled选项。
  • Edge 15 – 需要在about:flags里开启 Experimental JavaScript Features 选项
<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);
}

我们需要做的只是在script标签元素上声明type=module就可以了,这样,浏览器就能解析代码中的module语法了。

网上已经有很多关于modules的好文章了,但我在这里想单独分享一下专门针对浏览器里的ECMAScript modules的知识,这些都是我在阅读和测试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';

下面几种 module 语法是有效的:

  • 绝对路径的URL。也就是说,使用 new URL(模块地址) 也不会报错。
  • 地址开头是 /
  • 地址开头是 ./
  • 地址开头是 ../

另外一些语法是保留为将来使用的,比如,导入内部的(built-in) modules。

用来保持向后兼容的nomodule

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

能够认识type=module语法的浏览器会忽略具有nomodule属性的scripts。也就是说,我们可以使用一些脚本服务于支持module语法的浏览器,同时提供一个nomodule的脚本用于哪些不支持module语法的浏览器,作为补救。

浏览器问题

  • Firefox并不支持nomodule属性 (issue)。但在Firefox nightly版里已经修复了这个问题。
  • Edge并不支持nomodule属性。 (issue).
  • Safari 10.1 并不支持nomodule属性,但在最新的技术预览版中修复了这个问题。对于10.1版, 有一个很聪明的变通技巧。

缺省设置为Defer

<!-- 这个脚本的执行将会晚于… -->
<script type="module" src="1.js"></script> <!-- …这个脚本… -->
<script src="2.js"></script> <!-- …但是会先于这个脚本. -->
<script defer src="3.js"></script>

执行的顺序将会是2.js1.js3.js.

如果script代码块阻止HTML分析器下载其他代码,这是非常糟糕的事情,通常我们会使用 defer 属性来防止这种解析阻塞,但同时这样也会延迟script脚本的执行——直到真个文档解析完成。而且还要参考其它deferred script脚本的执行顺序。Module scripts缺省行为状态很像 defer 属性的作用 – 一个 module script 不会妨碍HTML分析器下载其它资源。

Module scripts队列的执行顺序跟使用了defer属性的普通脚本队列的执行顺序一样。

Inline scripts同样是deferred

<!-- 这个脚本的执行将会晚于… -->
<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>

执行的顺序是 1.js, inline script, inline module, 2.js.

普通的inline scripts会忽略defer属性,而 inline module scripts 永远是deferred的,不管它是否有 import 行为。

外部 & inline modules script脚本上的 Async 属性

<!-- 这个脚本将会在imports完成后立即执行 -->
<script async type="module">
import {addTextToBody} from './utils.js'; addTextToBody('Inline module executed.');
</script> <!-- 这个脚本将会在脚本加载和imports完成后立即执行 -->
<script async type="module" src="1.js"></script>

这个快速下载script会率先执行。

跟普通的scripts一样, async 属性能让script加载的同时并不阻碍HTML解析器的工作,而且在加载完成后立即执行。跟普通的scripts不同的是, async 属性在inline modules脚本上也有效。

因为永远都是async, 所以这些scripts的执行顺序也许并不会像它们出现在DOM里的顺序。

浏览器问题

  • Firefox并不支持inline module scripts上的 async 特性。(issue).

Modules只执行一次

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

如果你知道 ES modules,你就应该知道,modules可以import多次,但只会执行一次。这种原则同样适用于HTML里的script modules – 一个确定的URL上的module script在一个页面上只会执行一次。

浏览器问题

  • Edge浏览器会多次执行 modules (issue).

CORS 跨域资源共享限制

<!-- 这个脚本不会执行,因为跨域资源共享限制 -->
<script type="module" src="https://….now.sh/no-cors"></script> <!-- 这个脚本不会执行,因为跨域资源共享限制-->
<script type="module">
import 'https://….now.sh/no-cors'; addTextToBody("This will not execute.");
</script> <!-- 这个没问题 -->
<script type="module" src="https://….now.sh/cors"></script>

跟普通的scripts不同, module scripts (以及它们的 imports 行为) 受 CORS 跨域资源共享限制。也就是说,跨域的 module scripts 必须返回带有有效 Access-Control-Allow-Origin: * 的CORS头信息。

浏览器问题

  • Firefox没有成功的加载演示页面 (issue).
  • Edge浏览器加载了没有 CORS headers 的 module scripts (issue)。

没有凭证信息(credentials)

<!-- 有凭证信息 (cookies等) -->
<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>

当请求在同一安全域下,大多数的CORS-based APIs都会发送凭证信息 (cookies 等),但fetch()和 module scripts 例外 – 它们并不发送凭证信息,除非我们要求它们。

我们可以通过添加crossorigin属性来让同源module脚本携带凭证信息,如果你也想让非同源module脚本也携带凭证信息,使用 crossorigin="use-credentials" 属性。需要注意的是,非同源脚本需要具有 Access-Control-Allow-Credentials: true 头信息。

同样,“Modules只执行一次”的规则也会影响到这一特征。URL是Modules的唯一标志,如果你先请求的Modules没有携带凭证信息,然后你第二次请求希望携带凭证信息,你仍然会得到一个没有凭证信息的module。这就是为什么上面的有个URL上我使用了一个?号,是让URL变的不同。

浏览器问题

  • 谷歌浏览器在请求同源 modules 时带有 credentials (issue).
  • Safari 请求同源 modules 时不带有 credentials,即使你使用了 crossorigin 属性标志。 (issue).
  • Edge浏览器完全相反。它缺省状态下对同源请求 modules 时带有 credentials 而当你指定了 crossorigin 属性后反倒没了。 (issue).

火狐浏览器是唯一做的正确的浏览器,干的漂亮!

Mime-types 文档类型

跟普通的scripts不一样,modules scripts 必须指定一个有效的 JavaScript MIME 类型,否则将不会执行。

浏览器问题

  • Edge浏览器在无效MIME types下也执行 (issue).

这些就是目前我学到的。毋庸置疑,ES modules能够登陆浏览器,已经让我兴奋不已了。你呢?

在浏览器中高效使用JavaScript module(模块)的更多相关文章

  1. WebAssembly 浏览器中运行c/c++模块

    今天,要给前端造点儿福利 浏览器中能调用javascript,曾经我们以为够用了,够强大了,但是事实上是完全不够 还好,mozilla的工程师提出了webassembly,目前是利用emsctript ...

  2. 原生ES-Module在浏览器中的尝试

    其实浏览器原生模块相关的支持也已经出了一两年了(我第一次知道这个事情实在2016年下半年的时候) 可以抛开webpack直接使用import之类的语法 但因为算是一个比较新的东西,所以现在基本只能自己 ...

  3. 在浏览器中输入URL并回车后都发生了什么?

    1.解析URL ________________________________________________________________________ 关于URL: URL(Universa ...

  4. 网易笔试题:浏览器中输入一个url后回车到返回页面信息的过程

    You enter a URL into the browser输入一个url地址 The browser looks up the IP address for the domain name浏览器 ...

  5. AMD:浏览器中的模块规范

    前面提到,为实现与Node.js相同方式的模块写法,大牛们做了很多努力. 但浏览器环境不同于服务器端,它的模块有一个HTTP请求过程(而Node.js的模块文件就在本地),这个请求过程多数使用scri ...

  6. 在浏览器中使用ES6的模块功能 import 及 export

    感谢英文原作者 Jake Archibald 的技术分享 各个浏览器对于ES6模块 import . export的支持情况 Safari 10.1. Chrome 61. Firefox 54 – ...

  7. 第十一章:WEB浏览器中的javascript

    客户端javascript涵盖在本系列的第二部分第10章,主要讲解javascript是如何在web浏览器中实现的,这些章节介绍了大量的脚本宿主对象,这些对象可以表示浏览器窗口.文档树的内容.这些章节 ...

  8. 转《在浏览器中使用tensorflow.js进行人脸识别的JavaScript API》

    作者 | Vincent Mühle 编译 | 姗姗 出品 | 人工智能头条(公众号ID:AI_Thinker) [导读]随着深度学习方法的应用,浏览器调用人脸识别技术已经得到了更广泛的应用与提升.在 ...

  9. JavaScript权威指南--WEB浏览器中的javascript

    知识要点 1.客户端javascript window对象是所有客户端javascript特性和API的主要接入点.它表示web浏览器的一个窗口或窗体,并且可以用window表示来引用它.window ...

随机推荐

  1. android: 接收系统广播

    Android 内置了很多系统级别的广播,我们可以在应用程序中通过监听这些广播来得到 各种系统的状态信息.比如手机开机完成后会发出一条广播,电池的电量发生变化会发出一 条广播,时间或时区发生改变也会发 ...

  2. EasyUI使用技巧总结

    combobox组件 一.禁用combobox里面的输入框 $("选择器").combo('textbox').attr("readonly", "r ...

  3. 微软BI 之SSIS 系列 - 带有 Header 和 Trailer 的不规则的平面文件输出处理技巧

    案例背景与需求介绍 之前做过一个美国的医疗保险的项目,保险提供商有大量的文件需要发送给比如像银行,医疗协会,第三方服务商等.比如像与银行交互的 ACH 文件,传送给协会的 ACH Credit 等文件 ...

  4. 一些Vim使用的小技巧

    1. 全局替换(1) v + G + $ 选定全部,然后输入 :s/原始字符串/目标字符串/(2) :%s/原始字符串/目标字符串/ 2. 清除页面中所有行尾的空白符::%s/s+$// 3. 清除所 ...

  5. elasticsearch 支持中英文搜索和混合搜索

    环境: ubuntu16.04 安装: elasticsearch 5.22 1. 第一步,安装java apt-get install default-jre apt-get install def ...

  6. LightCapture for Mac(流程化截图工具)破解版安装

    1.软件简介    LightCapture 是 macOS 系统上的截图软件.LightCapture Mac 版号称是用最简洁的方式来截图,其的界面也确实非常简单整洁,不过功能同样出色,一键截屏. ...

  7. C#-MVC开发微信应用(8)--菜单管理的实现

    之前讲解了微信后台管理页面的操作来管理菜单,下面我们在简单的来看一下,代码是如何实现的. 我们要实现获取微信的菜单.创建菜单.删除菜单等操作. 01.首先定义菜单操作的接口: /// <summ ...

  8. 物联网架构成长之路(15)-Jenkins部署SpringBoot

    1.前言 现在慢慢也在负责一些工作了.这段时间有空,就多了解了解软件多人开发的一些知识.以前项目都是我一个人做的,从数据库设计到后端再到前端,全部放在一个war包丢到tomcat里面然后运行,利用to ...

  9. 【Spark 深入学习 04】再说Spark底层运行机制

    本节内容 · spark底层执行机制 · 细说RDD构建过程 · Job Stage的划分算法 · Task最佳计算位置算法 一.spark底层执行机制 对于Spark底层的运行原理,找到了一副很好的 ...

  10. 【转】Django中使用POST方法获取POST数据

    1.获取POST中表单键值数据 如果要在django的POST方法中获取表单数据,则在客户端使用JavaScript发送POST数据前,定义post请求头中的请求数据类型: xmlhttp.setRe ...