上手体验Vue3 Vite的魅力(“快”的艺术),有了它,你还会用webpack吗?

·  阅读 204

Vite的定义

Vite(法语单词,“快” 的意思)是一种新型的前端构建工具,最初是配合 Vue3.0 一起使用的,后来适配了各种前端项目,目前提供了 Vue、React、Preact 框架模板。目前来说,Vue 使用的是 vue-cli 脚手架,React 一般使用 create-react-app 脚手架。虽然脚手架工具不一样,但是内部的打包工具都是 Webpack,为什么要开发一个全新的构建工具,是 Webpack 不香了吗?Vite 方式构建的项目,和使用 Webpack 构建的项目,有什么不同?一个新工具的出现,一定是为了解决现有工具存在的问题的,否则新工具就没有存在的价值和意义**。**

为什么选Vite?

现实问题

在浏览器支持 ES 模块之前,开发者没有以模块化的方式开发 JavaScript 的原生机制。这也是 “打包” 这个概念出现的原因:使用工具抓取、处理和链接我们的源码模块到文件中,使其可以运行在浏览器中。

时过境迁,我们见证了许多诸如 webpack、Rollup 和 Parcel 等工具的诞生,这些工具极大地改善了前端开发者的开发体验。

然而,当我们开始构建越来越大型的应用时,需要处理的 JavaScript 代码量也呈指数级增长。大型项目包含数千个模块的情况并不少见。我们开始遇到性能瓶颈 —— 使用 JavaScript 开发的工具通常需要很长时间(甚至是几分钟!)才能启动开发服务器,即使使用 HMR,文件修改后的效果也需要几秒钟才能在浏览器中反映出来。如此循环往复,迟钝的反馈会极大地影响开发者的开发效率和幸福感。

Vite 旨在利用生态系统中的新进展解决上述问题:浏览器开始原生支持 ES 模块,越来越多 JavaScript 工具使用编译型语言编写

问题一:缓慢的服务器启动

当冷启动开发服务器时,基于打包器的方式是在提供服务前去急切地抓取和构建你的整个应用。

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

依赖 大多为纯 JavaScript 并在开发时不会变动。一些较大的依赖(例如有上百个模块的组件库)处理的代价也很高。依赖也通常会以某些方式(例如 ESM 或者 CommonJS)被拆分到大量小模块中。

Vite 将会使用 esbuild 预构建依赖。Esbuild 使用 Go 编写,并且比以 JavaScript 编写的打包器预构建依赖快 10-100 倍。

源码 通常包含一些并非直接是 JavaScript 的文件,需要转换(例如 JSX,CSS 或者 Vue/Svelte 组件),时常会被编辑。同时,并不是所有的源码都需要同时被加载。(例如基于路由拆分的代码模块)。

Vite 以 原生 ESM 方式服务源码。这实际上是让浏览器接管了打包程序的部分工作:Vite 只需要在浏览器请求源码时进行转换并按需提供源码。根据情景动态导入的代码,即只在当前屏幕上实际使用时才会被处理。

两种构建方式的对比图 基于打包器的开发服务器

基于 ESM 的开发服务器

问题二:缓慢的更新

当基于打包器启动时,编辑文件后将重新构建文件本身。显然我们不应该重新构建整个包,因为这样更新速度会随着应用体积增长而直线下降。

一些打包器的开发服务器将构建内容存入内存,这样它们只需要在文件更改时使模块图的一部分失活[1],但它也仍需要整个重新构建并重载页面。这样代价很高,并且重新加载页面会消除应用的当前状态,所以打包器支持了动态模块热重载(HMR):允许一个模块 “热替换” 它自己,而对页面其余部分没有影响。这大大改进了开发体验 - 然而,在实践中我们发现,即使是 HMR 更新速度也会随着应用规模的增长而显著下降。

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

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

注意:尽管原生 ESM 现在得到了广泛支持,但由于嵌套导入会导致额外的网络往返,在生产环境中发布未打包的 ESM 仍然效率低下(即使使用 HTTP/2)。为了在生产环境中获得最佳的加载性能,最好还是将代码进行 tree-shaking、懒加载和 chunk 分割(以获得更好的缓存)

如何创建vue3.0+vite 这里就不讲了

Vite的工作机制

Vite整体的工作机制,用简单的图表示:

根据图片来看,Vite实际上也是一个server,在启动vite构建web server后,即服务启动之后(如访问),我们访问这个地址,该页面会向web server发送请求,请求对应的文件,我们可以从network中看到,如下:

注意观察network里面的响应,我们会发现里面有两个App.vue,两个HelloWorld.vue,实际上这就是因为在我们请求这些文件的时候,被vite server拦截了,经过编译后才返回对应的内容。

打开network里面的HelloWorld.vue

对比我们项目里面的HelloWorld.vue,两个的内容是截然不同的

观察network中HelloWorld.vue里面的内容

import { render as __render } from "/src/components/HelloWorld.vue?type=template 表示引入了HelloWorld.vue?type=template 正好network里面有HelloWorld.vue?type=template请求,打开查看response,这实际就是HelloWorld.vue中template编译之后的内容

这个工作机制也正是vite的速度快的原因,vite server拦截请求,只对请求的文件及其依赖的模块进行编译,那些没有被引用到的文件就不会被编译,做到了按需编译。

证明一下,查看当前的main.js,其引入了index.css

import { createApp } from 'vue' import App from './App.vue' import './index.css'

createApp(App).mount('#app') 此时network中是会去请求index.css

修改main.js,去掉对index.css的引入

import { createApp } from 'vue'
import App from './App.vue'
//import './index.css'
 
createApp(App).mount('#app')
复制代码

此时查看network中发现,index.css不去请求了

那么问题就来了,每次发请求都去拦截、编译,岂不是每次刷新页面都会重复之前的编译工作(即使页面没做更新)?答案是不会的。刚刚我们把index.css的引用去掉了,页面自动的刷新,观察上图中的状态码,除了几个为200,其他的大部分为403,这代表着这些请求用的是缓存里面的资源,而并没有重新去编译,合理利用浏览器的缓存机制,减少请求与编译,提升速度。

除了利用浏览器缓存外,vite本身也有一个缓存的机制,它会针对依赖的包做一个缓存,这里我们可以看一下network中main.js的response。

可以发现,我们对vue的引用,变成了对@modules下的vue.js的引用,在项目的目录,找到node_modules下的.vite_opt_cache文件夹可以找到vue.js文件。

与network里面的vue.js进行对比

network里面的vue.js就是vite缓存里面的vue.js,是对vue进行编译后生成的js文件,证明一下,现在在目录里面的vue.js文件添加一行代码:

**刷新查看network中的vue.js,二者一致。实际上.vite_opt_cache就是用来存放被缓存的依赖包的,而这里不止缓存vue的依赖包,其他的依赖包也可以,比如这里我们引入axios **。

分类:
前端
标签:
分类:
前端
标签: