参考:
主要:由一次业务项目落地 Vite 的经历,我重新理解了 Vite 预构建
vite官方文档
为什么有人说 vite 快,有人却说 vite 慢?
深入理解Vite核心原理
面向未来的前端构建工具-vite
聊一聊 Vite 的预构建和二次预构建
聊一聊 Vite
Vite原理
Vite 基于 浏览器原生 ESM 规范的支持,采取了与 Webpack 完全不同的 unbundle 机制。
也就是说Vite无需像webpack那样去遍历生成模块依赖图,它在使用时直接启动了一个开发服务器devServer,劫持浏览器的HTTP请求,在中间件中对请求的文件进行处理,将项目中使用的文件通过简单的分解与整合,然后再返回给浏览器。
Vite在启动时会先做一些准备工作,比如对第三方模块进行预编译处理,然后在本地启动一个开发服务器dev server,在中间件中对请求的文件进行处理,大概处理流程如下:加载源文件、转化翻译成ESM可解析的文件,比如ts -> js, less -> css 等,分析源文件依赖,然后返回请求的js文件。
Vite整个过程中没有对文件进行打包编译,做到了真正的按需加载。因此vite编译速度很快,尤其在大型项目中表现得更加明显,大大节约了开发时间。
它在正式环境采用了rollup打包的方式,但是目前在生态上和稳定性、兼容性上还无法和webpack媲美。
esbuild打包为什么这么快呢?
esbulid是使用goLang进行打包的,goLang是静态语言,因此它的速度比使用js/ts更快。
vite内部解析
vite内部创建了一个本地的KOA server,热更新监听使用chokidar监听文件变化。模块解析、增加中间件、启动服务器。
devServer会编译代码文件,编译成浏览器可运行的代码,它具备静态文件服务功能,vite会使用serveStaticPlugin将项目根目录、public目录设置为静态目录,并且plugin中包含koa-etag中间件,文件如果发生了变化,devServer会通知变化的etag,从而达到浏览器的更新效果。
重写模块路径-非js资源打包策略-静态资源打包策略
vite会重写模块路径:
- 相对路径转为绝对路径
- 补齐扩展名
- 对非js类型的文件地址会加上一个"import"的query参数。
- hmr文件加上时间戳。
Vue脚本打包策略
Vite会引入vue的插件@vue/compiler-sfc
解析vue文件,将template、style、script三部分解析出来,加上一个type=【style|template】的后缀,在打包后的vue文件中增加一个import语句引入style和template,最后渲染出vue页面。
webpack处理静态资源:1. css转为js,新建style标签插入html中。2. 图片转为图片路径或者base64。3. JSON转化为js模块
Vite和webpack对比
webpack会先打包,然后启动开发服务器,请求服务器时直接给予打包结果。 而vite是直接启动开发服务器,请求哪个模块再对该模块进行实时编译。
现代浏览器本身就支持ES Module,会自动向依赖的Module发出请求。
vite利用这一点,将开发环境下的模块文件,就作为浏览器要执行的文件,vite在启动的时候不需要打包,不需要分析模块的依赖和编译,因此启动速度快。
当浏览器请求某个模块时,再根据需要对模块内容进行编译。按需动态编译缩减了编译时间。
HMR方面,当改动了一个模块后,仅需让浏览器重新请求该模块,而webpack需要把该模块的相关依赖模块全部编译一次,效率更高。
Webpack 的热更新会以当前修改的文件为入口重新 build 打包,所有涉及到的依赖也都会被重新加载一次。虽然webpack 也采用的是局部热更新并且是有缓存机制的,但是还是需要重新打包所以很大的代码项目是真的有卡顿的现象
文件缓存:Vite 会将预构建的依赖缓存到node_modules/.vite。它根据几个源来决定是否需要重新运行预构建步骤:package.json中的 dependencies列表, package-lock等
浏览器缓存:源码模块的请求会根据 304 Not Modified 进行协商缓存,而依赖模块请求则会通过 Cache-Control: max-age=31536000,immutable 进行强缓存
Vite的优缺点
-
优点:
快速的冷启动: 采用No Bundle和esbuild预构建,速度远快于Webpack
高效的热更新:基于ESM实现,同时利用HTTP头来加速整个页面的重新加载,增加缓存策略
真正的按需加载: 基于浏览器ESM的支持,实现真正的按需加载
-
缺点:
生态:目前Vite的生态不如Webapck,不过生态应该也只是时间上的问题。
生产环境由于esbuild对css和代码分割不友好使用Rollup进行打包。
对服务器渲染方面目前没有比较成熟的方案,不过这也是生态还没有起来的一部分。
在首屏渲染和懒加载的时候可能会比webpack慢一点,因为服务器中间件拦截请求后在响应过程中完成编译,所以第一次打开的时候,没有缓存 首屏相对而言慢一点,懒加载是因为动态加载的时候,也需要再次请求、动态编译,因此相对而言会慢。
vite使用原理
首先浏览器向开发服务器请求页面,返回一个html文件,文件中引入了需要挂载的js文件。
如
<script type="module" src="/src/main.js"></script>
当浏览器解析资源时,会往当前域名发起一个GET请求main.js文件
// main.js import { createApp } from 'vue' import App from './App.vue' createApp(App).mount('#app')
请求到了main.js文件,会检测到内部含有import引入的包,又会import 引用发起HTTP请求获取模块的内容文件,如App.vue、vue文件
Vite其核心原理是利用浏览器现在已经支持ES6的import,碰见import就会发送一个HTTP请求去加载文件,Vite启动一个 koa 服务器拦截这些请求,并在后端进行相应的处理将项目中使用的文件通过简单的分解与整合,然后再以ESM格式返回返回给浏览器。Vite整个过程中没有对文件进行打包编译,做到了真正的按需加载,所以其运行速度比原始的webpack开发编译速度快出许多!
import { defineConfig } from 'vite'
import { resolve } from "path"; // 主要用于alias文件路径别名
import vue from '@vitejs/plugin-vue'
import vueJsx from "@vitejs/plugin-vue-jsx"; // jsx插件
export default defineConfig({
plugins: [vue(), vueJsx()], // 插件列表
base: './', // 在生产中服务时的基本公共路径。
resolve: {
alias: { "@": path.resolve(__dirname, './src'), } // 配置别名
},
// 引入第三方的配置,强制预构建插件包
optimizeDeps: { include: ['echarts', 'axios', 'mockjs'], },
// 打包配置
build: {
target: 'modules', // 最终构建的浏览器兼容目标。modules:支持原生 ES 模块的浏览器
outDir: 'dist', // 指定输出路径
assetsDir: 'assets', // 指定生成静态资源的存放路径
sourcemap: false, // 构建后是否生成 source map 文件
minify: 'terser' // 混淆器,terser构建后文件体积更小
},
// 本地运行配置,及反向代理配置
server: {
host: 'localhost', // 指定服务器主机名
port: 3000, // 指定服务器端口
open: true, // 在服务器启动时自动在浏览器中打开应用程序
strictPort: false, // false-- 若端口已被占用则会尝试下一个可用端口,而不是直接退出
https: false, // 是否开启 https
cors: true, // 为开发服务器配置 CORS。默认启用并允许任何源
proxy: { // 为开发服务器配置自定义代理规则
'/foo': 'http://192.168.xxx.xxx:xxxx',
'/api': {
target: 'http://192.168.xxx.xxx:xxxx', //代理接口
changeOrigin: true,
rewrite: (path) => path.replace(/^\/api/, '')
}
}
}
})