如果你还在 JeecgBoot 老版本上做低代码平台二次开发,大概率见过这样的场景:早上到公司打开终端执行npm run dev,然后去接杯水、看看消息,回来 Vite 才完成依赖预构建。更有甚者,改了一个公共组件的代码,页面白屏一两秒才算缓过来。这不是个例,JeecgBoot 这类后台管理系统的前端依赖动辄几百个包,Vite2 在冷启动和依赖优化上的疲态非常明显。我手上这套项目从 Vite2 一路升到 Vite5,整个过程踩了不少坑,也实打实拿到了构建耗时减半、页面秒开的效果。这篇文章不聊原理书上的车轱辘话,只讲我实际怎么改的、遇到了什么问题、每步为什么这么做。适合正在维护 JeecgBoot 或类似 Vue3 低代码平台前端的同学参考,也适合准备把旧构建链升级到 Vite5 的人避坑。
1. 升级前把性能账算清楚:JeecgBoot前端慢在哪
1.1 慢的根源不是“代码乱”,而是依赖规模和构建链路
JeecgBoot 的前端不是普通的管理后台,它默认集成了非常多东西:ant-design-vue 组件库、vxe-table 表格、echarts 图表、wangEditor 富文本、Online 表单和 Online 报表的动态渲染、流程设计器等等。这些依赖在 npm 里都是体积很大的包。Vite2 时代,冷启动要做依赖预构建,把所有依赖预先用 esbuild 打包一遍,几百个依赖塞进去,再快的机器也得十几秒。项目越大,这个时间越接近线性上涨。等它预构建完,第一次页面加载还要重新请求依赖,浏览器里经常能看到一个长时间的白屏。
另一个痛点是人多的时候:上午大家同时开机,dev server 第一次响应奇慢,一会儿就提示optimized dependencies changed. reloading,然后整个页面刷新。热更新理论上很快,但 Vite2 面对这种庞大依赖图,经常退化成全量刷新。改一个按钮的文字,整个页面重新加载,开发节奏直接被打断。
1.2 Vite5 在底层到底改了什么
这里只说几个和性能直接相关的点,理论不展开太多:
- 依赖预构建效率更高。Vite5 底层仍是 esbuild 做预构建,但它对依赖扫描和缓存的策略做了很多修正,不会没事就把整包依赖重扫一遍。实际感受就是冷启动后不再频繁出现“reloading”白屏。
- Rollup 4 接管生产构建。生产构建部分 Vite5 用了 Rollup4,tree-shaking、模块合并、缓存方面都比旧版强。低代码平台这种大量动态路由、import.meta.glob 的代码结构,收益尤其明显。
- 默认浏览器目标进步。Vite5 把默认构建目标从“广泛兼容”调整为现代浏览器基线(大致是 Chrome 87+、Edge 88+ 这一档)。这意味着生产包不用再为远古浏览器做大量转译和 polyfill,产物体积自然下来。
- 包体更小、热更新路径更短,这是开发体验上最直观的飞跃。
用户视角就是:同样的项目代码,什么都不优化,从 Vite2 升到 Vite5,冷启动从几十秒降到个位数,构建耗时减掉一半以上,首屏资源也能小一截。
1.3 预期收益和风险清单
在动手之前,我给团队拉了一张简单的收益/风险表:
| 维度 | 收益预期 | 风险点 |
|---|---|---|
| 冷启动 | 从 30s+ 降到 10s 内 | 依赖预构建缓存失效需容忍首次启动 |
| 生产构建 | 耗时减半以上 | 部分旧插件不兼容,可能报错 |
| 开发体验 | HMR 更跟手,不易全量刷新 | 需要 Node 18 及以上环境 |
| 产物性能 | gzip 体积变小,首屏更快 | 老的浏览器兼容目标需重新定义 |
这个表很关键,它决定了我们要不要在这个迭代里动构建链。最后我们决定升,因为收益足够大,风险可回滚。顺便说一句,网上经常有人问 JeecgBoot 和若依哪个好,我不好直接下结论,但至少在前端构建链上,JeecgBoot 从 Vite2 升到 Vite5 的动作是实打实的,对长期维护者来说这是加分项。
2. 升级前的准备清单:版本基线、依赖对齐与回滚方案
2.1 先摸清家底:当前版本和环境
不要一上来就动 package.json。我习惯先执行:
node -v npm -v grep -E '"vite"|"vue"|"@vitejs/plugin-vue"' package.json我们项目的基线是:Node 14.21.3、npm 6.14.18、Vite 2.9.16、Vue 3.2.45、ant-design-vue 3.2.20。这基本就是 JeecgBoot 3.5.x 时代的前端标配,Vite2 跑起来已经吃力了。
这一步的价值在于:后面所有报错,都能对照基线判断是不是“升级引入的”。如果没有这个基线,报一个错你都不知道是本来就有,还是版本升级造成的,排查效率会低很多。
2.2 制定升级矩阵:一次只升一条链路
很多项目死在“顺手把 UI 库也升了”。我强烈建议,构建链升级是构建链升级,UI 库升级是 UI 库升级,分两个迭代做。这次我们的矩阵很简单:
| 依赖 | 升级前 | 升级后 | 说明 |
|---|---|---|---|
| node | 14.21.3 | 18.20.4 LTS | Vite5 硬性要求,推荐 LTS |
| vite | 2.9.16 | 5.2.12 | 核心升级 |
| @vitejs/plugin-vue | 2.3.4 | 5.0.5 | 必须同步升 |
| @vitejs/plugin-legacy | 不启用 | 5.4.2 | 视浏览器兼容需求决定 |
| vue | 3.2.45 | 3.4.31 | 建议同步,兼容 Vite5 |
| ant-design-vue | 3.2.20 | 维持 3.x | 与本次升级解耦 |
| 样式插件 | vite-plugin-style-import | unplugin 方案 | Vite5 下旧插件不可靠 |
当时刚好 Vite5 是稳定主流,Vite6 刚出但生态插件适配还参差不齐,所以锁定 5.x 是稳妥选择。如果你在阅读时已经到 Vite7 时代,同理选一个生态内反馈最稳的当前主流版本就行。关键不是版本号最新,而是团队能不能稳定跑起来。
2.3 备份、回滚和基线数据
改动之前,我做三件事:
- 从主线切一个
feature/vite5-upgrade分支,整个前端目录单独提交; - 保留旧
package-lock.json的 git 记录,如果升级失败可以随时 reset 回来; - 在升级前先测一轮基线数据:冷启动耗时、生产构建耗时、dist 体积、首页 FCP。没有基线数据,后面说“性能提升多少”都是拍脑袋。
这一步千万别省。我见过太多团队升级完说“变快了”,结果一问快多少,谁也说不出来。特别是低代码平台这种大项目,优化效果往往不是线性的,没有前后数据对比,很难向团队交代这次升级到底值不值。
3. 核心迁移动作:依赖升级与 vite.config 改造
3.1 package.json 和依赖安装
改动后的关键片段:
{ "engines": { "node": ">=18.0.0", "npm": ">=9.0.0" }, "scripts": { "dev": "vite", "build:prod": "vite build --mode production", "preview": "vite preview" }, "dependencies": { "vue": "^3.4.31", "ant-design-vue": "3.2.20" }, "devDependencies": { "@vitejs/plugin-legacy": "^5.4.2", "@vitejs/plugin-vue": "^5.0.5", "unplugin-auto-import": "^0.17.6", "unplugin-vue-components": "^0.27.2", "vite": "^5.2.12" } }安装前先把 Node 切到 18。用 nvm 最省事:
nvm install 18.20.4 nvm use 18.20.4 node -v rm -rf node_modules package-lock.json npm install这里有个细节:Vite5 及大部分新版本依赖都是 ESM-only,旧 lockfile 在 npm6 下生成后,npm9 去安装会有格式冲突,所以直接删掉 package-lock 重新生成,能省一堆莫名其妙的 peer 依赖报错。
3.2 vite.config 的改造点
这是我最终收敛下来的配置骨架,几个重点都加了注释:
import { defineConfig } from 'vite' import vue from '@vitejs/plugin-vue' import AutoImport from 'unplugin-auto-import/vite' import Components from 'unplugin-vue-components/vite' import { AntDesignVueResolver } from 'unplugin-vue-components/resolvers' export default defineConfig({ plugins: [ vue(), AutoImport({ imports: ['vue', 'vue-router', 'pinia'], dts: 'src/auto-imports.d.ts' }), Components({ resolvers: [AntDesignVueResolver()], dts: 'src/components.d.ts' }) ], resolve: { alias: { '@': '/src' } }, server: { host: '0.0.0.0', port: 3000, proxy: { '/jeecg-boot': { target: 'http://localhost:8080', changeOrigin: true } } }, optimizeDeps: { include: ['ant-design-vue', 'axios', 'vxe-table', 'echarts', 'wangeditor'] }, build: { chunkSizeWarningLimit: 1500, rollupOptions: { output: { manualChunks: { antd: ['ant-design-vue'], vxetable: ['vxe-table'], echarts: ['echarts'], editor: ['wangeditor'], vendor: ['vue', 'vue-router', 'pinia', 'axios'] } } } } })几个容易踩的配置项:
server.proxy配置不用动,低代码平台前端调后端接口基本全靠/jeecg-boot这个代理前缀,改坏了登录都会挂。- 如果你之前用了
define传大段字符串,Vite5 对这个字段的解析更严格。我的建议是能不用就不用,环境变量走import.meta.env。 build.target:Vite5 默认值比较激进(现代浏览器)。JeecgBoot 用户里可能还有大量老款办公浏览器,如果确认要兼容 Chrome 80 这类,就显式写build.target: 'es2015',或者启用@vitejs/plugin-legacy。我们最终选择保留 legacy 做降级,代价是产物会多一些 polyfill,但首屏关键包不受影响。chunkSizeWarningLimit调大不是掩盖问题,是为了把 manualChunks 拆包后的 warning 收敛,别让 CI 刷屏。
3.3 动态渲染组件和 import.meta.glob
低代码平台升级后最怕的就是“某个由元数据配置生成的页面白屏”。JeecgBoot 的 Online 表单、Online 报表,很多视图组件是运行时通过 glob 动态加载的。Vite5 对import.meta.glob语义和 Vite2 基本一致,但这里我建议做一次显式收敛:
// 原来可能散在各处的动态加载 const modules = import.meta.glob('../views/**/*.vue') // 升级后我更推荐在核心渲染处维护一份显式列表 const onlineViewModules = import.meta.glob('../views/online/**/*.vue')也就是把动态导入范围从“所有 views”缩小到“在线表单相关目录”。好处很明显:glob 范围小了,构建时的模块图更小,热更新不必要的依赖链路更短,性能又提升一截。同时还能避免因为路径大小写不一致导致的生产构建找不到模块。
这一步不会立刻体现在某个单次构建数据里,但它决定了升级后大页面还有没有可能偶发白屏。
3.4 manualChunks 拆包
这个配置直接关系首屏加载。JeecgBoot 这种项目,如果不拆包,ant-design-vue、vxe-table、echarts 会全部塞进一个超大 vendor,首屏解析卡死。升级到 Vite5 后 Rollup4 对 chunk 合并策略更智能,但我还是坚持手动拆。
拆包的核心思路:
- 常用且稳定的库单独成 chunk,比如 antd、vxe-table、echarts;
- Vue 全家桶放 vendor,保证框架代码的强缓存;
- 业务代码和第三方库隔离,业务发布更新时不会让用户重新下载大 vendor。
实测下来,拆包后首屏请求数量多一点,但每个包都小得多,浏览器并发加载后整体白屏时间更短。
4. 升级过程里最折磨人的几类坑
4.1 Node 版本不够,vite 直接起不来
最开始的报错是这样的:
Error: Cannot find module 'node:stream/web' Require of node:stream/web is not supported by Node 14.这就是 Vite5 要求 Node 18+ 最直白的体现。解决办法就是前面说的 nvm 切换。这里提醒两点:
- 让团队都装
.nvmrc,文件里写18.20.4,避免有人用不同版本; - CI 和 Docker 镜像也要同步改,否则本地跑得欢,发布机直接挂。
4.2 老样式插件 vite-plugin-style-import 的兼容问题
项目早期的 JeecgBoot 前端都是靠vite-plugin-style-import按需引入 antd 样式。这个插件更新已经滞后,在 Vite5 下经常出现:
- 启动不报错,但按钮、表格样式全丢;
- 运行时报某个内部方法 undefined。
原因很简单:插件内部依赖的 API 在 Vite5 中已经调整,它没有跟进。解决不是修它,而是换官方推荐的unplugin-vue-components+unplugin-auto-import组合。上面配置里的AntDesignVueResolver就是干这个的,按需引入组件和样式更干净。注意换完后要删掉之前手动 import 的全量样式文件,比如import 'ant-design-vue/dist/antd.css'这类,否则样式重复,部分组件会出现覆盖问题。
4.3 JavaScript heap out of memory
升级后第一次执行生产构建,我的项目跑到了JavaScript heap out of memory,直接崩。这是因为 Rollup4 的并发打包更激进,大项目占用堆内存更多。加大 Node 内存即可:
NODE_OPTIONS=--max-old-space-size=4096 vite build --mode production如果是在 Jenkins 里跑,记得在构建脚本里加上这行。这个问题在 Vite2 时代不常见,升级后反而容易冒出来,遇到别慌。
4.4 动态组件找不到,在线页面白屏
这是低代码平台特有的坑。我们升级后在“流程中心”打开一个流程配置页,直接白屏,控制台报:
Failed to fetch dynamically imported module: /src/views/flow/xxx.vue排查链路由浅到深:
- 先看路由配置里的 component 路径对不对;
- 再排查是否有目录改名/大小写变化;
- 最后是动态 import 路径的 glob 匹配是否覆盖到该目录。
最终发现是流程设计器某个组件用了import.meta.glob匹配了../views/**/process/*.vue,但新分支下目录名从process改成了bpm,glob 没匹配到。这不是 Vite5 的问题,是升级过程中顺手整理目录造成的副作用。经验就是:升级构建工具时,尽量不要同时重构目录结构,否则出了问题很难定位是构建链还是代码层的锅。
5. 实测对比:冷启动、构建耗时与首屏指标
5.1 冷启动对比
测试方法:清空node_modules/.vite缓存,执行npm run dev,从命令开始到终端输出 Local 地址为止计时。
| 指标 | Vite2 | Vite5 |
|---|---|---|
| 冷启动时间 | 约 35s | 约 8s |
| 首次依赖预构建 | 经常要两次 reload | 一次到位 |
| 保存代码后的 HMR 感知 | 2s 左右,重页面全刷 | 500ms 内,局部更新 |
冷启动对 JeecgBoot 的价值很大,因为后台项目要登录、要进菜单,开发调试时等启动真的非常磨人。这个数字已经足够说服团队动手。
5.2 生产构建与产物体积
| 指标 | Vite2 | Vite5 |
|---|---|---|
| 生产构建耗时 | 约 3 分钟 | 约 1 分钟 |
| dist 目录总大小 | 约 68MB | 约 51MB |
| 首屏 gzip 后资源 | 约 3.4MB | 约 2.1MB |
这些数字来自这台 16G 内存的 MacBook,机械硬盘和服务器上效果会不一样,但趋势是一致的:Rollup4 的 tree-shaking 和现代浏览器目标确实能砍掉不少冗余。
5.3 首屏指标
我在局域网部署环境用 Chrome DevTools 测了登录页和首页(清缓存、不额外做网络限速):
- FCP:从约 2.1s 降到约 1.2s;
- LCP:从约 2.6s 降到约 1.5s;
- 首屏请求体积小了一截,登录验证接口的等待不再是瓶颈。
首屏变快不全是 Vite5 的功劳,manualChunks 拆包也贡献很大。但如果没有 Vite5 的默认 target 优化和 Rollup4 的产物收敛,光靠拆包不会降这么多。所以这步就是标题里说的“性能飞跃的关键一步”。
6. 收尾验证与低代码平台特有回归点
6.1 功能回归清单
构建链升级完,最忌讳的就是只测一个登录页就宣告完成。低代码平台至少要过一遍这些场景:
- 登录、退出、菜单权限动态加载;
- Online 表单的列表查询、表单新增、表单编辑、表单删除;
- Online 表单设计器的打开和保存;
- Online 报表的 SQL 渲染和导出;
- 流程中心(flowable)的流程定义、流程设计器、待办列表;
- 系统监控、系统日志、定时任务的页面;
- 主题定制和 less 变量是否还生效;
- 低代码平台调用外部 API 的场景,比如流程回调、Online 表单数据源配置。
其中 Online 表单和流程中心是我特意标红的,因为这类页面强依赖动态组件加载,最容易受 glob 和按需引入影响。
如果你生产库是达梦这类国产数据库,前端升级本身不影响数据库连接,但建议后端接口一起联调一遍,确保列表分页、字典翻译等接口没有因为前后端版本交替出现联调遗漏。
6.2 CI/CD 同步适配
不要忘掉发布链路:
- Dockerfile 里的
node:14镜像改成node:18-alpine或更合适的 LTS 镜像; - CI 脚本里 npm 版本同步升到 9+;
- 构建命令加上
NODE_OPTIONS=--max-old-space-size=4096,免得在服务器上内存不足; - package.json 里加上
engines字段,让环境不匹配的人第一时间看到提示。
我把这些都放进了团队的发布检查单,避免“代码能跑,但一上构建机就挂”的尴尬。
6.3 给团队沉淀的经验
升级完不是终点,我建议把这次过程整理成一篇团队内部的升级笔记,至少包含:
- 升级前后的性能基线数据;
- 常见报错速查表(Node 版本、样式插件、内存溢出、动态组件白屏);
- 约定的 Node 版本和 .nvmrc 位置;
- 后续升级到新 Vite 版本时的操作顺序:先看 breaking changes、再只动构建链、最后动业务依赖。
这套沉淀比单纯升版本更有价值,下次再做类似升级,团队能省一个下午。
这次 Vite5 升级给我最大的体会是:别迷信“升级是小事”,也别被“兼容性一堆坑”吓住。真正值钱的是动手前那半个小时的基线摸底,它把后面所有报错都收敛到了可解释的范围,也让最后的数据对比有说服力。JeecgBoot 这种重前端低代码平台,构建链越顺,团队生产力释放得越明显。如果你们也还困在 Vite2 的冷启动里,建议找个小版本迭代窗口,把这事提上日程,早升早省心。