原生ES-Module在浏览器中的尝试
其实浏览器原生模块相关的支持也已经出了一两年了(我第一次知道这个事情实在2016年下半年的时候)
可以抛开webpack
直接使用import
之类的语法
但因为算是一个比较新的东西,所以现在基本只能自己闹着玩 :p
但这并不能成为不去了解它的借口,还是要体验一下的。
首先是各大浏览器从何时开始支持module
的:
- Safari 10.1
- Chrome 61
- Firefox 54 (有可能需要你在
about:config
页面设置启用dom.moduleScripts.enabled
) - Edge 16
使用方式
首先在使用上,唯一的区别就是需要在script
标签上添加一个type="module"
的属性来表示这个文件是作为module
的方式来运行的。
<script type="module">
import message from './message.js' console.log(message) // hello world
</script>
然后在对应的module
文件中就是经常会在webpack
中用到的那样。
语法上并没有什么区别(本来webpack
也就是为了让你提前用上新的语法:) )
message.js
export default 'hello world'
优雅降级
这里有一个类似于noscript
标签的存在。
可以在script
标签上添加nomodule
属性来实现一个回退方案。
<script type="module">
import module from './module.js'
</script>
<script nomodule>
alert('your browsers can not supports es modules! please upgrade it.')
</script>
nomodule
的处理方案是这样的: 支持type="module"
的浏览器会忽略包含nomodule
属性的script
脚本执行。
而不支持type="module"
的浏览器则会忽略type="module"
脚本的执行。
这是因为浏览器默认只解析type="text/javascript"
的脚本,而如果不填写type
属性则默认为text/javascript
。
也就是说在浏览器不支持module
的情况下,nomodule
对应的脚本文件就会被执行。
一些要注意的细节
但毕竟是浏览器原生提供的,在使用方法上与webpack
的版本肯定还是会有一些区别的。
(至少一个是运行时解析的、一个是本地编译)
有效的module路径定义
因为是在浏览器端的实现,不会像在node
中,有全局module
一说(全局对象都在window
里了)。
所以说,from 'XXX'
这个路径的定义会与之前你所熟悉的稍微有些出入。
// 被支持的几种路径写法 import module from 'http://XXX/module.js'
import module from '/XXX/module.js'
import module from './XXX/module.js'
import module from '../XXX/module.js' // 不被支持的写法
import module from 'XXX'
import module from 'XXX/module.js'
在webpack
打包的文件中,引用全局包是通过import module from 'XXX'
来实现的。
这个实际是一个简写,webpack
会根据这个路径去node_modules
中找到对应的module
并引入进来。
但是原生支持的module
是不存在node_modules
一说的。
所以,在使用原生module
的时候一定要切记,from
后边的路径一定要是一个有效的URL
,以及一定不能省略文件后缀(是的,即使是远端文件也是可以使用的,而不像webpack
需要将本地文件打包到一起)。
module的文件默认为defer
这是script
的另一个属性,用来将文件标识为不会阻塞页面渲染的文件,并且会在页面加载完成后按照文档的顺序进行执行。
<script type="module" src="./defer/module.js"></script>
<script src="./defer/simple.js"></script>
<script defer src="./defer/defer.js"></script>
为了测试上边的观点,在页面中引入了这样三个JS
文件,三个文件都会输出一个字符串,在Console
面板上看到的顺序是这样的:
行内script也会默认添加defer特性
因为在普通的脚本中,defer
关键字是只指针对脚本文件的,如果是inline-script
,添加属性是不生效的。
但是在type="module"
的情况下,不管是文件还是行内脚本,都会具有defer
的特性。
可以对module类型的脚本添加async属性
async
可以作用于所有的module
类型的脚本,无论是行内还是文件形式的。
但是添加了async
关键字以后并不意味着浏览器在解析到这个脚本文件时就会执行,而是会等到这段脚本所依赖的所有module
加载完毕后再执行。
import的约定,必须在一段代码内的起始位置进行声明,且不能够在函数内部进行
也就是说下边的log
输出顺序完全取决于module.js
加载的时长。
<script async type="module" >
import * from './module.js'
console.log('module')
</script>
<script async src="./defer/async.js"></script>
一个module只会加载一次
这个module
是否唯一的定义是资源对应的完整路径是否一致。
如果当前页面路径为https://www.baidu.com/a/b/c.html
,则文件中的/module.js
、../../module.js
与https://www.baidu.com/module.js
都会被认为是同一个module
。
但是像这个例子中的module1.js
与module1.js?a=1
就被认定为两个module
,所以这个代码执行的结果就是会加载两次module1.js
。
<script type="module" src="https://blog.jiasm.org/module-usage/example/modules/module1.js"></script>
<script type="module" src="/examples/modules/module1.js"></script>
<script type="module" src="./modules/module1.js"></script>
<script type="module" src="./modules/module1.js?a=1"></script>
<script type="module">
import * as module1 from './modules/module1.js'
</script>
import和export在使用的一些小提示
不管是浏览器原生提供的版本,亦或者webpack
打包的版本。
import
和export
基本上还是共通的,语法上基本没有什么差别。
下边列出了一些可能会帮到你更好的去使用modules
的一些技巧。
export的重命名
在导出某些模块时,也是可以像import
时使用as
关键字来重命名你要导出的某个值。
// info.js
let name = 'Niko'
let age = 18 export {
name as firstName,
age
} // import
import {firstName, age} from './info.js'
Tips: export的调用不像node中的module.exports = {}
可以进行多次调用,而且不会覆盖(key重名除外)。
export { name as firstName }
export { age }
这样的写法两个key都会被导出。
export导出的属性均为可读的
也就是说export
导出的属性是不能够修改的,如果试图修改则会得到一个异常。
但是,类似const
的效果,如果某一个导出的值是引用类型的,对象或者数组之类的。
你可以操作该对象的一些属性,例如对数组进行push
之类的操作。
export {
firstName: 'Niko',
packs: [1, 2]
}
import * as results from './export-editable.js' results.firstName = 'Bellic' // error results.packs.push(3) // success
这样的修改会导致其他引用该模块都会受到影响,因为使用的是一个地址。
export在代码中的顺序并不影响最终导出的结果
export const name = 'Niko'
export let age = 18 age = 20
const 或者 let 对于 调用方来说没有任何区别
import {name, age} from './module' console.log(name, age) // Niko 20
import获取default模块的几种姿势
获取default
有以下几种方式都可以实现:
import defaultItem from './import/module.js'
import { default as defaultItem2 } from './import/module.js'
import _, { default as defaultItem3 } from './import/module.js' console.log(defaultItem === defaultItem2) // true
console.log(defaultItem === defaultItem3) // true
默认的规则是第一个为default
对应的别名,但如果第一个参数是一个解构的话,就会被解析为针对所有导出项的一个匹配了。
P.S. 同时存在两个参数表示第一个为default,第二个为全部模块
导出全部的语法如下:
import * as allThings from './iport/module.js'
类似index的export文件编写
如果你碰到了类似这样的需求,在某些地方会用到十个module
,如果每次都import
十个,肯定是一种浪费,视觉上也会给人一个不好的感觉。
所以你可能会写一个类似index.js
的文件,在这个文件中将其引入到一块,然后使用时import index
即可。
一般来说可能会这么写:
import module1 from './module1.js'
import module2 from './module2.js' export default {
module1,
module2
}
将所有的module
引入,并导出为一个Object
,这样确实在使用时已经很方便了。
但是这个索引文件依然是很丑陋,所以可以用下面的语法来实现类似的功能:
export {default as module1} from './module1.js'
export {default as module2} from './module2.js'
然后在调用时修改为如下格式即可:
import * as modules from './index.js'
小记
想到了最近爆红的deno
,其中有一条特性也是提到了,没有node_modules
,依赖的第三方库直接通过网络请求的方式来获取。
然后浏览器中原生提供的module
也是类似的实现,都是朝着更灵活的方向在走。
祝愿抛弃webpack
来进行开发的那一天早日到来 :)
参考资料
文中示例代码的GitHub仓库:传送阵
原生ES-Module在浏览器中的尝试的更多相关文章
- 使用 ES Module 的正确姿势
前面我们在深入理解 ES Module 中详细介绍过 ES Module 的工作原理.目前,ES Module 已经在逐步得到各大浏览器厂商以及 NodeJS 的原生支持.像 vite 等新一代的构建 ...
- 在浏览器中高效使用JavaScript module(模块)
在浏览器中也可以使用JavaScript modules(模块功能)了.目前支持这一特性的浏览器包括: Safari 10.1. 谷歌浏览器(Canary 60) – 需要在chrome:flags里 ...
- HTML5移动Web开发(十)——在浏览器中启动手机原生应用
用户可以在浏览器中启动移动设备的原生应用程序,比如地图.电话.短信等,具体能够启动哪些应用程序,这取决于该移动设备上哪些原生应用是否允许从浏览器启动. 新建ch02r05.html <!doct ...
- 彻底掌握 Commonjs 和 Es Module
目录 Commonjs commonjs 实现原理 require 文件加载流程 require 模块引入与处理 require 加载原理 require 避免重复加载 require 避免循环引用 ...
- 部分安卓手机微信浏览器中使用XMLHttpRequest 2上传图片显示字节数为0的解决办法
前端JS中使用XMLHttpRequest 2上传图片到服务器,PC端和大部分手机上都正常,但在少部分安卓手机上上传失败,服务器上查看图片,显示字节数为0.下面是上传图片的核心代码: HTML < ...
- 第十一章:WEB浏览器中的javascript
客户端javascript涵盖在本系列的第二部分第10章,主要讲解javascript是如何在web浏览器中实现的,这些章节介绍了大量的脚本宿主对象,这些对象可以表示浏览器窗口.文档树的内容.这些章节 ...
- JS JavaScript模块化(ES Module/CommonJS/AMD/CMD)
前言 前端开发中,起初只要在script标签中嵌入几十上百行代码就能实现一些基本的交互效果,后来js得到重视,应用也广泛起来了, jQuery,Ajax,Node.Js,MVC,MVVM等的助力也使得 ...
- Python_socket常见的方法、网络编程的安全注意事项、socketsever模块、浏览器中在一段时间记录用户的登录验证机制
1.socket常见的方法 socket_常见方法_服务器端 import socket from socket import SOL_SOCKET,SO_REUSEADDR sk = socket. ...
- JavaScript权威指南--WEB浏览器中的javascript
知识要点 1.客户端javascript window对象是所有客户端javascript特性和API的主要接入点.它表示web浏览器的一个窗口或窗体,并且可以用window表示来引用它.window ...
随机推荐
- Node js MySQL简单操作
//win7环境下node要先安装MySQL的相关组件(非安装MySQL数据库),在cmd命令行进入node项目目录后执行以下语句 //npm install mysql var mysql = re ...
- name(实例化类名).hbm.xml文件案例
[html] view plain copy print? <span xmlns="http://www.w3.org/1999/xhtml"><?xml ve ...
- Spyder5 & 显示器校准 & 色彩校准
Spyder5 & 显示器校准 & 色彩校准 Spyder5EXPRESS 绿蜘蛛5 – 轻松.快速地校准您的屏幕. Spyder5PRO 蓝蜘蛛5 – 可为您的所有笔记本电脑和台式机 ...
- javascript中的this作用域详解
javascript中的this作用域详解 Javascript中this的指向一直是困扰我很久的问题,在使用中出错的机率也非常大.在面向对象语言中,它代表了当前对象的一个引用,而在js中却经常让我觉 ...
- 【转】TCP拥塞控制,慢启动、拥塞避免、快重传以及快恢复
转自:http://blog.csdn.net/yusiguyuan/article/details/22847787 注:本文绝大部分是来自转载的博客,还补充了少量内容. 一.TCP的拥塞控制 拥塞 ...
- 洛谷 P2323 [HNOI2006]公路修建问题 解题报告
P2323 [HNOI2006]公路修建问题 题目描述 输入输出格式 输入格式: 在实际评测时,将只会有m-1行公路 输出格式: 思路: 二分答案 然后把每条能加的大边都加上,然后加小边 但在洛谷的题 ...
- oracle语法
执行计划: 1.1 设置autotrace 序号 命令 解释 1 SET AUTOTRACE OFF 此为默认值,即关闭Autotrace 2 SET AUTOTRACE ON EXPLAIN 只显示 ...
- [AHOI2008] 逆序对
link 我们可以很容易的推断出$-1$是单调不降的,若$i>j$且$a_i$与$a_j$都没有填数,若填完之后$a_i>a_j$或者$a_i<a_j$,则对答案产生影响的只在$[i ...
- 34张史上最全IT架构师技术知识图谱 最新下载
本文是笔者多年来积累和收集的知识技能图谱,小编极力推荐分享给身边的技术人儿,希望这份技术知识图谱能够帮助到每一位奋斗在技术路上的小伙伴. 下面是笔者多年来积累和收集的知识技能图谱,有的是笔者原创总结的 ...
- ACE自适配通信环境简介
转载于:http://www.cnblogs.com/TianFang/archive/2006/12/03/580795.html ACE自适配通信环境 (Adaptive Communicatio ...