浏览器、ESM规范、模块化、webpack和vite之间联系?
文章来源于公众号:惊天码盗 ,作者:鸡汤小弟
前言
通俗的讲:JS这门语言,在设计之处就没有模块体系,所以他会经历模块化演变,直到2015年,TC39(一群浏览器厂商代表组成的委员会)发布了ES6规范,ES Modules才被世人所知,也就是ESM。而在模块化演变过程中,模块化本身暴露的问题也越来越明显。项目中的模块越来越多,在管理上造成了混乱,所以迫切需要一些工具解决各种模块类型混乱的问题。webpack和vite等工具就是用来解决这些问题的。
从浏览器的发展,JS的流行,到模块化探索,再到构建工具的区别,这里面涉及的知识点很多,在这里简单的列几点:
- 浏览器的运行机制
- 模块化的演变
- 模块化工具的演变
- webpack的演变
- vite的诞生
- 前端开发者的选择
浏览器与js
浏览器在渲染页面前会解析 HTML文档,通过 HTML 解析器输出 DOM 元素和属性。
然后构建 DOM tree 和 CSSOM tree。这两个模型构建时是相互独立的。
最后 CSSOM tree 和 DOM tree 合并成渲染树,然后用于计算每个可见元素的布局,并输出绘制流程,将像素渲染到屏幕上。
如果 DOM 或 CSSOM 被修改,需要重新构建,以确定哪些像素需要在屏幕上进行重新渲染,这里会涉及到两个知识点:repeat
和reflow
。
而 JS 允许查询和修改 DOM 和 CSSOM,同样 JS 也会阻塞 DOM 和 CSSOM 的构建和渲染,所以我们要把 JS 放在 HTML 的底部加载。
现在的浏览器的内核大多分为两部分:渲染引擎和JS引擎。
渲染引擎,负责对网页语法的解释并渲染网页。我们常说的gecko引擎、"斯巴阿"(edge)、presto引擎、webkit、blink引擎等都是渲染引擎。
JS引擎,是专用于对JS进行解释、编译和执行,使网页达到动态得到效果。常说的V8引擎就是JS引擎。
js与模块化
在 js 模块化进程中,CommonJS 常用在服务端,AMD、CMD规范常用在客户端。
起初,CommonJS专攻服务端,原名叫 ServerJS,后来,为了统一前后端而改名 CommonJs 。而就在社区讨论下一版规范的时候,内部发生了比较大的分歧,分裂出三个主张,渐渐的形成三个不同的派别:
- 在现有CommonJs的基础上满足浏览器的需要,把现有模块化转化为适合浏览器端即可。制定了*Modules/Transport*(http://wiki.commonjs.org/wiki/Modules/Transport)规范,browserify就是这样一个工具,可以把nodejs的模块编译成浏览器可用的模块。
- 还有一波人,认为浏览器与服务器环境差别太大,不能沿用旧的标准,而且浏览器必须异步加载代码,那么模块在定义的时候就必须指明所依赖的模块,然后把本模块的代码写在回调函数里。模块的加载也是通过下载-回调这样得到过程来进行,这个思想也是\A******MD****的基础(https://github.com/amdjs/amdjs-api/wiki/AMD)。
- 最后一波人,既不想丢掉旧的规范,也不想像AMD那样重来。他们认为CommonJs的一些理念是好的,比如通过require来声明依赖,新的规范应兼容这些,同时也认为AMD也有可取之处,比如预加载等。最终他们制定来一个*Modules/Wrappings*规范(http://wiki.commonjs.org/wiki/Modules/Wrappings)。
RequireJs 的出现,迅速被广大开发者接受。但是 RequireJs 也有被吐槽的地方,*预先下载没有什么争议,预先执行是否需要*?如果一个模块依赖十个其他模块,那么在本模块的代码执行之前,要先把其他十个模块的代码都执行一遍,不管这些模块是不是马上会被用到。这个性能消耗是不不容忽视的。
针对 RequireJs 的不优雅的地方,国内大牛玉伯写出了 SeaJs。SeaJs 全面拥抱 Modules/Wrapping 规范,但也没有完全按照 Modules/Wrapping 规范,seajs 并没有使用 declare 来定义模块,而是使用和 requirejs 一样的define。依赖会被预先下载,在需要执行的时候执行。同样 seajs 也实现了在需要执行的时候下载这一功能,提供了 require.async API,并支持 CommonJs 和 RequireJs 关于模块对外暴露 API 的方式。鉴于 seajs 融合了太多的东西,已经无法说它遵循哪个规范了,所以玉伯干脆就自立门户,起名曰CMD(Common Module Definition)规范。
以上所讲的 CommonJs 、AMD、CMD 等等,只是社区制定而非官方。官方一看社区呼声这么高,js模块化终于在2015年发布ES6正式版。从此ESM诞生了。
ES6 模块的设计思想是尽量的静态化,使得编译时就能确定模块的依赖关系,以及输入和输出的变量。CommonJS 和 AMD 模块,都只能在运行时确定这些东西。比如,CommonJS 模块就是对象,输入时必须查找对象属性。
CommonJs 是运行时加载,ES6是编译时加载。可能你对编译和运行这两个关键词有点不理解。CommonJs 是先加载模块,输出一个对象,输出后内部不会再变化。而 ES6 是语言层面的改变,它 输出的是一个引用,提到引用就想到了JS的数据类型-引用类型 。等执行的时候才会取值输出。
模块化与构建工具
模块的加载和传输,我们首先能想到两种极端的方式:
- 一种是每个模块文件都单独请求
- 另一种是把所有模块打包成一个文件然后只请求一次
显而易见,每个模块都发起单独的请求造成了请求次数过多,导致应用启动速度慢;一次请求加载所有模块导致流量浪费、初始化过程慢。这两种方式都不是好的解决方案,它们过于简单粗暴。
分块传输,按需进行懒加载,在实际用到某些模块的时候再增量更新,才是较为合理的模块加载方案。
要实现模块的按需加载,就需要一个对整个代码库中的模块进行静态分析、编译打包的过程。
但是我们的项目不单单只有js啊!有图片、样式、字体、html模版等资源。在编译的时候,要对整个代码进行静态分析,分析出各个模块的类型和它们依赖关系,然后将不同类型的模块提交给适配的加载器来处理。所以构建工具产生了。
关于构建工具可以查看之前写的一篇文章《关于前端构建工具的大杂烩》。
webpack脱颖而出是因为它的理念:一切皆为模块。
关于webpack还有一个故事,Tobias Koppers是 Webpack 仓库创建者,Tobias 的网络昵称叫 sokra ,后面我们就叫 sokra,sokra 没有写过 web 页面,这个就很有意思了,一个没有写过web页面的人发明了当代web开发的基石。
sokra 一开始是写 Java 的, Java 里面有个很出名的技术叫GWT(Google Web Toolkit),GWT 是把 Java 代码转换成JavaScript,也就是让后端来写前端,本质上也是在AST层面对代码做一层转换,Babel 也是干这件事的,但是 GWT 这门技术没有流行起来,后面 Google 也不推广了。
GWT里面有个feature叫「code splitting」,于是他当时给用来做前端项目 Bundle 的 node.js 库 modules-webmake 提了一个 issue,希望他们能实现,「code splitting」就是Webpack现在提供的主要功能,也是当代前端的基石。
大多技术的产生都离不开前人的经验,都是把原有的技术,改巴改巴就成了一个新的技术,所以说「 造轮子 」是技术发展不可避免的一环。
webpack与vite
webpack 与 vite 本质上的区别就在于「 按需加载 」。
webpack 无论怎么提倡按需加载,在 ESM 面前都是假的按需加载。因为在 ESM 之前浏览器并不支持模块化,上面我们聊的模块化大多都是社区的产物,不是本身语言层面的支持。而 ESM 的诞生,标志着js有了自己的模块体系。
有一个很好的例子可以说明,在 script 标签中添加type=“modules”,引入 js 文件,在 js 文件中直接使用 import 导入,浏览器是可以正常运行的,在这里我们不需要依赖工具帮我们实现浏览器对模块化的支持。
vite 内部的实现是把其他资源都编译成 js 文件,换句话就是,把图片、样式、字体、vue文件等等其他资源模块,都转化JS模块,让浏览器加载编译。当然你可能会想到 webpack 也会啊,这不正是技术的本性吗。难道 vite 的诞生就不能借鉴其他构建工具吗。
当然很多同学都会有个疑问:vite会取代webpack吗?
不会,vite 的发展才刚刚起步,而浏览器的多样性也导致 ESM 规范落地没有那么快。webpack 的社区、生态已经非常完善,这不是 vite 一个刚出生的小孩可以媲美的,但是 vite 的未来是一定会比 webpack 更加强大,这是科技发展所必然经历的,一代更比一代强。
以上就是W3Cschool编程狮
关于浏览器、ESM规范、模块化、webpack和vite之间联系?的相关介绍了,希望对大家有所帮助。