theme: v-green
导言
基于webpack构建的项目会遇到一些问题,包括
- 当项目体积逐渐庞大,webpack启动devServer的速度越来越慢
- 随着代码文件的增多,热更新效率逐渐降低
vite
vite是一个新型前端构建工具,主要用途是提升开发体验,也就是解决上述的问题。
和传统构建工具webpack相比,vite有着一些特别的地方
特点
- 启动devServer时,不需要对代码进行打包
- 代码只有在必要的时候进行编译处理
- 按需加载代码,热更新速度快
启动devServer不需要打包
webpack
启动devServer前,需要对代码进行编译打包,而vite没有这个过程。
- 旧版的浏览器不支持import这种esm语法,代码必须经由webpack处理后,浏览器才能正常加载。而在现代浏览器中,只需要在script标签上指定
type=module
,就可以通过import直接引入模块,可以省略打包的过程。 - vite只是启动了一个devServer,对代码进行编译处理的时机放在了浏览器请求的时候
必要的时候才进行编译
虽然不需要打包,但是浏览器不能够完全解析我们写的代码,比如
- 导入核心模块,比如
import { createApp } from 'vue'
,由于写的不是相对路径,浏览器无法找到对应的模块文件 - 导入vue文件时,
import App from './App.vue'
,浏览器不能直接识别vue文件
当存在这些情况时,vite需要对代码做一定的处理,将其转化为浏览器可以访问到并识别的文件
按需加载
vite构建devServer时,只有在浏览器向服务器请求指定文件时,vite才对其做编译处理,属于真正的按需加载。
启动devServer发生了什么
为什么vite能如此快的启动devServer
对依赖包的处理
vite会对依赖包进行处理,也就是dependencies
指定的内容。主要做的事情就两个
- 预构建依赖
- 利用缓存
预构建依赖
vite会使用 esbuild
预构建依赖,主要用途
- 兼容模块化规范
vite 的 DevServer 是基于ESM实现的,如果依赖是 CommonJS规范,则需要进行模块转化。
比如说loadsh这个库,它是基于commonjs规范去实现的,直接通过import引入会有问题。因此需要在启动server前做一个规范整合。
- 避免模块之间引用过多,导致请求次数太多的问题
引入一个库的时候,这个库内部又会通过import引入各种依赖,最后结果就是浏览器要发起很多的请求。vite会对这种情况做处理,将相关文件进行打包,避免过多的请求。
缓存
一般来说一个项目的依赖包是很少会有变动的,也就是说没有必要每次启动devServer时都去做预构建的操作,vite利用了一定的缓存功能。
- 文件缓存
vite会将预构建的依赖缓存到 node_modules/.vite
当以下内容发生变动时,才会重新预构建
package.json
中的dependencies
- 包管理器的 lockfile
vite.config.js
文件中指定的字段
- 浏览器缓存
对依赖包的请求会设置为强缓存
对业务代码的处理
vite以会在浏览器请求代码时进行处理
import { createApp } from 'vue'
import App from './App.vue'
- 改写路径
vite会重写引入依赖的路径,打开控制台查看网络请求,可以看到返回的代码为
import { createApp } from '/node_modules/.vite/vue.js?v=079e737a'
import App from '/src/App.vue?t=1637582768248'
import node_modules
中的模块,路径会被修改为 /node_modules/.vite
,也就是esBuild处理后的依赖文件所在的目录
- 处理vue文件
vite在获取一个vue文件时,发起了两个请求。
- 首先请求 vue 文件,返回vite处理后的js代码。简单说就是将template的内容转为render函数,并与scripts中的逻辑组合并返回。
- 在
App.vue
内会import与样式相关的资源,浏览器会再发起一次请求。vite接受请求,并返回处理后的内容。
热更新
场景
三个文件a b c,c中引入了a 和 b
export function funA() {} // a.js
export function funB() {} // b.js
// c.js
improt { funA } from './a'
improt { funB } from './b'
export function funC() {
funA()
funB()
}
Webpack
会对代码进行编译打包,生成一个bundle.js
function funA() {}
function funB() {}
export function funC() {
funA()
funB()
}
当修改a文件时,Webpack会重新编译打包。生产一个新的bundle.js文件。可以预想,当模块内的依赖越来越多时,热更新的效率会逐渐降低
vite
没有打包的过程,浏览器访问时直接访问到a b c
当修改a文件时,只需要更新a 和 c,b直接从缓存中获取即可
a 文件重新获取
b 文件从缓存中获取,304 协商缓存
这种方式可以最大化的利用浏览器缓存能力,同时能做到理论上热更新的速度不会随着文件规模变大而减慢
构建生产环境
vite构建生产环境代码时,采用rollup进行打包
为什么还是要打包
虽然vite号称是无打包模式的,但是为什么构建生产环境代码时,还是有打包的操作呢? 主要是为了解决以下问题
- 模块间嵌套导入会导致额外的网络往返
- 对代码进行tree-shaking、代码分割、懒加载等,实现生产环境的最佳性能
为什么不用ESBUILD
- esbuild对scss、代码分隔等方面不完善,不适合构建一个标准的应用程序
- 在打包应用程序方面,rollup更加成熟
和Webpack对比
为什么选择了rollup而不是webpack
- rollup和vite都采用ESM,而
Webpack
采用commonJs - rollup支持相对路径,
Webpack
需要使用path模块 - rollup写法更简洁,打包出的文件更小
缺点
- 生态不如webpack
- 解决的是开发环境问题
- 浏览器兼容性问题,不支持IE