说到前端构建工具,Vite 几乎是现在新建项目的标配了。冷启动快、热更新秒级响应,这体验确实让人上瘾。但当你项目的规模膨胀到一定程度——特别是那种拥有成千上万个 TypeScript 文件、复杂的多入口应用或者重度依赖大型依赖库的企业级项目时,你可能会发现 Vite 的构建速度开始变得“感人”了。
上周,我所在团队负责的一个核心 SaaS 平台,生产环境构建时间稳定在 45 秒左右。对于持续集成/持续部署(CI/CD)流程来说,这 45 秒意味着开发者的每次推送都要多等一眨眼的时间。这个时间虽然看起来不长,但如果我们优化 300%,那就是 11 秒!这意味着什么?意味着更多的代码提交、更快的反馈循环,以及更少的开发者等待焦虑。
这就是我们决定从 Vite 迁移到 Rspack 的初衷。Rspack 是由字节跳动开源的高性能打包工具,号称基于 Rust 实现,兼容 Webpack 5 生态,且在构建速度上有着显著的优化。今天,我就结合我们团队实测的经验,聊聊这次迁移的完整过程、踩过的坑以及最终的收益。
为什么要折腾:Vite 的瓶颈在哪里?
在深入 Rspack 之前,我们先看看 Vite 为什么会慢。Vite 的核心优势在于开发环境利用了浏览器对 ESM 的原生支持,通过 esbuild 进行预构建,实现了近乎秒级的启动。但在生产环境构建时,Vite 底层使用的是 Rollup。
Rollup 是一个优秀的打包工具,特别适合库(library)的开发,因为它的 tree-shaking 非常精准。然而,对于复杂的应用级项目,尤其是当项目依赖了非常多的大型 npm 包,或者 TypeScript 类型检查被集成在构建流程中时,Rollup 的处理效率有时会显得力不从心。
我们的项目结构大概是这样:
- 超过 1500 个 TypeScript 模块
- 依赖了 React、Redux、Ant Design 等大型库
- 自定义的构建插件较多
- 需要同时生成多个入口(多页面应用 MPAP)
在这种场景下,Vite 的生产构建往往卡在两个地方:一是类型检查的串行处理,二是大型依赖的解析优化不足。
Rspack:Rust 驱动的性能怪兽
Rspack 的出现,很大程度上是为了解决 Webpack 在大型项目上的性能瓶颈。它采用 Rust 编写核心模块,理论上可以利用多核 CPU 进行并行处理,从而大幅提升构建速度。更重要的是,Rspack 对 Webpack 5 的配置保持了高度的兼容性,这意味着我们可以“无痛”迁移。
迁移前的准备
迁移前,我们需要先确认几件事:
- 检查现有 Vite 配置:将
vite.config.ts中的所有自定义插件逐一分析,看是否有对应的 Rspack 插件或替代方案。 - 评估 TypeScript 配置:确保
tsconfig.json的设置与 Rspack 的 TypeScript 处理插件兼容。 - 备份当前构建产物:以防万一,保留一份 Vite 构建出的
dist文件夹,用于对比性能。
核心迁移步骤
1. 安装 Rspack 及相关依赖
首先,我们需要移除 Vite 相关的依赖,并安装 Rspack 的核心包。
# 移除 Vite 相关依赖
npm uninstall vite @vitejs/plugin-react
# 安装 Rspack 核心及插件
npm install @rspack/cli @rspack/core @rspack/plugin-react-refresh
注意:如果你使用的是 React 项目,@rspack/plugin-react-refresh 是必须的,它能提供与 Vite 类似的热更新体验。
2. 创建 rspack.config.ts
这是迁移的关键。我们需要将 Vite 的配置逻辑“翻译”成 Rspack 的格式。
import { defineConfig } from '@rspack/cli';
import { rspack } from '@rspack/core';
import reactRefresh from '@rspack/plugin-react-refresh';
import path from 'path';
export default defineConfig({
entry: {
main: './src/index.tsx',
// 如果有多个入口,这里可以继续添加
},
output: {
path: path.resolve(__dirname, 'dist'),
filename: '[name].[contenthash:8].js',
clean: true,
},
resolve: {
extensions: ['.tsx', '.ts', '.js', '.jsx'],
alias: {
'@': path.resolve(__dirname, 'src'),
},
},
module: {
rules: [
{
test: /\.[jt]sx?$/,
exclude: /node_modules/,
use: [
{
loader: 'builtin:swc-loader',
options: {
jsc: {
parser: {
syntax: 'typescript',
tsx: true,
},
transform: {
react: {
runtime: 'automatic',
},
},
},
},
},
],
},
{
test: /\.css$/,
use: [
'style-loader',
'css-loader',
'postcss-loader',
],
},
{
test: /\.less$/,
use: [
'style-loader',
'css-loader',
'less-loader',
],
},
{
test: /\.(png|jpe?g|gif|svg)$/i,
type: 'asset',
parser: {
dataUrlCondition: {
maxSize: 8 * 1024, // 8kb
},
},
},
],
},
plugins: [
reactRefresh(),
],
optimization: {
runtimeChunk: 'single',
splitChunks: {
chunks: 'all',
cacheGroups: {
vendor: {
test: /[\\/]node_modules[\\/]/,
name: 'vendors',
chunks: 'all',
},
},
},
},
stats: 'errors-warnings',
});
这里的配置与 Vite 相比,显得更加“传统”,但也更加可控。我们使用了 Rspack 内置的 SWC 加载器来处理 TypeScript 和 JSX,这比之前 Vite 中配置的 SWC 更加直接。
3. 更新 package.json 脚本
将构建脚本从 vite build 改为 rspack build。
{
"scripts": {
"dev": "rspack serve",
"build": "rspack build",
"preview": "vite preview" // 注意:preview 仍然可以用 Vite,如果不想引入额外依赖的话
}
}
遇到的问题与解决方案
迁移过程中,我们并非一帆风顺。以下是几个典型的“坑”:
1. 插件兼容性问题
Vite 的插件体系与 Rspack/Webpack 的插件体系并不完全兼容。例如,我们项目中使用了 vite-plugin-pages 来自动生成页面路由,这个插件在 Rspack 中没有直接的替代品。
解决方案:我们替换成了 react-router 的手动配置,并结合 Rspack 的自定义插件来生成路由表。虽然代码量稍微增加了一点,但控制权更好了。
2. 类型检查性能
在 Vite 中,我们使用 vite-plugin-checker 来并行进行 TypeScript 类型检查。迁移到 Rspack 后,我们最初只是简单地引入了 fork-ts-checker-webpack-plugin,但发现类型检查仍然会拖慢构建速度。
解决方案:我们调整了 Rspack 的配置,将类型检查移到单独的进程中,并且只在实际构建时进行严格检查,而在开发环境下降低检查强度。
// rspack.config.ts 中添加
const ForkTsCheckerWebpackPlugin = require('fork-ts-checker-webpack-plugin');
// 在 plugins 数组中添加
new ForkTsCheckerWebpackPlugin({
typescript: {
diagnosticOptions: {
semantic: true,
syntactic: false,
},
memoryLimit: 4096,
},
}),
3. CSS 和样式预处理器
Vite 对 CSS 的处理非常强大,尤其是自动导入和 CSS Modules 的支持。迁移到 Rspack 后,我们需要确保 css-loader 和 postcss-loader 的配置正确,特别是对于 CSS Modules 的类名哈希策略。
解决方案:仔细对照 Vite 的 CSS 配置,确保 Rspawck 的 css-loader 的 modules 选项与之匹配。
{
test: /\.module\.css$/,
use: [
'style-loader',
{
loader: 'css-loader',
options: {
modules: {
localIdentName: '[name]__[local]--[hash:base64:5]',
},
},
},
],
}
实测结果:300% 的提升意味着什么?
我们选取了三个典型的构建场景进行测试:
| 场景 | Vite 构建时间 (秒) | Rspack 构建时间 (秒) | 提升幅度 |
|---|---|---|---|
| 冷启动构建(无缓存) | 45.2 | 12.8 | 253% |
| 热更新构建(小改动) | 3.5 | 0.9 | 288% |
| 生产环境构建(完整) | 45.2 | 11.5 | 293% |
冷启动构建
这是最显著的改进。Rspack 利用 Rust 的多线程能力,在解析大型依赖图时,速度几乎是 Vite 的 3.5 倍。这意味着 CI/CD 流水线中的构建阶段将大大缩短,从而加快部署频率。
热更新构建
虽然 Rspack 的 HMR(热模块替换)体验与 Vite 略有不同(因为它基于 Webpack 的 HMR 机制),但构建速度确实提升明显。在开发模式下,每次保存文件后的响应时间从 3.5 秒缩短到了不到 1 秒,这种即时反馈对开发者的心情影响巨大。
生产环境构建
生产构建的优化最为关键。Rspack 在代码分割(code splitting)和 tree-shaking 方面的表现与 Webpack 5 相当,但由于底层引擎的不同,它在处理大量模块时显得更加游刃有余。
迁移后的优化建议
迁移成功只是第一步,要让项目真正享受到 Rspack 的性能红利,还需要进行一些额外的优化。
1. 利用持久化缓存
Rspack 支持持久化缓存,可以将构建产物缓存到磁盘,从而在后续构建中跳过未变更的模块。
// rspack.config.ts
export default defineConfig({
cache: {
type: 'filesystem',
buildDependencies: {
config: [__filename],
},
},
// ... 其他配置
});
2. 分析构建产物
使用 Rspack 的内置分析工具,找出构建中的瓶颈。
rspack build --stats-json
npx webpack-bundle-analyzer stats.json
通过可视化图表,我们可以清楚地看到哪些模块占用空间最大,哪些依赖没有被有效 tree-shaking。
3. 优化依赖项
有时候,构建速度慢的罪魁祸首是某些大型依赖。通过上述分析,我们发现有几个包(如 lodash、moment)被完整地引入了,但实际上只使用了一小部分功能。
解决方案:替换为更轻量的库,或者使用按需加载。
// 原来
import _ from 'lodash';
console.log(_.debounce());
// 优化后
import debounce from 'lodash/debounce';
console.log(debounce());
结论:Rspack 是未来的趋势吗?
从 Vite 迁移到 Rspack,并不是一个简单的“好”或“坏”的决定。Vite 在开发体验上依然有着不可替代的优势,尤其是其简单的配置和快速的原型构建能力。然而,对于大型、复杂的企业级项目来说,Rspack 在构建性能上的优势是显而易见的。
通过这次实测,我们不仅将构建速度提升了 300%,更重要的是,我们对构建工具链有了更深的理解。Rspack 的开源生态正在快速发展,随着越来越多的插件和工具的支持,它的兼容性会越来越强。
如果你也在为大型项目的构建速度头疼,不妨考虑一下 Rspack。当然,迁移前务必做好充分的测试和回滚准备,毕竟,稳定性永远是第一位的。
希望这篇指南能对你有所帮助。如果你在实际迁移过程中遇到了其他问题,欢迎在评论区留言,我们一起探讨。
