0.故事会
今天上午给项目更新了依赖
此前的项目就有vite启动项目之后要等很多分钟才能正常打卡网页的问题
我学习了vite的启动流程(结尾附上)
由于每一个页面都使用了大同小异的组件和同样的依赖,这就导致
对于vite来说是最大的优势的点变得不再灵活,因为第一页就加载了基本上所有的大都会用到的组件
所以如果要做首屏加载时间的优化肯定是困难的
但是暂且不考虑太远的问题,今天早上更新form之后,
最快打开页面的同事,用时半个小时(只是加载页面,vite的开发环境首屏加载)
加载一个文件就用了5分钟
这显然已经影响到了开发环境的工作效率,
所以需要优化
1.前期排查
我排查了可以想到的可能性,我发现都站不住脚,因为一旦启动之后,再加载页面就显得没有那么慢了
在排查的时候我发现vite的配置里 有一个warmup选项,会提前加载文件,但是首次访问某些页面或模块时仍可能遇到较长时间的等待,因为需要实时编译和处理这些资源。通过预热(warmup)一些关键路径或模块,可以减少首次加载所需的时间,使开发体验更加流畅。
在比方说一定会用到这些依赖,但是调用链又比较深的情况下会导致加载缓慢,这种情况下就会提速
但是如果预热的文件过多,会占用更多的内存和CPU资源。尤其是在开发环境中,大量的文件预热可能会显著增加初始加载时间,并占用大量系统资源,从而影响整体性能。
试着减少文件预热,发现并没有生效,他只是从大段大段的卡顿,变成了小段小段的卡顿,
2.optimizeDeps替代warmup
继续查阅我发现对于我们的Monorepo项目改用optimizeDeps会更好,于是将warmup修改为使用官方文档中推荐的optimizeDeps
但是更改之后发现,似乎效果并不明显,但是也是有所改变的启动的首次加载还是会花费10多分钟的时间
3.scss编译时间优化
继续排查问题的时候我注意到有人提出了同样的问题
尤雨溪大佬在下方亲自回复 ,
原来scss或者是less在启动的时候会被vite编译为css。这里就花费了大量的时间
为了排查到底是哪一个文件导致了大量的花销
我给项目添加了vite-plugin-inspect插件
在vite配置中引入这个插件, 启动dev后命令行会多打印一个url, 点开可以看到一个页面.
页面的内容是一个当前应用所有module的列表, 点击列表会弹出一个新列表, 内容是当前module被哪些plugin处理过, 点击plugin还能看到这个plugin对module处理前后的diff.
能够自上而下的看到各自的加载用时,找到了最多花销的css文件,
发现里面花销最多的有来自另一个项目form和ui的css,但是已经被他们在打包的时候treeshaking和编译过了,所以并不是这个问题
还有最多的一项来自当前项目的scss,这个文件中发现使用了tailwind
由于tailwind作为scss被use了,所以数量庞大的scss会一一被转化一遍这就导致了项目的缓慢加载
把tailwind注释之后,项目果然能够算是正常运行起来了
所以这里的解决思路就是tailwind按需引入
加上配置之后
原本30分钟才启动好的代码也是不到一分钟就编译完成启动了
以下附上vite启动流程:
Vite 的首次启动流程可以分为以下几个阶段,结合现代浏览器对 ES Module(ESM) 的支持和 预构建优化,Vite 实现了极快的冷启动速度和高效的开发体验。
1. 冷启动阶段:快速初始化开发服务器
当你执行 npm run dev 命令时,Vite 的核心流程如下:
1.1 启动开发服务器
- 无需打包所有模块:传统工具(如 Webpack)会在启动服务器前对整个项目进行打包编译,而 Vite 直接启动一个基于 Koa 的轻量开发服务器,无需等待所有代码打包完成。
- 预构建依赖:
-
- 将 CommonJS 模块 转换为 ESM 格式。
- 合并小文件(如依赖中的多个小模块文件),减少 HTTP 请求次数。
- 首次启动时,Vite 会使用 esbuild 对
node_modules中的第三方依赖进行预处理: - 预构建结果缓存:预构建的依赖会被缓存到
node_modules/.vite目录下,后续启动时直接复用,无需重复处理。
1.2 按需加载源码
- 入口文件解析:当浏览器请求
index.html时,Vite 解析 HTML 中的<script type="module">,识别入口模块(如main.js或main.ts)。 - 动态编译源码:
-
- JavaScript/TypeScript:使用 esbuild 快速转译为浏览器兼容的 ESM 代码。
- Vue/Svelte 单文件组件:通过插件(如
@vitejs/plugin-vue)拆解模板、样式和逻辑,分别编译为 JavaScript 和 CSS。 - 静态资源:CSS、图片等资源通过插件处理(如添加哈希名或转换为 Base64)。
2. 开发阶段:按需加载与实时更新
Vite 的核心优势在于 按需加载 和 热模块替换(HMR),具体流程如下:
2.1 按需加载模块
- 浏览器驱动构建:
浏览器请求某个模块(如.vue或.ts文件)时,Vite 拦截请求并实时编译: -
- 依赖模块:使用强缓存(
Cache-Control: max-age=31536000,immutable),避免重复请求。 - 源码模块:使用协商缓存(
304 Not Modified),减少不必要的传输。 - 动态加载依赖:浏览器通过 ESM 的
import语句请求模块时,Vite 动态编译并返回结果。 - 缓存策略:
- 依赖模块:使用强缓存(
2.2 热模块替换(HMR)
- 实时更新:
-
- 修改文件后,Vite 仅重新编译变更的模块,并通过 WebSocket 通知浏览器更新。
- 保留应用状态:例如,修改 Vue 组件时,仅替换该组件的代码,无需刷新整个页面。
- HMR 优化策略:
-
- 边界推断:自动识别 HMR 的影响范围,避免全量更新。
- 依赖追踪:通过 ESM 的依赖关系图,精准定位需要更新的模块。
3. 生产环境构建:Rollup 打包优化
虽然 Vite 在开发环境中不依赖打包工具,但生产环境仍使用 Rollup 进行优化:
3.1 Rollup 打包流程
- Tree Shaking:移除未使用的代码。
- 代码压缩:使用 Terser 压缩 JavaScript 代码。
- 代码分割:根据依赖关系拆分代码块(Chunk),优化加载性能。
- 静态资源处理:提取 CSS 为独立文件,图片等资源根据配置内联或外部引用。
5.技术原理总结
- 预构建依赖:通过 esbuild 快速处理第三方依赖,减少首次启动时间。
- 按需加载源码:利用浏览器原生 ESM 支持,动态编译源码,避免全量打包。
- HMR 优化:精准更新模块,保留应用状态,提升开发效率。
- 缓存策略:依赖模块强缓存 + 源码模块协商缓存,加速后续请求。
6. 适用场景
- 现代前端框架:React、Vue、Svelte 等基于 ESM 的项目。
- 大型项目:无需等待全量打包,开发体验更流畅。
- 调试需求:快速验证代码变更,HMR 保持状态。
通过上述流程,Vite 在开发阶段实现了 “开箱即用” 的高效体验,同时保留了生产环境的优化能力。