外观
前端构建工具演进:Webpack 5、Vite 与 Rspack
2020 年前后,前端工程化遇到了一个很具体的天花板:业务代码越来越多,webpack 的冷启动从十几秒涨到两三分钟,改一行代码要等几秒才能看到 HMR 结果。构建工具不再只是"把代码跑起来"的辅助设施,而开始直接决定团队的开发效率。
这篇文章不比较参数表,而是从构建工具真正解决的问题出发,梳理 Webpack 5、Vite、Rspack 三代方案的设计差异,以及迁移时最容易踩的坑。
构建工具到底在做什么
不论工具怎么变,一次完整构建都要处理这几件事:
- 模块解析:把
import、require解析成真实文件,处理路径别名与包入口。 - 代码转换:TypeScript、JSX、CSS 预处理器、语法降级。
- 依赖图构建与打包:决定哪些模块进入同一个 chunk,如何拆分与缓存。
- 产物优化:压缩、Tree Shaking、Scope Hoisting、Source Map。
- 开发服务:HMR、代理、按需编译。
不同工具的核心差异,本质上是在"什么时候做哪些事"上做了不同选择。
Webpack 5:成熟的工程化基座
Webpack 5 在 2020 年发布,是这个阶段大型项目的默认选择。它最关键的几个能力:
- 持久化缓存:
cache: { type: 'filesystem' }把模块编译结果写入磁盘,二次冷启动显著加速。 - Module Federation:让多个独立构建的应用在运行时共享模块,直接催生了新一波微前端实践。
- Asset Modules:内置图片、字体等资源处理,减少
url-loader、file-loader依赖。 - 更好的 Tree Shaking:ESM 静态分析 +
sideEffects标记,配合usedExports去掉未使用代码。
一个典型的生产配置:
js
// webpack.config.js
const path = require('node:path')
module.exports = {
mode: 'production',
entry: './src/main.ts',
output: {
path: path.resolve(__dirname, 'dist'),
filename: '[name].[contenthash:8].js',
clean: true
},
cache: { type: 'filesystem' },
resolve: {
extensions: ['.ts', '.tsx', '.js'],
alias: { '@': path.resolve(__dirname, 'src') }
},
module: {
rules: [
{ test: /\.[jt]sx?$/, exclude: /node_modules/, use: 'swc-loader' },
{ test: /\.css$/, use: ['style-loader', 'css-loader'] }
]
},
optimization: {
splitChunks: { chunks: 'all' },
runtimeChunk: 'single'
}
}Webpack 的问题是开发阶段仍要打包。依赖图越大,冷启动和 HMR 越慢。持久化缓存能缓解,但不能消除根本成本。
Vite:开发时不打包
Vite 的核心洞察是:浏览器已经原生支持 ESM,开发环境完全可以让浏览器按需请求模块,服务器只做即时转换,不需要先打出一个完整的 bundle。
它把开发和生产拆成了两套策略:
- 开发态:原生 ESM +
esbuild预构建第三方依赖,冷启动通常在秒级。 - 生产态:仍然用 Rollup 打包,保证 chunk 拆分、压缩与兼容性。
- HMR:基于 ESM 的模块边界精确更新,不再让整条依赖链重新编译。
ts
// vite.config.ts
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
export default defineConfig({
plugins: [vue()],
resolve: {
alias: { '@': '/src' }
},
server: {
warmup: { clientFiles: ['./src/main.ts'] }
},
optimizeDeps: {
include: ['lodash-es', 'echarts']
},
build: {
target: 'es2020',
sourcemap: true
}
})optimizeDeps.include 值得特别说明。Vite 会把 CommonJS 依赖预构建成 ESM,但依赖扫描基于静态分析,遇到动态导入或某些 monorepo 链接包时可能漏掉,表现为页面刷新后突然多出一次重新预构建。显式声明可以稳定启动速度。
Vite 的代价是开发态与生产态的构建链路不同。大部分业务无感,但涉及 CJS/ESM 互操作、依赖副作用、特殊资源处理时,可能出现"开发正常、构建失败"或反过来的情况。关键项目要在 CI 中保留生产构建和预览环境的冒烟测试。
Rspack:用 Rust 重写 Webpack 兼容层
Rspack 选择了另一条路线:保留 Webpack 的配置模型和插件生态,用 Rust 重写核心编译流程。对已有 Webpack 项目来说,这通常意味着最低的心智迁移成本。
js
// rspack.config.js
const { defineConfig } = require('@rspack/cli')
const { rspack } = require('@rspack/core')
module.exports = defineConfig({
entry: { main: './src/main.ts' },
module: {
rules: [
{
test: /\.[jt]sx?$/,
use: 'builtin:swc-loader',
exclude: /node_modules/
}
]
},
optimization: { minimize: true },
plugins: [
new rspack.HtmlRspackPlugin({ template: './index.html' })
]
})它适合两类团队:
- 历史项目多、Webpack 配置复杂,短期内无法重写工程链路。
- 对构建性能有强诉求,但不想承担 Vite 生态迁移的适配成本。
需要确认的是插件兼容度。Rspack 覆盖了主流 loader 和 plugin,但一些依赖 Webpack 内部钩子的老插件仍可能不兼容。迁移时先跑通构建,再逐个验证自定义插件。
如何做选择
| 维度 | Webpack 5 | Vite | Rspack |
|---|---|---|---|
| 开发冷启动 | 中到慢 | 快 | 快 |
| HMR 速度 | 中 | 快 | 快 |
| 生产构建 | 成熟稳定 | Rollup,生态完善 | Rust 加速,兼容 Webpack |
| 配置迁移成本 | 基准 | 高 | 低到中 |
| 生态兼容性 | 最强 | 中 | 高 |
| 适合场景 | 存量复杂项目 | 新项目、框架生态 | 大型存量项目提速 |
我的实践建议:
- 新项目优先 Vite,尤其是 Vue、React 的常规 Web 应用。
- 存量 Webpack 项目先评估 Rspack,通常能获得明显的构建提速,同时保留配置资产。
- 库与组件包更关注产物质量与多格式输出,Rollup 或 Vite 的库模式更合适。
- 不要为了快而一次性重写构建链路。先做迁移验证,在 CI 中并行跑新旧构建,确认产物体积和运行行为一致后再切换。
常见坑
- CJS/ESM 混用。
default导出在不同工具链下行为不一致,库作者应同时提供exports字段,业务侧避免依赖具体工具的互操作实现。 - 动态导入变量。
import(./pages/${name}.vue)需要工具能静态分析出候选集,路径拼接过于灵活会导致整个目录被打包或直接失败。 - 浏览器目标与 polyfill。Vite 默认面向现代浏览器,老项目要显式设置
build.target并引入 polyfill。 - 缓存失效。
contenthash要覆盖所有影响产物的因素,动态导入的 chunk 名不要与内容无关。 - 只看构建速度,不看产物体积。接入
rollup-plugin-visualizer或webpack-bundle-analyzer,定期检查依赖体积。
2025 年补充:Vite 正在用 Rolldown 统一开发与生产构建链路,Rspack 也已成为大型项目的主流选择之一。工具仍在快速演进,但"开发期按需、生产期优化"的基本判断没有变。
小结
构建工具的演进不是简单的"新工具替换旧工具",而是围绕开发反馈速度和产物质量不断重新分配计算时机。理解这一点,比记住某个配置项更重要。