Webpack 迁移到 Vite
问题
如何将一个已有的 Webpack 项目迁移到 Vite?迁移过程中有哪些坑点?如何制定渐进式迁移策略?
答案
将 Webpack 项目迁移到 Vite 是当下前端工程化升级的热门话题。Vite 利用浏览器原生 ESM 和 esbuild 预构建,在开发阶段可以实现近乎即时的冷启动和毫秒级 HMR。但迁移并非简单的"换个构建工具",需要系统性地评估、规划和执行。
Webpack → Vite 的迁移本质上是 从 Bundle-based 开发模式切换到 Native ESM 开发模式。开发环境收益巨大(启动和 HMR 快 10-100 倍),生产环境改善相对温和。
为什么要迁移
Webpack 的痛点
随着项目规模增长,Webpack 在开发体验上暴露出明显瓶颈:
// 1. 冷启动慢:中大型项目 30s-2min
// Webpack 需要解析整个依赖图 → 编译所有模块 → 打包 → 启动 DevServer
// 2. HMR 慢:修改一个文件后等待 2-10s
// 即使只改了一行代码,也可能触发大量模块重新编译
// 3. 配置复杂:各种 Loader/Plugin 的组合
const config = {
module: {
rules: [
{ test: /\.tsx?$/, use: 'ts-loader' },
{ test: /\.scss$/, use: ['style-loader', 'css-loader', 'sass-loader'] },
{ test: /\.svg$/, use: '@svgr/webpack' },
// ... 数十个规则
],
},
plugins: [
new HtmlWebpackPlugin(),
new MiniCssExtractPlugin(),
new DefinePlugin(),
// ... 十几个插件
],
};
// 4. 构建产物体积优化需要额外配置
// splitChunks、terser、css-minimizer 等一堆优化插件
Vite 的优势
| 维度 | Webpack | Vite |
|---|---|---|
| 冷启动 | 需要打包所有模块(30s-2min) | 原生 ESM 按需加载(<1s) |
| HMR 速度 | 重新编译受影响模块(2-10s) | 精确更新单个模块(<50ms) |
| 配置复杂度 | 高(Loader + Plugin 组合) | 低(开箱即用,约定优于配置) |
| TypeScript | 需要 ts-loader 或 babel | 原生支持(esbuild 编译) |
| CSS 预处理器 | 需要配置多个 Loader | 只需安装预处理器本身 |
| 生态 | 最成熟,插件最多 | 快速增长,兼容 Rollup 插件 |
Vite 的 No-Bundle 策略在超大型项目(数万模块)中可能出现浏览器请求瀑布问题。对于 IE11 等旧浏览器兼容需求,Webpack 仍是更稳妥的选择。
迁移前评估
迁移前需要从多个维度评估可行性,避免盲目迁移导致项目陷入困境:
评估维度
| 评估项 | 低风险 | 高风险 |
|---|---|---|
| 项目规模 | 中小型、模块数 < 5000 | 超大型 monorepo |
| 依赖兼容性 | 纯 ESM 依赖为主 | 大量 CommonJS-only 依赖 |
| 浏览器兼容 | 现代浏览器 | 需要支持 IE11 |
| 自定义 Webpack 配置 | 标准配置 | 深度定制 Loader/Plugin |
| 团队熟悉度 | 熟悉 Vite/Rollup | 只熟悉 Webpack |
| CI/CD 环境 | 灵活可调 | Node 版本受限 |
决策流程图
迁移步骤
以一个典型的 React + TypeScript + Webpack 项目为例,详细介绍每个迁移步骤。
第一步:安装 Vite 及相关依赖
- npm
- Yarn
- pnpm
- Bun
npm install vite @vitejs/plugin-react -D
yarn add vite @vitejs/plugin-react --dev
pnpm add vite @vitejs/plugin-react -D
bun add vite @vitejs/plugin-react --dev
如果项目使用了 SCSS、SVG 等,还需安装对应依赖:
- npm
- Yarn
- pnpm
- Bun
npm install sass vite-plugin-svgr -D
yarn add sass vite-plugin-svgr --dev
pnpm add sass vite-plugin-svgr -D
bun add sass vite-plugin-svgr --dev
同时可以卸载不再需要的 Webpack 相关依赖:
如果采用渐进式迁移策略(先迁开发环境),暂时不要卸载 Webpack 相关依赖,等生产环境也迁移完成后再清理。
- npm
- Yarn
- pnpm
- Bun
npm uninstall webpack webpack-cli webpack-dev-server html-webpack-plugin mini-css-extract-plugin css-loader style-loader sass-loader ts-loader babel-loader @svgr/webpack
yarn remove webpack webpack-cli webpack-dev-server html-webpack-plugin mini-css-extract-plugin css-loader style-loader sass-loader ts-loader babel-loader @svgr/webpack
pnpm remove webpack webpack-cli webpack-dev-server html-webpack-plugin mini-css-extract-plugin css-loader style-loader sass-loader ts-loader babel-loader @svgr/webpack
bun remove webpack webpack-cli webpack-dev-server html-webpack-plugin mini-css-extract-plugin css-loader style-loader sass-loader ts-loader babel-loader @svgr/webpack
第二步:创建 vite.config.ts
在项目根目录创建 vite.config.ts,将原有 Webpack 配置逐项迁移:
import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';
import svgr from 'vite-plugin-svgr';
import path from 'path';
export default defineConfig({
plugins: [react(), svgr()],
resolve: {
alias: {
'@': path.resolve(__dirname, './src'),
},
},
css: {
preprocessorOptions: {
scss: {
additionalData: `@import "@/styles/variables.scss";`,
},
},
},
server: {
port: 3000,
proxy: {
'/api': {
target: 'http://localhost:8080',
changeOrigin: true,
rewrite: (path) => path.replace(/^\/api/, ''),
},
},
},
define: {
__APP_VERSION__: JSON.stringify(process.env.npm_package_version),
},
build: {
outDir: 'build',
sourcemap: true,
rollupOptions: {
output: {
manualChunks: {
vendor: ['react', 'react-dom'],
router: ['react-router-dom'],
},
},
},
},
});
第三步:修改 index.html
Vite 要求 index.html 位于项目根目录(而非 public/),并使用原生 ESM 引入入口脚本:
import fs from 'fs';
import path from 'path';
// 1. 将 index.html 从 public/ 移动到项目根目录
const source = path.resolve('public/index.html');
const target = path.resolve('index.html');
fs.renameSync(source, target);
// 2. 修改 index.html 内容
let html = fs.readFileSync(target, 'utf-8');
// 移除 Webpack 的模板语法
html = html.replace(/<%= htmlWebpackPlugin\.options\.title %>/g, '我的应用');
html = html.replace(/%PUBLIC_URL%/g, '');
// 在 </body> 前添加入口 script
html = html.replace(
'</body>',
' <script type="module" src="/src/main.tsx"></script>\n</body>'
);
fs.writeFileSync(target, html);
迁移前后的 index.html 对比:
<!DOCTYPE html>
<html>
<head>
<title><%= htmlWebpackPlugin.options.title %></title>
<link rel="icon" href="%PUBLIC_URL%/favicon.ico" />
</head>
<body>
<div id="root"></div>
<!-- Webpack 自动注入 script 标签 -->
</body>
</html>
<!DOCTYPE html>
<html>
<head>
<title>我的应用</title>
<link rel="icon" href="/favicon.ico" />
</head>
<body>
<div id="root"></div>
<script type="module" src="/src/main.tsx"></script>
</body>
</html>
第四步:环境变量迁移
Webpack 使用 process.env,Vite 使用 import.meta.env,且前缀规则不同:
| Webpack(CRA) | Vite | 说明 |
|---|---|---|
REACT_APP_ | VITE_ | 客户端暴露前缀 |
process.env.REACT_APP_API_URL | import.meta.env.VITE_API_URL | 访问方式 |
process.env.NODE_ENV | import.meta.env.MODE | 当前模式 |
process.env.NODE_ENV === 'production' | import.meta.env.PROD | 是否生产环境 |
process.env.NODE_ENV === 'development' | import.meta.env.DEV | 是否开发环境 |
// .env(Webpack / CRA)
// REACT_APP_API_URL=https://api.example.com
// REACT_APP_VERSION=$npm_package_version
// .env(Vite)
// VITE_API_URL=https://api.example.com
// VITE_APP_VERSION=$npm_package_version
批量替换代码中的环境变量引用:
import fs from 'fs';
import path from 'path';
import { glob } from 'glob';
async function migrateEnvVars(): Promise<void> {
const files = await glob('src/**/*.{ts,tsx}');
for (const file of files) {
let content = fs.readFileSync(file, 'utf-8');
// 替换环境变量前缀和访问方式
content = content.replace(
/process\.env\.REACT_APP_(\w+)/g,
'import.meta.env.VITE_$1'
);
content = content.replace(
/process\.env\.NODE_ENV\s*===\s*['"]production['"]/g,
'import.meta.env.PROD'
);
content = content.replace(
/process\.env\.NODE_ENV\s*===\s*['"]development['"]/g,
'import.meta.env.DEV'
);
content = content.replace(
/process\.env\.NODE_ENV/g,
'import.meta.env.MODE'
);
fs.writeFileSync(file, content);
}
}
migrateEnvVars();
为了获得 TypeScript 类型提示,需要添加环境变量类型声明:
/// <reference types="vite/client" />
interface ImportMetaEnv {
readonly VITE_API_URL: string;
readonly VITE_APP_VERSION: string;
}
interface ImportMeta {
readonly env: ImportMetaEnv;
}
第五步:路径别名迁移
// ❌ Webpack
const webpackConfig = {
resolve: {
alias: {
'@': path.resolve(__dirname, 'src'),
'@components': path.resolve(__dirname, 'src/components'),
'@utils': path.resolve(__dirname, 'src/utils'),
},
extensions: ['.ts', '.tsx', '.js', '.jsx'],
},
};
// ✅ Vite
import { defineConfig } from 'vite';
import path from 'path';
export default defineConfig({
resolve: {
alias: {
'@': path.resolve(__dirname, 'src'),
'@components': path.resolve(__dirname, 'src/components'),
'@utils': path.resolve(__dirname, 'src/utils'),
},
// Vite 默认支持 .ts/.tsx/.js/.jsx,无需额外配置 extensions
},
});
路径别名需要在 tsconfig.json 中同步配置 paths,否则 TypeScript 编译器无法识别:
{
"compilerOptions": {
"baseUrl": ".",
"paths": {
"@/*": ["src/*"],
"@components/*": ["src/components/*"],
"@utils/*": ["src/utils/*"]
}
}
}
第六步:CSS / 资源处理迁移
Vite 对 CSS 和资源的处理大幅简化,大部分场景开箱即用:
// ❌ Webpack 需要配置多个 Loader
const webpackConfig = {
module: {
rules: [
{
test: /\.css$/,
use: ['style-loader', 'css-loader', 'postcss-loader'],
},
{
test: /\.scss$/,
use: ['style-loader', 'css-loader', 'postcss-loader', 'sass-loader'],
},
{
test: /\.module\.scss$/,
use: [
'style-loader',
{ loader: 'css-loader', options: { modules: true } },
'sass-loader',
],
},
],
},
};
// ✅ Vite 开箱即用
// 只需安装 sass 依赖即可,无需任何配置:
// pnpm add -D sass
// CSS Modules 自动识别 *.module.scss 文件
// PostCSS 自动读取 postcss.config.js
静态资源处理对比:
// Webpack 中的资源导入(需要对应 Loader)
import logo from './logo.png'; // file-loader / url-loader
import styles from './app.module.css'; // css-loader + modules
import rawText from '!!raw-loader!./template.html'; // raw-loader
// Vite 中的资源导入(内置支持,无需配置)
import logo from './logo.png'; // 直接支持
import styles from './app.module.css'; // 自动识别 .module
import rawText from './template.html?raw'; // ?raw 后缀
import workerUrl from './worker.ts?worker&url'; // Web Worker
第七步:代理配置迁移
// ❌ Webpack DevServer
const webpackConfig = {
devServer: {
proxy: {
'/api': {
target: 'http://localhost:8080',
changeOrigin: true,
pathRewrite: { '^/api': '' },
},
'/ws': {
target: 'ws://localhost:8080',
ws: true,
},
},
},
};
// ✅ Vite Server(几乎相同,注意 pathRewrite → rewrite)
export default defineConfig({
server: {
proxy: {
'/api': {
target: 'http://localhost:8080',
changeOrigin: true,
rewrite: (path) => path.replace(/^\/api/, ''),
},
'/ws': {
target: 'ws://localhost:8080',
ws: true,
},
},
},
});
第八步:全局变量处理
Webpack 的 DefinePlugin 对应 Vite 的 define 选项:
// ❌ Webpack DefinePlugin
const webpack = require('webpack');
module.exports = {
plugins: [
new webpack.DefinePlugin({
__DEV__: JSON.stringify(process.env.NODE_ENV === 'development'),
__APP_VERSION__: JSON.stringify(require('./package.json').version),
'process.env.API_URL': JSON.stringify('https://api.example.com'),
}),
],
};
// ✅ Vite define
export default defineConfig({
define: {
__DEV__: JSON.stringify(true),
__APP_VERSION__: JSON.stringify(process.env.npm_package_version),
// 注意:Vite 的 define 做的是纯文本替换
// 字符串值必须用 JSON.stringify 包裹
},
});
Vite 的 define 是编译时纯文本替换,不像 DefinePlugin 会做语法分析。如果 define 一个对象,需要 JSON.stringify 整个对象,否则会报错:
// ❌ 错误写法
define: {
__CONFIG__: { apiUrl: 'https://api.example.com' }
}
// ✅ 正确写法
define: {
__CONFIG__: JSON.stringify({ apiUrl: 'https://api.example.com' })
}
Webpack 配置 → Vite 配置完整映射表
| Webpack 配置 | Vite 配置 | 备注 |
|---|---|---|
entry | 不需要 | Vite 从 index.html 中的 <script> 自动识别 |
output.path | build.outDir | 默认 dist |
output.publicPath | base | 默认 / |
output.filename | build.rollupOptions.output.entryFileNames | Vite 自动生成带 hash 的文件名 |
resolve.alias | resolve.alias | 用法基本一致 |
resolve.extensions | resolve.extensions | Vite 默认支持 .ts/.tsx/.js/.jsx |
module.rules(各种 Loader) | 插件 或 内置支持 | 大部分开箱即用 |
devServer.proxy | server.proxy | pathRewrite → rewrite 函数 |
devServer.port | server.port | 用法一致 |
devServer.https | server.https | 用法一致 |
plugins: [HtmlWebpackPlugin] | 不需要 | 直接使用根目录 index.html |
plugins: [DefinePlugin] | define | 注意 JSON.stringify |
plugins: [CopyWebpackPlugin] | vite-plugin-static-copy | 或直接放 public/ 目录 |
plugins: [MiniCssExtractPlugin] | 不需要 | 生产构建自动提取 CSS |
optimization.splitChunks | build.rollupOptions.output.manualChunks | API 不同,需要重写 |
optimization.minimizer | build.minify | 默认 esbuild,可切换 terser |
externals | build.rollupOptions.external | 配合 CDN 使用 |
devtool | build.sourcemap | 开发模式默认开启 |
stats | 无直接对应 | Vite 日志更简洁 |
常见坑点与解决方案
坑点一:CommonJS 依赖不兼容
Vite 开发模式基于原生 ESM,遇到纯 CJS 的依赖会报错。
export default defineConfig({
optimizeDeps: {
// 强制预构建 CJS 依赖,将其转换为 ESM
include: [
'lodash', // 纯 CJS 包
'moment', // 有 CJS 主入口
'react-dom', // 嵌套依赖
'some-lib > nested-cjs-dep', // 嵌套的 CJS 依赖
],
},
});
如果启动后页面报 require is not defined 或 module is not defined 错误,将对应的包加入 optimizeDeps.include 即可。
坑点二:require() 和 require.context 替换
Vite 不支持 CommonJS 的 require() 语法,需要替换为 ESM:
// ❌ Webpack 的 require
const logo = require('./logo.png');
const config = require('./config.json');
// ✅ Vite 的 ESM import
import logo from './logo.png';
import config from './config.json';
// ❌ Webpack 的 require.context(批量导入)
const context = require.context('./modules', true, /\.ts$/);
context.keys().forEach((key: string) => {
const module = context(key);
// ...
});
// ✅ Vite 的 import.meta.glob
const modules = import.meta.glob('./modules/**/*.ts', { eager: true });
for (const [path, module] of Object.entries(modules)) {
// module 就是导出内容
console.log(path, module);
}
// 懒加载版本(返回 Promise)
const lazyModules = import.meta.glob('./modules/**/*.ts');
for (const [path, importFn] of Object.entries(lazyModules)) {
const module = await importFn();
}
坑点三:环境变量前缀变化
除了前面提到的 REACT_APP_ → VITE_ 替换外,还有一些容易遗漏的场景:
// ❌ 在非组件文件中使用(如 utils、config)
const apiUrl = process.env.REACT_APP_API_URL;
// ✅ 统一替换
const apiUrl = import.meta.env.VITE_API_URL;
// ❌ 动态拼接环境变量名(Vite 不支持)
const key = 'API_URL';
const value = process.env[`REACT_APP_${key}`]; // Webpack 可以
// ✅ Vite 中需要显式访问
const value = import.meta.env.VITE_API_URL; // 必须直接写完整名称
VITE_ 前缀的环境变量会被编译到客户端代码中,切勿放入密钥、密码等敏感信息。服务端专用的变量不要加 VITE_ 前缀。
坑点四:SVG 处理
Webpack 项目常用 @svgr/webpack 将 SVG 作为 React 组件导入,Vite 中需要使用 vite-plugin-svgr:
import svgr from 'vite-plugin-svgr';
export default defineConfig({
plugins: [
react(),
svgr({
svgrOptions: {
// SVGR 配置选项
icon: true,
dimensions: false,
},
}),
],
});
// 作为 React 组件导入
import Logo from './logo.svg?react';
// 作为 URL 导入(默认行为)
import logoUrl from './logo.svg';
function App(): JSX.Element {
return (
<div>
<Logo className="icon" />
<img src={logoUrl} alt="Logo" />
</div>
);
}
坑点五:全局 SCSS 变量注入
Webpack 中通常使用 sass-loader 的 additionalData 注入全局变量,Vite 的配置方式类似但位置不同:
export default defineConfig({
css: {
preprocessorOptions: {
scss: {
// 注入全局 SCSS 变量/Mixin(每个 .scss 文件编译前自动注入)
additionalData: `
@import "@/styles/variables.scss";
@import "@/styles/mixins.scss";
`,
},
},
},
});
被注入的文件中不要有实际的 CSS 输出(只放变量和 mixin),否则每个文件都会重复输出这些样式,导致产物体积膨胀。
坑点六:动态 import 路径
Vite 对动态 import() 的路径有更严格的要求:
// ❌ 完全动态的路径(Vite 无法分析)
const module = await import(someVariable);
// ❌ 模板字符串中包含变量目录
const module = await import(`./pages/${dir}/${file}.tsx`);
// ✅ 使用 import.meta.glob 替代
const pages = import.meta.glob('./pages/**/*.tsx');
const module = await pages[`./pages/${dir}/${file}.tsx`]();
// ✅ 动态路径只允许一层变量
const module = await import(`./pages/${name}.tsx`);
渐进式迁移策略
对于大型项目,推荐分阶段迁移,降低风险:
第一阶段:开发环境迁移
目标:让团队在开发时使用 Vite,生产构建仍用 Webpack,两套配置并存。
{
"scripts": {
"dev": "vite", // 开发使用 Vite
"dev:webpack": "webpack serve", // 保留 Webpack 作为备用
"build": "webpack --mode production", // 生产仍用 Webpack
"preview": "vite preview"
}
}
在这个阶段,webpack.config.ts 和 vite.config.ts 同时存在。团队日常开发用 pnpm dev(Vite),遇到问题可以随时切回 pnpm dev:webpack。
第二阶段:验证与优化
- 全团队使用 Vite 开发 1-2 周,收集兼容性问题
- 修复所有 CJS 兼容性、环境变量、路径别名等问题
- 比较 Vite 和 Webpack 的构建产物,确保功能一致
第三阶段:生产环境迁移
确认开发环境稳定后,将生产构建也迁移到 Vite:
{
"scripts": {
"dev": "vite",
"build": "tsc && vite build", // 生产也使用 Vite
"build:webpack": "webpack --mode production", // 保留备用
"preview": "vite preview",
"lint": "eslint src --ext .ts,.tsx"
}
}
第四阶段:清理与优化
- 移除 Webpack 相关配置文件和依赖
- 清理不再需要的 Loader/Plugin
- 优化 Vite 配置(chunk 拆分、预构建优化)
- npm
- Yarn
- pnpm
- Bun
npm uninstall webpack webpack-cli webpack-dev-server html-webpack-plugin css-loader style-loader sass-loader ts-loader babel-loader
yarn remove webpack webpack-cli webpack-dev-server html-webpack-plugin css-loader style-loader sass-loader ts-loader babel-loader
pnpm remove webpack webpack-cli webpack-dev-server html-webpack-plugin css-loader style-loader sass-loader ts-loader babel-loader
bun remove webpack webpack-cli webpack-dev-server html-webpack-plugin css-loader style-loader sass-loader ts-loader babel-loader
迁移效果对比
以一个中型 React 项目(约 800 个模块、50+ 路由页面)的真实迁移数据为参考:
| 指标 | Webpack 5 | Vite 5 | 提升 |
|---|---|---|---|
| 冷启动时间 | 38s | 1.2s | 快 31 倍 |
| HMR 更新 | 2.8s | 50ms | 快 56 倍 |
| 生产构建 | 65s | 42s | 快 35% |
| 构建产物体积 | 2.1 MB | 1.9 MB | 小 10% |
| dev 依赖数量 | 47 个 | 12 个 | 减少 74% |
| 配置文件行数 | 320 行 | 65 行 | 减少 80% |
以上数据基于典型中型项目,实际效果因项目规模、依赖数量和配置复杂度而异。冷启动和 HMR 提升通常最为显著,生产构建的提升相对温和。
常见面试问题
Q1: 如何将一个 Webpack 项目迁移到 Vite?有哪些关键步骤?
答案:
迁移的核心思路是 从 Bundle-based 开发模式切换到 Native ESM 开发模式,关键步骤分为八步:
- 安装依赖:安装
vite和对应框架插件(如@vitejs/plugin-react),按需安装 CSS 预处理器和功能插件 - 创建 vite.config.ts:将 Webpack 配置项逐一映射到 Vite 配置
- 迁移 index.html:从
public/移到根目录,移除 Webpack 模板语法,添加<script type="module">入口 - 环境变量:
process.env→import.meta.env,REACT_APP_→VITE_前缀 - 路径别名:
resolve.alias配置迁移,同步更新tsconfig.json的paths - CSS / 资源处理:移除多余 Loader 配置,Vite 大部分开箱即用
- 代理配置:
devServer.proxy→server.proxy,pathRewrite改为rewrite函数 - 全局变量:
DefinePlugin→define选项
推荐采用渐进式迁移策略:先开发环境用 Vite、生产环境保留 Webpack,验证稳定后再全量迁移。
import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';
import path from 'path';
export default defineConfig({
plugins: [react()],
resolve: {
alias: { '@': path.resolve(__dirname, 'src') },
},
server: {
port: 3000,
proxy: { '/api': { target: 'http://localhost:8080', changeOrigin: true } },
},
define: { __APP_VERSION__: JSON.stringify('1.0.0') },
});
Q2: 迁移过程中最常见的兼容性问题有哪些?如何解决?
答案:
最常见的六大兼容性问题及解决方案:
| 问题 | 原因 | 解决方案 |
|---|---|---|
require is not defined | CJS 依赖未转换 | optimizeDeps.include 强制预构建 |
require.context 不可用 | Webpack 专有 API | 替换为 import.meta.glob |
| 环境变量读取失败 | 前缀和访问方式变了 | REACT_APP_ → VITE_,process.env → import.meta.env |
| SVG 组件导入报错 | 缺少 SVGR 插件 | 安装 vite-plugin-svgr,使用 ?react 后缀 |
| 全局 SCSS 变量丢失 | 注入方式不同 | 配置 css.preprocessorOptions.scss.additionalData |
| 动态 import 路径报错 | Vite 对动态路径限制更严 | 使用 import.meta.glob 替代完全动态路径 |
其中最隐蔽的是 CJS 兼容性问题,因为很多常用的 npm 包仍然使用 CJS 格式(如 lodash、moment)。排查方法是观察浏览器控制台错误信息,将报错的包加入 optimizeDeps.include:
export default defineConfig({
optimizeDeps: {
include: [
'lodash',
'moment',
'classnames',
'some-lib > nested-cjs-dep', // 嵌套的 CJS 依赖也要显式声明
],
},
});
Q3: 迁移后的效果如何量化评估?
答案:
从开发体验和构建产物两个维度来量化:
开发体验指标(改善最明显):
interface MigrationMetrics {
// 1. 冷启动时间:从 npm run dev 到页面可交互的时间
coldStart: { webpack: '30-60s'; vite: '< 2s' };
// 2. HMR 时间:修改代码到页面更新的时间
hmrLatency: { webpack: '2-5s'; vite: '< 100ms' };
// 3. 依赖安装时间:devDependencies 数量和安装耗时
devDepsCount: { webpack: '40-60'; vite: '10-20' };
}
构建产物指标(改善温和):
| 指标 | 测量方法 | 预期改善 |
|---|---|---|
| 构建时间 | CI 流水线耗时 | 20-40% 提升 |
| 产物体积 | build 后 dist/ 总大小 | 5-15% 减少 |
| 首屏加载 | Lighthouse Performance | 基本持平 |
| chunk 数量 | 构建日志 | 视 manualChunks 配置而定 |
评估方法:
- 迁移前记录基准数据(冷启动、HMR、构建时间、产物体积)
- 迁移后在相同条件下重新测量
- 关注 CI/CD 流水线耗时变化
- 团队主观体验(DX 满意度调查)
迁移是否值得,主要看开发体验提升。生产构建的改善是锦上添花,但冷启动从 30s 降到 1s、HMR 从 3s 降到 50ms——这种量级的提升对开发效率和团队幸福感的影响是巨大的。