Webpack 迁 Vite 别硬切:双构建与 Legacy 语法兼容
Webpack 迁 Vite 别硬切双构建与 Legacy 语法兼容Webpack 项目迁移到 Vite 时常见差异包括require.context()、process.env、资源路径和 CSS 解析。一次替换所有配置会把这些差异集中暴露排查成本很高。迁移 Vite 的工程精髓在于双引擎平滑共存、分阶段渐进切换。flowchart TD A[大型 Webpack 存量旧系统] -- B[双构建配置文件共存阶段] B -- C[建立环境变量与模块全局兼容适配层 Node.js Bridge] C -- D[开发环境优先接入 Vite 快速冷启动] D -- E{校验编译与 HMR 稳定性} E -- 报错/兼容卡点 -- F[使用 Vite 自定义插件拦截并重写 legacy 语法] E -- 验证通过 -- G[预发/生产环境分阶段接入 Vite Build] F -- D G -- H[完全废弃 Webpack 依赖完成渐进式迁移]1. 大型 Webpack 系统迁移 Vite 的三大语法深水坑从 Webpack 切换到基于 ES ModulesESM的 Vite最棘手的不是配置文件的差异而是代码库里长期积累的非标准 CommonJS 语法与打包工具特有 API。主要有三个深水坑require()与require.context()动态加载Webpack 支持这些工具特有 APIVite 项目应将其迁移为明确的 ESM 导入或import.meta.glob()。process.env全局变量依赖Webpack 依赖DefinePlugin把process.env全局注入到了组件树。Vite 默认只识别import.meta.env导致项目里所有引用了process.env.XXX的代码全部爆出引用错误。隐式全局 CSS 与资源别名 (Alias) 机制差异Webpack 的路径别名在 CSSimport中通常带有~前缀如~/styles/base.less而 Vite 对路径解析的逻辑与浏览器原生的 URL 解析完全一致~前缀直接抛出找不到文件。想平滑过渡你必须写一套兼容桥接层Compatibility Bridge。2. 编写自定义 Vite 插件处理 Legacy 语法兼容我们编写一个专门用来适配 Webpack 遗留代码的 Vite 自定义插件vite-plugin-webpack-legacy-bridge。插件可以帮助识别遗留语法但不宜用字符串替换把require()或require.context()自动改写为语义不同的 Vite API。应将它作为迁移告警与清单工具逐处重构并添加测试。import type { Plugin } from vite; export interface LegacyBridgeOptions { envPrefix?: string; aliasMap?: Recordstring, string; } export function viteWebpackLegacyBridgePlugin(options: LegacyBridgeOptions {}): Plugin { const aliasMap options.aliasMap || { ~/: /src/ }; return { name: vite-plugin-webpack-legacy-bridge, enforce: pre, // 确保在 Vite 标准解析之前拦截 transform(code, id) { // 避开 node_modules 第三方依赖 if (id.includes(node_modules)) return null; let transformedCode code; // 1. 兼容转换替换 process.env.NODE_ENV 与自定义环境变量 if (transformedCode.includes(process.env)) { transformedCode transformedCode.replace( /process\.env\.([A-Z0-9_])/g, (match, p1) { if (p1 NODE_ENV) { return JSON.stringify(process.env.NODE_ENV || development); } // 桥接至 Vite 的 import.meta.env return (import.meta.env.VITE_${p1} ?? process.env.${p1} ?? ); } ); } // 2. 兼容转换清洗 CSS / Less / Sass 文件中的 ~ 别名前缀 if (id.endsWith(.css) || id.endsWith(.less) || id.endsWith(.scss) || id.endsWith(.vue)) { for (const [legacyPrefix, newPrefix] of Object.entries(aliasMap)) { if (transformedCode.includes(legacyPrefix)) { transformedCode transformedCode.split(legacyPrefix).join(newPrefix); } } } // 3. 兼容转换替换简单的 require.context() 为 Vite 的 import.meta.glob() if (transformedCode.includes(require.context)) { transformedCode transformedCode.replace( /require\.context\(([^)])\)/g, (match, args) { console.warn([Vite Migration Warning]: 发现 legacy require.context() 调用在 [${id}]推荐重构为 import.meta.glob()); // 自动桥接为 Vite 原生的同步模块加载 return import.meta.glob(${args}, { eager: true }); } ); } return { code: transformedCode, map: null, // 保持 source map 干净 }; }, }; }迁移时优先把require.context()改为明确的import.meta.glob()用法并将环境变量改为import.meta.env。每个模块应在开发与生产构建中验证。3. Webpack-to-Vite 双构建共存配置范式在迁移的前两个周切记不要删掉webpack.config.js。正确的架构是保持package.json中的构建脚本双轨并行npm run dev:legacy- 继续跑 Webpack 开发服务器保障线上紧急 Bug 修复时不被 Vite 迁移阻断。npm run dev- 跑全新的 Vite 极速开发环境。下面是用于说明双构建共存的vite.config.ts示例别名、全局定义和依赖兼容项需要按项目逐项确认。import { defineConfig } from vite; import vue from vitejs/plugin-vue; import path from path; import { viteWebpackLegacyBridgePlugin } from ./plugins/viteWebpackLegacyBridge; export default defineConfig({ plugins: [ vue(), // 插入 Webpack 兼容桥接插件 viteWebpackLegacyBridgePlugin({ aliasMap: { ~/: /src/, ~styles/: /src/styles/, }, }), ], resolve: { alias: { // 保持与 Webpack 相同的别名映射规则 : path.resolve(__dirname, ./src), components: path.resolve(__dirname, ./src/components), }, }, define: { // 仅为已确认的兼容依赖提供常量不要用 window 或空对象伪造 Node 运行时。 __APP_LEGACY__: JSON.stringify(true), }, server: { port: 8080, // 桥接原 Webpack devServer 的 Proxy 代理 proxy: { /api: { target: http://localhost:9000, changeOrigin: true, rewrite: (path) path.replace(/^\/api/, ), }, }, }, build: { // 渐进式构建输出目录避免覆盖旧 Webpack 的 dist outDir: dist-vite, rollupOptions: { output: { // 拆分 vendor 模块防止单文件体积过大 manualChunks: { vendor-core: [vue, vue-router, pinia], }, }, }, }, });4. 存量系统渐进迁移的“四步切频法”构建工具迁移可以按四步推进第一步入口插桩与双轨并行在根目录新建index.html作为 Vite 的入口文件同时保留 Webpack 的public/index.html。保持两条 npm script 命令共存。第二步开发环境验证 (Dev First)选择一个业务入口接入 Vite记录冷启动、HMR、路由和资源加载的基线遇到报错时按迁移清单逐项修复。第三步代码库 Legacy 语法清理在日常迭代中慢慢把require.context()批量替换为import.meta.glob()把process.env全面规范为import.meta.env。第四步生产构建接管 (Build Takeover)在预发比较两套构建的产物、关键路径和错误监控确认回滚方案后再逐步切换生产流量。每一步都保留两套构建的产物对比和失败记录。兼容问题清单归零、关键页面回归通过后再关闭旧构建入口。