Skip to content

前端构建工具演进:Webpack 5、Vite 与 Rspack ​

2020 年前后,前端工程化遇到了一个很具体的天花板:业务代码越来越多,webpack 的冷启动从十几秒涨到两三分钟,改一行代码要等几秒才能看到 HMR 结果。构建工具不再只是"把代码跑起来"的辅助设施,而开始直接决定团队的开发效率。

这篇文章不比较参数表,而是从构建工具真正解决的问题出发,梳理 Webpack 5、Vite、Rspack 三代方案的设计差异,以及迁移时最容易踩的坑。

构建工具到底在做什么 ​

不论工具怎么变,一次完整构建都要处理这几件事:

  1. 模块解析:把 import、require 解析成真实文件,处理路径别名与包入口。
  2. 代码转换:TypeScript、JSX、CSS 预处理器、语法降级。
  3. 依赖图构建与打包:决定哪些模块进入同一个 chunk,如何拆分与缓存。
  4. 产物优化:压缩、Tree Shaking、Scope Hoisting、Source Map。
  5. 开发服务: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 5ViteRspack
开发冷启动中到慢快快
HMR 速度中快快
生产构建成熟稳定Rollup,生态完善Rust 加速,兼容 Webpack
配置迁移成本基准高低到中
生态兼容性最强中高
适合场景存量复杂项目新项目、框架生态大型存量项目提速

我的实践建议:

  • 新项目优先 Vite,尤其是 Vue、React 的常规 Web 应用。
  • 存量 Webpack 项目先评估 Rspack,通常能获得明显的构建提速,同时保留配置资产。
  • 库与组件包更关注产物质量与多格式输出,Rollup 或 Vite 的库模式更合适。
  • 不要为了快而一次性重写构建链路。先做迁移验证,在 CI 中并行跑新旧构建,确认产物体积和运行行为一致后再切换。

常见坑 ​

  1. CJS/ESM 混用。default 导出在不同工具链下行为不一致,库作者应同时提供 exports 字段,业务侧避免依赖具体工具的互操作实现。
  2. 动态导入变量。import(./pages/${name}.vue) 需要工具能静态分析出候选集,路径拼接过于灵活会导致整个目录被打包或直接失败。
  3. 浏览器目标与 polyfill。Vite 默认面向现代浏览器,老项目要显式设置 build.target 并引入 polyfill。
  4. 缓存失效。contenthash 要覆盖所有影响产物的因素,动态导入的 chunk 名不要与内容无关。
  5. 只看构建速度,不看产物体积。接入 rollup-plugin-visualizer 或 webpack-bundle-analyzer,定期检查依赖体积。

2025 年补充:Vite 正在用 Rolldown 统一开发与生产构建链路,Rspack 也已成为大型项目的主流选择之一。工具仍在快速演进,但"开发期按需、生产期优化"的基本判断没有变。

小结 ​

构建工具的演进不是简单的"新工具替换旧工具",而是围绕开发反馈速度和产物质量不断重新分配计算时机。理解这一点,比记住某个配置项更重要。

基于 VitePress 构建 · 持续更新中