浏览器、ESM规范、模块化、webpack和vite之间联系?

2020-09-27 14:39:08 浏览数 (5235)

文章来源于公众号:惊天码盗 ,作者:鸡汤小弟

前言

通俗的讲:JS这门语言,在设计之处就没有模块体系,所以他会经历模块化演变,直到2015年,TC39(一群浏览器厂商代表组成的委员会)发布了ES6规范,ES Modules才被世人所知,也就是ESM。而在模块化演变过程中,模块化本身暴露的问题也越来越明显。项目中的模块越来越多,在管理上造成了混乱,所以迫切需要一些工具解决各种模块类型混乱的问题。webpack和vite等工具就是用来解决这些问题的。

从浏览器的发展,JS的流行,到模块化探索,再到构建工具的区别,这里面涉及的知识点很多,在这里简单的列几点:

  • 浏览器的运行机制
  • 模块化的演变
  • 模块化工具的演变
  • webpack的演变
  • vite的诞生
  • 前端开发者的选择

浏览器与js

浏览器在渲染页面前会解析 HTML文档,通过 HTML 解析器输出 DOM 元素和属性。

然后构建 DOM tree 和 CSSOM tree。这两个模型构建时是相互独立的。

最后 CSSOM tree 和 DOM tree 合并成渲染树,然后用于计算每个可见元素的布局,并输出绘制流程,将像素渲染到屏幕上。

浏览器与js

如果 DOM 或 CSSOM 被修改,需要重新构建,以确定哪些像素需要在屏幕上进行重新渲染,这里会涉及到两个知识点:repeatreflow

而 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 。而就在社区讨论下一版规范的时候,内部发生了比较大的分歧,分裂出三个主张,渐渐的形成三个不同的派别:

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之间联系?的相关介绍了,希望对大家有所帮助。