其实浏览器原生模块相关的支持也已经出了一两年了(我第一次知道这个事情实在2016年下半年的时候)
可以抛开webpack直接使用import之类的语法
但因为算是一个比较新的东西,所以现在基本只能自己闹着玩 :p
但这并不能成为不去了解它的借口,还是要体验一下的。

首先是各大浏览器从何时开始支持module的:

  • Safari 10.1
  • Chrome 61
  • Firefox 54 (有可能需要你在about:config页面设置启用dom.moduleScripts.enabled)
  • Edge 16

数据来自jakearchibald.com/2017/es-mod…

使用方式

首先在使用上,唯一的区别就是需要在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.jshttps://www.baidu.com/module.js都会被认为是同一个module
但是像这个例子中的module1.jsmodule1.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>

在线Demo

import和export在使用的一些小提示

不管是浏览器原生提供的版本,亦或者webpack打包的版本。
importexport基本上还是共通的,语法上基本没有什么差别。

下边列出了一些可能会帮到你更好的去使用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'

在线Demo

小记

想到了最近爆红的deno,其中有一条特性也是提到了,没有node_modules,依赖的第三方库直接通过网络请求的方式来获取。
然后浏览器中原生提供的module也是类似的实现,都是朝着更灵活的方向在走。
祝愿抛弃webpack来进行开发的那一天早日到来 :)

参考资料

  1. es modules in browsers
  2. es6 modules in depth
  3. export - JavaScript | MDN
  4. import - JavaScript | MDN

文中示例代码的GitHub仓库:传送阵

原生ES-Module在浏览器中的尝试的更多相关文章

  1. 使用 ES Module 的正确姿势

    前面我们在深入理解 ES Module 中详细介绍过 ES Module 的工作原理.目前,ES Module 已经在逐步得到各大浏览器厂商以及 NodeJS 的原生支持.像 vite 等新一代的构建 ...

  2. 在浏览器中高效使用JavaScript module(模块)

    在浏览器中也可以使用JavaScript modules(模块功能)了.目前支持这一特性的浏览器包括: Safari 10.1. 谷歌浏览器(Canary 60) – 需要在chrome:flags里 ...

  3. HTML5移动Web开发(十)——在浏览器中启动手机原生应用

    用户可以在浏览器中启动移动设备的原生应用程序,比如地图.电话.短信等,具体能够启动哪些应用程序,这取决于该移动设备上哪些原生应用是否允许从浏览器启动. 新建ch02r05.html <!doct ...

  4. 彻底掌握 Commonjs 和 Es Module

    目录 Commonjs commonjs 实现原理 require 文件加载流程 require 模块引入与处理 require 加载原理 require 避免重复加载 require 避免循环引用 ...

  5. 部分安卓手机微信浏览器中使用XMLHttpRequest 2上传图片显示字节数为0的解决办法

    前端JS中使用XMLHttpRequest 2上传图片到服务器,PC端和大部分手机上都正常,但在少部分安卓手机上上传失败,服务器上查看图片,显示字节数为0.下面是上传图片的核心代码: HTML < ...

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

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

  7. JS JavaScript模块化(ES Module/CommonJS/AMD/CMD)

    前言 前端开发中,起初只要在script标签中嵌入几十上百行代码就能实现一些基本的交互效果,后来js得到重视,应用也广泛起来了, jQuery,Ajax,Node.Js,MVC,MVVM等的助力也使得 ...

  8. Python_socket常见的方法、网络编程的安全注意事项、socketsever模块、浏览器中在一段时间记录用户的登录验证机制

    1.socket常见的方法 socket_常见方法_服务器端 import socket from socket import SOL_SOCKET,SO_REUSEADDR sk = socket. ...

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

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

随机推荐

  1. 3ds Max学习日记(三)

      今天把第三章搞完了,学的是样条线(splines)建模的一些操作.不过实习又有新任务了,得去研究一下如何将单张图片转化为三维模型(我擦,这神马操作),所以可能没有那么多时间愉快地与3ds max玩 ...

  2. Jenkins系列-Jenkins忘记密码的修复方法

    找回 admin 用户的密码后,可以登录系统修改其他用户的密码. 1. Jenkins 目录结构 Jenkins 没有使用数据库,所有的信息都保存在 JENKINS_HOME 目录下的文件中.其中 J ...

  3. 内存交换空间(swap)的构建

    一.使用物理分区构建swap 1.先进行分区的行为. [root@iZ255cppmtxZ ~]# fdisk /dev/xvdb Welcome to fdisk (util-linux ). Ch ...

  4. mysql 重置 root 密码

    mysqld_safe --skip-grant-tables & UPDATE mysql.user SET authentication_string=PASSWORD('mima') W ...

  5. 从一个简单的main方法执行谈谈JVM工作机制

    本来JVM的工作原理浅到可以泛泛而谈,但如果真的想把JVM工作机制弄清楚,实在是很难,涉及到的知识领域太多.所以,本文通过简单的mian方法执行,浅谈JVM工作原理,看看JVM里面都发生了什么. 先上 ...

  6. windows下本地调试hadoop代码,远程调试hadoop节点。

    1.在github上搜索下载winutils.exe相关的一套文件,下载对应hadoop的版本. 2.将所有文件复制到hadoop的bin目录下 3.将hadoop.dll复制到windows\sys ...

  7. CentOS 文本操作命令

    1.cat 用于查看纯文本文件,显示行号,加-n参数,适合内容较少的情况 2.more 用于查看纯文本文件,适合内容较多的情况 3.less 用于查看纯文本文件,可以上下翻页 4.head 用于查看纯 ...

  8. UVA.357 Let Me Count The Ways (DP 完全背包)

    UVA.357 Let Me Count The Ways (DP 完全背包) 题意分析 与UVA.UVA.674 Coin Change是一模一样的题.需要注意的是,此题的数据量较大,dp数组需要使 ...

  9. 阅读android源码了解 android 加载so的流程

    参考原文:http://bbs.pediy.com/thread-217656.htm Android安全–linker加载so流程,在.init下断点: http://www.blogfshare. ...

  10. STL之四:list用法详解

    转载于:http://blog.csdn.net/longshengguoji/article/details/8520891 list容器介绍 相对于vector容器的连续线性空间,list是一个双 ...