大型 SaaS 产品的 Vite 迁移实录:从 Webpack 到 Vite 的 6 个月演进
大型 SaaS 产品的 Vite 迁移实录从 Webpack 到 Vite 的 6 个月演进一、迁移背景与前期评估该项目为面向企业客户的 SaaS 平台前端仓库包含 32 个子应用微前端架构总模块数超过 6800 个。技术栈为 React 18 TypeScript Less构建工具使用 Webpack 5构建产线为 Jenkins Docker。迁移前的构建痛点开发服务器冷启动单应用 45-90s32 个应用全量启动需约 18 分钟。HMR 延迟修改一行代码到浏览器热更新平均等待 3.2s。生产构建耗时全量构建约 14.5 分钟CI 流水线的等待时间成为交付瓶颈。配置复杂度Webpack 配置文件总量超过 3200 行包含 18 个自定义 loader 和 24 个 plugin。经过两周的技术评估确定 Vite 迁移的可行性项目以 ESM 为主TypeScript 源码核心依赖React、Ant Design、ECharts均提供 ESM 版本不存在不可绕过的 Webpack 特有功能依赖。二、基础迁移配置文件对齐2.1 resolve.alias 映射Webpack 中大量使用了别名指向src/目录Vite 中通过resolve.alias等价的配置/** * Vite 配置文件 * * 与该项目的 Webpack 配置功能等价 * 保留所有别名映射以确保导入路径不发生变化。 */ import { defineConfig } from vite; import react from vitejs/plugin-react; import path from node:path; export default defineConfig({ plugins: [ react({ // 启用 babel 以兼容部分装饰器语法 babel: { plugins: [ [babel/plugin-proposal-decorators, { legacy: true }], ], }, }), ], resolve: { alias: { : path.resolve(__dirname, src), components: path.resolve(__dirname, src/components), utils: path.resolve(__dirname, src/utils), hooks: path.resolve(__dirname, src/hooks), services: path.resolve(__dirname, src/services), types: path.resolve(__dirname, src/types), // 保留 Webpack 时期的公共组件别名 shared: path.resolve(__dirname, ../shared), }, }, css: { preprocessorOptions: { less: { // 注入全局 Less 变量等价于 Webpack 的 style-resources-loader additionalData: import /styles/variables.less; import /styles/mixins.less; , javascriptEnabled: true, }, }, modules: { // CSS Modules 命名规则与 Webpack 保持一致 localsConvention: camelCaseOnly, generateScopedName: [name]__[local]___[hash:base64:5], }, }, });2.2 环境变量兼容Webpack 通过process.env.XXX注入环境变量Vite 使用import.meta.env.XXX。迁移阶段采用兼容性中间层/** * 环境变量兼容层 * * 统一提供 process.env 的访问方式 * 平滑过渡到 import.meta.env减少业务代码改动。 * * 使用方式在入口文件最顶部引入 * import ./env-compat; */ // src/env-compat.ts if (typeof process undefined || !process.env) { (globalThis as Recordstring, unknown).process { env: {} as Recordstring, string, }; } // 将 Vite 环境变量映射到 process.env 上 const envKeys Object.keys(import.meta.env); for (const key of envKeys) { if (key.startsWith(VITE_)) { // 移除 VITE_ 前缀以保持与 Webpack 时期一致 const legacyKey key.replace(/^VITE_/, ); (process.env as Recordstring, string)[legacyKey] ( import.meta.env as Recordstring, string )[key]; } }2.3 require 语法的处理项目中存量代码中存在require.context和动态require的情况。对于自动化加载场景如自动注册全局组件、自动导入路由使用import.meta.glob替代/** * Webpack require.context → Vite import.meta.glob 迁移 * * 原始代码Webpack: * const modules require.context(./modules, true, /\.tsx$/); * modules.keys().forEach(key { ... }); * * 迁移后Vite: */ const modules import.meta.glob{ default: React.ComponentType }( ./modules/**/*.tsx, { eager: true } ); // 保持与原有 API 一致的使用方式 for (const [path, module] of Object.entries(modules)) { const componentName path .replace(./modules/, ) .replace(/\.tsx$/, ) .replace(/\//g, _); registerComponent(componentName, module.default); }三、深度适配自定义 Vite 插件3.1 微前端子应用的构建适配项目基于 qiankun 的微前端架构子应用需要导出bootstrap、mount、unmount生命周期。Vite 的默认构建产物格式为 ESM而 qiankun 需要通过window全局访问子应用因此需要自定义构建配置/** * Vite 插件微前端子应用构建适配 * * 确保构建产物符合 qiankun 的加载要求 * 1. 格式为 UMD通过 window 导出 * 2. 入口 JS 和 CSS 文件名可预测用于主应用动态加载 * 3. publicPath 在运行时动态注入 */ import type { Plugin } from vite; interface MicroAppPluginOptions { /** 子应用名称用于 window 挂载 */ appName: string; /** 构建目标默认 es2015 */ target?: string; } export function microAppPlugin(options: MicroAppPluginOptions): Plugin { const { appName, target es2015 } options; return { name: vite-plugin-micro-app, config(config) { return { ...config, base: //cdn.example.com/micro-apps/${appName}/, build: { ...config.build, target, // 类库模式构建以 UMD 格式暴露 lib: { entry: src/index.tsx, name: appName, formats: [umd], fileName: () index.js, }, rollupOptions: { // 排除主应用提供的公共依赖 external: [react, react-dom, antd, moment], output: { globals: { react: React, react-dom: ReactDOM, antd: antd, moment: moment, }, assetFileNames: index.[ext], }, }, }, }; }, // 在 HTML 中注入 publicPath 动态设置逻辑 transformIndexHtml(html) { return html.replace( /head, script // 动态设置 publicPath支持不同环境部署 if (window.__POWERED_BY_QIANKUN__) { __webpack_public_path__ window.__INJECTED_PUBLIC_PATH_BY_QIANKUN__; } /script/head ); }, }; }3.2 SVG 图标组件的兼容原 Webpack 配置中使用svgr/webpack将 SVG 文件作为 React 组件导入。Vite 中对应使用vite-plugin-svgr插件确保导入方式完全一致。3.3 传统构建产物的兼容处理项目中有 3 个老旧依赖使用 AMD 格式且不提供 ESM 版本。通过编写 Vite 插件在transform阶段做代码转换将 AMD 的define包装转换为 ESM 格式。四、性能对比与优化4.1 核心指标对比指标Webpack 5Vite提升幅度开发服务器启动单应用68s2.1s97%HMR 响应延迟3.2s62ms98%生产构建单应用4.5min1.8min60%CI 构建32 应用并行14.5min4.2min71%首屏 JS 体积gzip487KB412KB15%4.2 拆包优化策略Vite/Rollup 的默认拆包策略比较保守针对大型 SaaS 应用需要手动配置拆包策略以获得更优的缓存命中率/** * 自定义拆包策略 * * 目标将第三方依赖按更新频率分层 * 最大化浏览器缓存利用率。 */ // vite.config.ts 的 build.rollupOptions.output.manualChunks manualChunks(id: string) { // 框架层React 生态更新频率最低 if (id.includes(node_modules/react) || id.includes(node_modules/react-dom) || id.includes(node_modules/react-router)) { return framework; } // UI 层Ant Design中等更新频率 if (id.includes(node_modules/antd) || id.includes(node_modules/ant-design)) { return antd; } // 图表层ECharts体积大但更新频率低 if (id.includes(node_modules/echarts) || id.includes(node_modules/zrender)) { return echarts; } // 工具层lodash/moment/dayjs更新频率较低 if (id.includes(node_modules/lodash) || id.includes(node_modules/moment) || id.includes(node_modules/dayjs)) { return utils; } // 业务公共代码体积适中与业务迭代同步更新 if (id.includes(src/shared) || id.includes(src/common)) { return common; } // 其余第三方依赖 if (id.includes(node_modules)) { return vendor; } }4.3 迁移中的意外发现迁移完成后的一次 Code Review 中发现 Webpack 时期的ts-loader配置中transpileOnly: true开启了但对应的fork-ts-checker-webpack-plugin却在某次升级中意外失效。这意味着项目在过去 4 个月中CI 没有执行完整的类型检查。切换到 Vite 后团队同时引入vite-plugin-checker确保类型检查在开发和 CI 阶段始终有效。这一问题也直接促成了 CI 流水线中增加独立的tsc --noEmit检查步骤。五、总结六个月、32 个子应用、6800 模块的迁移技术决策的核心经验渐进式迁移优先先迁移一个中等复杂度的子应用作为样板积累配置模板和踩坑经验后再推广避免全面铺开导致的风险。兼容性优先于彻底性环境变量兼容层、别名映射等过渡代码在迁移阶段是必要的。彻底废弃旧模式应该安排在迁移稳定后作为独立迭代进行。迁移是质量检查的机会在迁移过程中发现的类型检查缺失、废弃依赖等问题应当作为迁移任务的一部分一并解决。灰度发布不可省略通过特性开关Feature Flag控制新旧构建产物的下发比例先覆盖内部用户逐步扩大到外网全量是风险最低的切换方式。在 Vite 6基于 Rolldown正式稳定后还可以考虑进一步迁移到 Rolldown 获得更快的生产构建速度。这将是下一个迭代周期的话题。

相关新闻

AI在电商价格策略中的应用:动态定价模型与实时调价的后端引擎

AI在电商价格策略中的应用:动态定价模型与实时调价的后端引擎

AI在电商价格策略中的应用:动态定价模型与实时调价的后端引擎 一、动态定价的业务背景 电商平台的商品定价已经从"运营手工改价"演进到"算法自动调价"。驱动这一变化的核心原因是定价因素的复杂度爆炸:一个爆款商品的价格受竞品价格…

2026/10/12 19:27:25 阅读更多 →
电商场景下的AI智能客服:从意图识别到多轮对话的后端架构设计

电商场景下的AI智能客服:从意图识别到多轮对话的后端架构设计

电商场景下的AI智能客服:从意图识别到多轮对话的后端架构设计 一、背景与问题定义 电商客服系统承载着售前咨询、售后处理、物流查询等多条业务线。在日均百万级咨询量的场景下,传统关键词匹配的规则引擎已经无法满足复杂意图的识别需求。一个典型的用户…

2026/10/10 11:50:39 阅读更多 →
中小团队AI分析转型生死线:预算<5万/年?这3款轻量级AI工具实测支持本地化部署+离线推理+中文财报结构化提取(附适配MySQL/Oracle/ClickHouse的Schema映射模板)

中小团队AI分析转型生死线:预算<5万/年?这3款轻量级AI工具实测支持本地化部署+离线推理+中文财报结构化提取(附适配MySQL/Oracle/ClickHouse的Schema映射模板)

更多请点击: https://codechina.net 第一章:中小团队AI分析转型生死线:预算<5万/年?这3款轻量级AI工具实测支持本地化部署离线推理中文财报结构化提取(附适配MySQL/Oracle/ClickHouse的Schema映射模板&…

2026/10/10 15:48:05 阅读更多 →

最新新闻

CSS3动画实战指南:从transition到keyframes的完整解析与性能优化

CSS3动画实战指南:从transition到keyframes的完整解析与性能优化

1. 先搞清楚CSS3动画的四大家族做前端这行,绕不开一个判断——CSS3动画到底应该怎么学。很多同行一上来就背属性列表,transition、transform、animation全记住,真到做项目的时候照样抓瞎。我自己的体会是,CSS3动画这套东西&#x…

2026/10/13 4:11:02 阅读更多 →
Unity屏幕适配全解析:UI锚点与2D相机正交尺寸实战

Unity屏幕适配全解析:UI锚点与2D相机正交尺寸实战

接手一个2D横版项目的第二周,我还没把客户那台19.5:9的新手机当回事,测试老设备时主界面右上角的技能按钮直接跑到了屏幕边缘外面,数字被截掉一半。当时项目里UI按16:9的参考分辨率摆得整整齐齐,代码全是固定坐标,一换…

2026/10/13 4:11:02 阅读更多 →
分块、PARTITION BY、分区三者区别:从数据处理到SQL窗口函数到存储架构

分块、PARTITION BY、分区三者区别:从数据处理到SQL窗口函数到存储架构

1. 先别急着记定义:三个词回答的是三个不同的问题你大概率是在这几个场景里分别遇见它们的:前端要搞大文件分块上传,搜到 lodash 的 chunk;刷 SQL 题看到 PARTITION BY 开窗函数;建表时又被 DBA 提了一嘴"这张表要…

2026/10/13 4:11:02 阅读更多 →
Linux运维必知:\cp命令绕过别名机制的原理与实践

Linux运维必知:\cp命令绕过别名机制的原理与实践

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/13 4:11:02 阅读更多 →
争议型代谢研究高分模板:从选题设计到证据链构建的完整拆解

争议型代谢研究高分模板:从选题设计到证据链构建的完整拆解

昨天半夜刷到一条科研圈的推送:某医科大学代谢团队,用一组最新数据,把此前吵了好几年的一个代谢机制争议彻底收口,文章直接落在一区Top期刊,IF 11.9。点赞很多,但评论区讨论其实有点跑偏——大家只盯着“多…

2026/10/13 4:11:02 阅读更多 →
操作系统处理器管理五条主线:进程调度同步与死锁全解析

操作系统处理器管理五条主线:进程调度同步与死锁全解析

操作系统教材翻到第二章,很多人的崩溃是从“处理器管理”这五个字开始的。前面还觉得自己能理解计算机怎么启动、中断怎么触发,看到进程状态转换和调度算法还勉强能背,等遇到信号量和死锁,就开始怀疑自己是不是不适合学操作系统。…

2026/10/13 4:10:01 阅读更多 →

日新闻

物联网系统集成避坑指南:从选型到验收的关键要点

物联网系统集成避坑指南:从选型到验收的关键要点

做了这么多年物联网系统集成的项目,我最大的感受是,大部分坑不是出在技术前沿,而是出在一些看起来很平常的地方。设备选了便宜的、协议没问清楚、现场没摸透、验收标准写得含糊——这些细枝末节,最后都会变成延期、超支和扯皮的大…

2026/10/13 0:00:57 阅读更多 →
ESP32舵机控制全攻略:从PWM原理到高级玩法与常见故障排查

ESP32舵机控制全攻略:从PWM原理到高级玩法与常见故障排查

如果你玩ESP32有一阵子了,很难不碰到舵机控制这道坎。做智能小车要转方向,做机械臂要抬关节,做云台要偏航俯仰,背后都是同一个东西在干活:舵机。而我见过太多人卡在“代码能编译、舵机就是不听话”这个阶段&#xff0c…

2026/10/13 0:00:57 阅读更多 →
CAN总线原理与STM32实战:抗干扰、仲裁、错误处理全解析

CAN总线原理与STM32实战:抗干扰、仲裁、错误处理全解析

1. 为什么CAN总线至今仍是工业现场的“硬通货”?——从汽车底盘到智能温室的真实需求你有没有拆过一辆老款燃油车的中控台?或者调试过某高校实验室里那台跑着Modbus协议却频频丢帧的PLC控制柜?又或者,在某个农业物联网项目里&…

2026/10/13 0:00:57 阅读更多 →

周新闻

流感时间序列预测实战:ARIMA/LSTM全流程拆解与避坑指南

流感时间序列预测实战:ARIMA/LSTM全流程拆解与避坑指南

简介:基于 ARIMA、LSTM、Transformer 等模型的流感时间序列预测 Python 源码,面向计算机相关专业课程设计与期末大作业学生,以及项目实战学习者。内容覆盖预处理、平稳性检验、定阶、残差分析、多模型对比预测的完整时序建模流程,…

2026/10/13 2:54:56 阅读更多 →
影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别

影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别

影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别 做影刀RPA自动化,十个新手有八个栽在"往输入框里填东西"这件事上:要么填不进去,要么填了一半,要么直接把原来内容追加在后面。这背后的根因&…

2026/10/12 0:16:38 阅读更多 →
影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容

影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容

影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容 1. 认识影刀:什么场景该用RPA采小说数据 起点中文网的页面结构相对稳定——分类榜单、书籍详情、章节内容三块独立页面,跳转链路清晰。这种场景非常适合影刀自动化&#x…

2026/10/13 1:58:08 阅读更多 →

月新闻

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/12 21:05:09 阅读更多 →
Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/12 14:48:49 阅读更多 →
黑夜航拍船只数据集训练YOLOV5模型全流程解析

黑夜航拍船只数据集训练YOLOV5模型全流程解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/12 18:11:17 阅读更多 →