给一个 Vue2 老项目做 Webpack 到 Vite 迁移
接手过老项目的人都懂那种感受:改一行代码,等热更新等到手停在键盘上发呆。我负责迁移的这个项目是一个跑了多年的广告投放前端工程,Vue2 + Webpack,模块一年年长出来,启动和热更新的等待时间也一年年长出来。
迁移到 Vite 不是为了追新。真实动机就两个:开发时的等待成本,和 Webpack 配置本身的维护成本——那份配置文件经手过很多人,loader 和 plugin 叠了一层又一层,没人敢动。
这篇记录迁移过程中真正花时间的几个地方。不是教程,更像一份”如果重来一遍,我会先看哪里”的清单。
Vue2 项目跑 Vite,第一件事是插件层
Vite 官方模板只认 Vue3。Vue2 项目要跑起来,得先解决 SFC 编译:
- Vue 2.7+:用官方的
@vitejs/plugin-vue2 - Vue 2.6 及以下:用社区的
vite-plugin-vue2
这一步看起来只是装个插件,但有个隐蔽的前提:template 编译器版本必须和运行时版本严格一致。Vue2 的 vue-template-compiler 和 vue 是成对的,版本对不上启动时会直接报错——报错信息倒是清楚,真正麻烦的是有些老项目锁版本的方式很随意,package.json 里写的和 lock 文件里装的不是一回事,先对齐版本再动手。
另外两个连带问题:
- 项目里如果有 JSX 写法(Vue2 时代不少高阶组件这么写),要额外配 Vue2 的 JSX 插件,不配的话报错位置会出现在很奇怪的地方。
- Element UI 的按需加载原来靠
babel-plugin-component,Vite 下这条路不通。我的做法是先全量引入把项目跑通,再换unplugin-vue-components的 resolver 做按需——迁移期一次只解决一类问题,不要把”能跑”和”跑得好”混在一个阶段。
环境变量:最容易”看起来没问题”的部分
Webpack 时代的习惯是 process.env.VUE_APP_XXX,Vite 的口径是 import.meta.env.VITE_XXX。改配置文件只要十分钟,真正的工作量在业务代码里。
我踩的最深的一个坑:老代码里埋点开关是用 process.env.VUE_APP_ENV 判断的,散落在多个业务文件里。迁移后构建能通过,页面也能打开——但 process 在浏览器里根本不存在,某些分支一执行就是 process is not defined。而且这种错误只在特定操作路径下触发,冒烟测试根本测不出来。
处理方式分两步:
- 过渡期先用
define把process.env.VUE_APP_*整体映射掉,保证任何遗漏的读取点都不会炸; - 再把散落的读取点逐步收敛到一个统一的 env 模块,业务代码只从这个模块拿配置。
这个坑给我留下的习惯是:迁移前先全局搜一遍 process.env,把所有出现位置列成清单。清单比记忆可靠。
require.context 和动态 require
Webpack 对模块加载很宽容:require()、运行时拼路径、require.context 批量导入,都能跑。Vite 基于 ESM,要求导入路径静态可分析——这是两种构建哲学的根本差异,也是迁移中真正要”改代码”而不是”改配置”的部分。
分三类处理:
require.context批量导入(路由、store 模块、全局组件注册最常见):换成import.meta.glob。注意它是编译期展开的,路径规则要写死,不能运行时拼。- 普通
require():能改的直接改成静态import或动态import()。 - 运行时拼路径的
require(比如按接口返回的名字加载图片):这类没有银弹,要么用import.meta.glob预先建立映射表,要么把资源挪到public目录走 URL 引用。哪种合适取决于资源数量和更新频率。
第三方 CJS 依赖大部分靠 Vite 的依赖预构建就能吃掉,个别顽固的再用 alias 指到 ESM 版本或单独配置。我的经验是:不要一上来就给每个报错找插件,先分清是”项目代码要改”还是”依赖要兼容”,前者占的比例比想象中大。
静态资源和别名
这部分琐碎但不难,列几个必查项:
resolve.alias:把 Webpack 的别名逐条搬过来,注意 CSS 里~@/assets这种带~前缀的写法 Vite 不认,要全局清理。base:对应 Webpack 的publicPath,部署在子路径或走 CDN 的项目,构建后资源 404 十有八九是它。- 写死的
/static路径:老项目常见,统一梳理成 import 引用或挪进public。
最不能省的一步:回归
构建工具迁移最怕的不是报错,是”本地能跑、线上白屏”。开发环境 Vite 用 esbuild 做依赖预构建,生产构建走 Rollup——两条链路,某些依赖开发能跑、build 就挂是真实会发生的,所以 vite build 加产物预览必须纳入验证,不能看 dev server 正常就收工。
我当时的回归清单按业务主链路列:登录、菜单权限、核心业务页面、接口代理、静态资源、埋点上报、异常监控。埋点和监控最容易被漏——它们不影响页面显示,坏了没人第一时间发现,但对业务是暗伤。
迁移完,值不值?
说实话,我当时没有保留严格的启动/HMR 耗时对比数据,所以给不出”提升了 X%”这种数字——这也是复盘时最大的遗憾:下次做这类迁移,第一步应该是记基准数据,启动耗时、HMR 耗时、build 耗时、产物体积,前后各测一轮,收益才有据可查。
能确定的是体感:本地启动从”起身倒水”变成”眨眼”,热更新基本无感,配置从一份没人敢碰的遗产变成一份看得懂的声明。对一个还要继续维护多年的老项目,这笔账是划算的。
最后一点心得:老项目迁移的难度不在 Vite 本身,在于你得先弄清楚这个项目这些年积累了哪些”隐性约定”——哪些环境变量在哪些地方被读、哪些资源路径是写死的、哪些依赖是 CJS。迁移工具链的过程,本质上是把这些隐性约定一条条显性化的过程。工具换完,项目也顺便被重新理解了一遍。