Vite快速开发探究

288 阅读5分钟

theme: v-green

导言

基于webpack构建的项目会遇到一些问题,包括

  • 当项目体积逐渐庞大,webpack启动devServer的速度越来越慢
  • 随着代码文件的增多,热更新效率逐渐降低

vite

vite是一个新型前端构建工具,主要用途是提升开发体验,也就是解决上述的问题。

和传统构建工具webpack相比,vite有着一些特别的地方

特点

  1. 启动devServer时,不需要对代码进行打包
  2. 代码只有在必要的时候进行编译处理
  3. 按需加载代码,热更新速度快

启动devServer不需要打包

webpack启动devServer前,需要对代码进行编译打包,而vite没有这个过程。

  1. 旧版的浏览器不支持import这种esm语法,代码必须经由webpack处理后,浏览器才能正常加载。而在现代浏览器中,只需要在script标签上指定type=module ,就可以通过import直接引入模块,可以省略打包的过程。
  2. vite只是启动了一个devServer,对代码进行编译处理的时机放在了浏览器请求的时候

必要的时候才进行编译

虽然不需要打包,但是浏览器不能够完全解析我们写的代码,比如

  1. 导入核心模块,比如 import { createApp } from 'vue' ,由于写的不是相对路径,浏览器无法找到对应的模块文件
  2. 导入vue文件时, import App from './App.vue' ,浏览器不能直接识别vue文件

当存在这些情况时,vite需要对代码做一定的处理,将其转化为浏览器可以访问到并识别的文件

按需加载

vite构建devServer时,只有在浏览器向服务器请求指定文件时,vite才对其做编译处理,属于真正的按需加载。

启动devServer发生了什么

为什么vite能如此快的启动devServer

对依赖包的处理

vite会对依赖包进行处理,也就是dependencies 指定的内容。主要做的事情就两个

  1. 预构建依赖
  2. 利用缓存

预构建依赖

vite会使用 esbuild 预构建依赖,主要用途

  • 兼容模块化规范

vite 的 DevServer 是基于ESM实现的,如果依赖是 CommonJS规范,则需要进行模块转化。

比如说loadsh这个库,它是基于commonjs规范去实现的,直接通过import引入会有问题。因此需要在启动server前做一个规范整合。

  • 避免模块之间引用过多,导致请求次数太多的问题

引入一个库的时候,这个库内部又会通过import引入各种依赖,最后结果就是浏览器要发起很多的请求。vite会对这种情况做处理,将相关文件进行打包,避免过多的请求。

缓存

一般来说一个项目的依赖包是很少会有变动的,也就是说没有必要每次启动devServer时都去做预构建的操作,vite利用了一定的缓存功能。

  • 文件缓存

vite会将预构建的依赖缓存到 node_modules/.vite

当以下内容发生变动时,才会重新预构建

  1. package.json 中的 dependencies
  2. 包管理器的 lockfile
  3. vite.config.js文件中指定的字段
  • 浏览器缓存

对依赖包的请求会设置为强缓存

image.png

对业务代码的处理

vite以会在浏览器请求代码时进行处理

import { createApp } from 'vue'
import App from './App.vue'
  1. 改写路径

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处理后的依赖文件所在的目录

  1. 处理vue文件

vite在获取一个vue文件时,发起了两个请求。

image.png

  • 首先请求 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 文件重新获取

image.png

b 文件从缓存中获取,304 协商缓存

image.png

这种方式可以最大化的利用浏览器缓存能力,同时能做到理论上热更新的速度不会随着文件规模变大而减慢

构建生产环境

vite构建生产环境代码时,采用rollup进行打包

为什么还是要打包

虽然vite号称是无打包模式的,但是为什么构建生产环境代码时,还是有打包的操作呢? 主要是为了解决以下问题

  • 模块间嵌套导入会导致额外的网络往返
  • 对代码进行tree-shaking、代码分割、懒加载等,实现生产环境的最佳性能

为什么不用ESBUILD

  • esbuild对scss、代码分隔等方面不完善,不适合构建一个标准的应用程序
  • 在打包应用程序方面,rollup更加成熟

和Webpack对比

为什么选择了rollup而不是webpack

  • rollup和vite都采用ESM,而Webpack采用commonJs
  • rollup支持相对路径,Webpack需要使用path模块
  • rollup写法更简洁,打包出的文件更小

缺点

  1. 生态不如webpack
  2. 解决的是开发环境问题
  3. 浏览器兼容性问题,不支持IE