Libra-studio / One-question-every-week

每天一道题,强壮打工人
1 stars 0 forks source link

第17题:vite为什么快 #17

Open hezhengjie opened 1 year ago

hezhengjie commented 1 year ago

简单描述一下 vite为什么快,和webpack有什么不同

hezhengjie commented 1 year ago

Vite是什么

Vite(法语意为 "快速的")是vue 的作者尤雨溪在开发 vue3.0 的时候开发的一个 基于原生 ES-Module 的前端构建工具。

官方称为新型前端构建工具,重点在于解决大型应用启动开发服务器慢的问题。

webpack为什么慢

回到vite出现的原因,是为了解决大型应用启动开发服务器慢。现阶段大部分项目打包使用的都是webpack,对应配套的开发服务器是webpack-dev-server,所以我们就要来看下webpack-dev-server为什么会慢,慢到需要我们搞出一个Vite。

首先我们来了解一下webpack和webpack-dev-server。

webpack支持的模块类型有:ES 模块、CommonJS 和 AMD 模块,基本所有新的老的模块类型都支持,webpack-dev-server 使用Express.js作为web服务器。

这里的webpack-dev-server 慢我们同样需要从两方面来看,一方面是启动慢,另一方面是热更新慢。

无论是生产环境的webpack还是开发环境的webpack-dev-server,构建的流程是基本一致的

其中的第一步和第二步随着模块的越来越多,类型越来越复杂,其耗时也越来越多。webpack需要找到所有的依赖(项目目录,node_modules等地方),然后把部分依赖进行编译(es6编译成es5,CommonJS 的模块转成可识别的模块,scss,less等转成css等)。

所以大型项目中,模块数量可能成百上千,启动的时候自然就慢。

热更新慢,我们需要看下webpack的热更新原理

  1. 第一步,在 webpack 的 watch 模式下,文件系统中某一个文件发生修改,webpack 监听到文件变化,根据配置文件对模块重新编译打包,并将打包后的代码通过简单的 JavaScript 对象保存在内存中。
  2. 第二步是 webpack-dev-server 和 webpack 之间的接口交互,而在这一步,主要是 dev-server 的中间件 webpack-dev-middleware 和 webpack 之间的交互,webpack-dev-middleware 调用 webpack 暴露的 API 对代码变化进行监控,并且告诉 webpack,将代码打包到内存中。
  3. 第三步是 webpack-dev-server 对文件变化的一个监控,这一步不同于第一步,并不是监控代码变化重新打包。当我们在配置文件中配置了 devServer.watchContentBase 为 true 的时候,Server 会监听这些配置文件夹中静态文件的变化,变化后会通知浏览器端对应用进行 live reload。注意,这儿是浏览器刷新,和 HMR 是两个概念。
  4. 第四步也是 webpack-dev-server 代码的工作,该步骤主要是通过 sockjs((webpack-dev-server 的依赖)在浏览器端和服务端之间建立一个 websocket 长连接,将 webpack 编译打包的各个阶段的状态信息告知浏览器端,同时也包括第三步中 Server 监听静态文件变化的信息。浏览器端根据这些 socket 消息进行不同的操作。当然服务端传递的最主要信息还是新模块的 hash 值,后面的步骤根据这一 hash 值来进行模块热替换。
  5. webpack-dev-server/client 端并不能够请求更新的代码,也不会执行热更模块操作,而把这些工作又交回给了 webpack,webpack/hot/dev-server 的工作就是根据 webpack-dev-server/client 传给它的信息以及 dev-server 的配置决定是刷新浏览器呢还是进行模块热更新。当然如果仅仅是刷新浏览器,也就没有后面那些步骤了。
  6. HotModuleReplacement.runtime 是客户端 HMR 的中枢,它接收到上一步传递给他的新模块的 hash 值,它通过 JsonpMainTemplate.runtime 向 server 端发送 Ajax 请求,服务端返回一个 json,该 json 包含了所有要更新的模块的 hash 值,获取到更新列表后,该模块再次通过 jsonp 请求,获取到最新的模块代码。
  7. HotModulePlugin 将会对新旧模块进行对比,决定是否更新模块,在决定更新模块后,检查模块之间的依赖关系,更新模块的同时更新模块间的依赖引用。

后面几步其实没什么问题,热更新慢的核心原因在于第一步,重新编译打包,这意味着你无论修改是什么, webpack都会将所有文件都重新构建打包一次。

Vite和webpack的区别

那么Vite和webpack又有什么区别呢,凭什么他比webpack快。

这里我们要了解Vite这个工具的核心依赖-浏览器原生支持esm。

整个Vite工具都是依赖这个前提。这就意味着我们无需要把所有代码打包成一个,只要把所有esm的模块扔给浏览器,我们只需要处理加载esm模块的路径即可。

这里使用官网的两张图,第一张是webpack等工具使用的模式,讲所有模块打包成一个bundle,然后提供给浏览器。

而vite则使用第二张的模式,先启动开发服务器,浏览器需要什么模块时再向dev server请求什么模块。

image image

Vite 通过在一开始将应用中的模块区分为 依赖 和 源码 两类,改进了开发服务器启动时间。

基于以上划分,Vite的构建流程会比webpack更短一些,大致如下:

  1. 依赖预构建,使用esbuild进行预构建,esbuild 使用 Go 编写,并且比以 JavaScript 编写的打包器预构建依赖快 10-100 倍。这一步是为了将一部分无法直接运行的依赖进行编译构建,转成浏览器可以直接运行的esm,主要有以下两步
    • 将非ESM的模块(比如CommonJS或者UMD)转成esm
    • 将一些较大的依赖(例如有上百个模块的组件库)转换成一个esm模块。
  2. 缓存资源,所有预构建的依赖都会缓存到 node_modules/.vite
  3. 源码构建,将源码转换成esm,并替换依赖的路径
    • 将特殊文件转换为esm(vue文件的转码,jsx的转码等)
    • 替换源码中的导入模块的路径
  4. 启动服务器

而在热更新阶段,Vite的大致过程和上面的webpack-dev-server的差不多,但是vite可以不用打包,只需要更新修改过的文件即可。

在 Vite 中,HMR 是在原生 ESM 上执行的。当编辑一个文件时,Vite 只需要精确地使已编辑的模块与其最近的 HMR 边界之间的链失活(大多数时候只是模块本身),使得无论应用大小如何,HMR 始终能保持快速更新。

Vite 同时利用 HTTP 头来加速整个页面的重新加载(再次让浏览器为我们做更多事情):源码模块的请求会根据 304 Not Modified 进行协商缓存,而依赖模块请求则会通过 Cache-Control: max-age=31536000,immutable 进行强缓存,因此一旦被缓存它们将不需要再次请求。

其他构建工具

Snowpack

是在Vite之前的一个非构建式原生 ESM 开发服务器,与 Vite 十分类似,基本一样的原理。可惜的是不维护了,并且相关团队也转到使用Vite了。

WMR

Preact 团队开发的非构建式原生 ESM 开发服务器。WMR 主要是为了 Preact项目而设计,并为其提供了集成度更高的功能,比如预渲染。就使用范围而言,它更加贴合于 Preact 框架,与 Preact 本身一样强调紧凑的大小。如果你正在使用 Preact,那么 WMR 可能会提供更好的体验。