我不知道的打包工具(05)— 构建工具选型:如何根据项目类型选择打包方案
npm init 之后的第一个问题往往不是"写什么业务逻辑",而是"用什么打包工具"。Webpack、Vite、Rollup、esbuild、tsup、Father、unbuild——名单太长了。网上的"工具对比"文章通常罗列一堆功能矩阵,但不回答那个最实际的问题:你的项目到底…
npm init 之后的第一个问题往往不是”写什么业务逻辑”,而是”用什么打包工具”。Webpack、Vite、Rollup、esbuild、tsup、Father、unbuild——名单太长了。网上的”工具对比”文章通常罗列一堆功能矩阵,但不回答那个最实际的问题:你的项目到底该选哪个?
问题的关键在于——选型的第一步不是比较工具,而是识别项目类型。不同类型的项目对构建工具的核心诉求完全不同,搞清楚自己在做什么,答案自然浮现。
一、四种项目类型,四条选型路线
前端项目大致可以分为四类,每类的构建需求差异巨大。
(1)SPA / MPA 应用。 核心需求是代码分割、HMR 热更新、dynamic import 懒加载、HTML 模板注入。这类项目的构建产物只有一个消费者——浏览器。新项目用 Vite,存量项目留在 Webpack,这是目前最务实的选择。
(2)TypeScript 工具库。 比如一个日期处理库、一个状态管理库。核心需求是 ESM + CJS 双格式输出、自动生成 .d.ts 类型声明、零配置上手。tsup 和 unbuild 都能在一个配置文件内搞定这些事。
(3)UI 组件库。 比如一个 React/Vue 组件库。核心需求比工具库多两层:Bundless 逐文件编译(保留目录结构,让消费端按组件粒度 Tree Shaking)、样式隔离(CSS Modules 或 CSS-in-JS)、按需加载。Father 就是为这个场景设计的。
(4)Monorepo 工具包。 一个仓库里有 10 个 package,互相引用。核心需求是 workspace 感知、依赖拓扑排序、增量构建。unbuild 的 stub 模式(开发时直接 jiti 加载源码,不需要构建)配合 turborepo 的任务编排,是目前 monorepo 场景下的高效方案。
说白了,项目类型决定了你需要什么能力,然后才是哪个工具提供了这些能力。 反过来先看工具功能列表再套项目,容易选错。
二、Vite vs Webpack——不是谁更好,是场景不同
很多人以为 Vite 出来之后 Webpack 就该淘汰了,但实际上两者的适用场景有明确分界。
Vite 的优势集中在开发体验:ESM 原生加载让开发服务器启动速度几乎与项目规模无关,esbuild 预构建处理第三方依赖,HMR 更新粒度到模块级别。对于新启动的应用项目,Vite 是默认选择。
Webpack 的优势在存量生态:Module Federation(微前端方案)、成熟的 Loader 链(处理各种非标格式)、以及企业项目中大量已有的构建流水线。把一个跑了三年的 Webpack 项目迁移到 Vite,收益不一定覆盖迁移成本。
下面两段配置展示了同一个 React 应用在两种工具下的典型写法:
// vite.config.ts
import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';
export default defineConfig({
plugins: [react()],
build: {
rollupOptions: {
output: { manualChunks: { vendor: ['react', 'react-dom'] } },
},
},
});
Vite 的配置围绕插件和 Rollup 输出选项展开,默认行为覆盖了大部分场景,显式配置的部分很少。
// webpack.config.js
const HtmlPlugin = require('html-webpack-plugin');
module.exports = {
entry: './src/index.tsx',
module: {
rules: [
{ test: /\.tsx?$/, use: 'ts-loader' },
{ test: /\.css$/, use: ['style-loader', 'css-loader'] },
],
},
optimization: {
splitChunks: { chunks: 'all' },
},
plugins: [new HtmlPlugin({ template: './index.html' })],
};
Webpack 需要显式声明每种文件类型的处理方式。配置更冗长,但每一步都是可控的——遇到非标需求时,这种显式声明反而更容易调试。
还有一个时效性信息值得关注:Vite 8 Beta(2025 年 12 月发布)已将 Rolldown 设为默认打包引擎,开发与生产共用同一引擎,之前”开发正常、生产报错”的双引擎一致性问题正在被根本性地解决。
三、tsup vs Rollup vs unbuild——库打包三件套
如果项目是一个 npm 包(不是应用),选择范围缩窄到这三个。
tsup 追求极致简单。底层用 esbuild 编译,配合 Rollup 的 dts 插件生成类型声明。一个 tsup.config.ts 就能输出 ESM + CJS 双格式加类型文件:
// tsup.config.ts
import { defineConfig } from 'tsup';
export default defineConfig({
entry: ['src/index.ts'],
format: ['esm', 'cjs'],
dts: true,
splitting: true,
clean: true,
});
六行配置,npx tsup 就能跑。对于大多数 TypeScript 工具库,tsup 是最快出活的方案。
Rollup 的 Tree Shaking 精确度是三者中最高的,因为它的静态分析直接操作 ESM 的 AST。对于极度在意产物体积的库(比如给移动端用的 SDK),Rollup 配合手动调优的插件链能压出最小的包。代价是配置复杂度高——TypeScript 支持、CommonJS 桥接、类型声明生成都需要手动接插件。
unbuild 由 unjs 团队维护,最大的特色是 stub 模式:开发阶段不构建,而是通过 jiti(一个 TypeScript 即时加载器)直接从源码引用。改了代码不需要重新 build,monorepo 里多个包互相引用时体验极好。正式构建时底层使用 Rollup。
这里有一个很多人会忽略的细节——tsup 的 Tree Shaking 能力弱于 Rollup。esbuild 的 Tree Shaking 只做基础的导出移除,对副作用标记(sideEffects)的处理不如 Rollup 精细。如果库的体积是核心指标,需要认真评估这个差异。
四、选型总览表
| 维度 | Vite | Webpack | tsup | Rollup | unbuild | Father |
|---|---|---|---|---|---|---|
| 构建速度 | 快 | 慢(大项目) | 极快 | 中等 | 快 | 中等 |
| Tree Shaking | Rollup 级 | 需配置 | 基础 | 最精确 | Rollup 级 | 依赖引擎 |
| 多格式输出 | 非核心 | 需多配置 | ESM+CJS | 灵活 | ESM+CJS | ESM+CJS+UMD |
| 类型声明 | 非内置 | 非内置 | 内置 | 需插件 | 内置 | 内置 |
| 插件生态 | Rollup 兼容 | 最丰富 | 有限 | 丰富 | 有限 | Umi 生态 |
| 配置复杂度 | 低 | 高 | 极低 | 中高 | 低 | 极低 |
| 适合项目 | 应用 | 存量应用 | TS 工具库 | 极致体积库 | Monorepo 包 | UI 组件库 |
五、具体建议
把上面的分析压缩成可操作的决策路径:
新建一个 SPA 应用——用 Vite。框架脚手架(create-vite、create-next-app)已经帮你选好了。
维护一个存量 Webpack 应用——留在 Webpack。升级到 Webpack 5 + SWC Loader 就能获得可观的提速,迁移到 Vite 的 ROI 需要评估。
发布一个 TypeScript npm 包——用 tsup。零配置,esbuild 驱动,ESM + CJS + .d.ts 一步到位。
发布一个 React/Vue 组件库——用 Father。Bundless + Bundle 双模式、样式隔离、按需加载,这些组件库的刚需在 Father 里是开箱即用的。
管理一个 Monorepo 里的多个包——用 unbuild + turborepo。stub 模式省去开发时的重复构建,turborepo 负责任务编排和缓存。
需要极致产物体积——用 Rollup 手动调优。Tree Shaking 精确度最高,但配置成本也最高,适合对体积有硬指标的场景(移动端 SDK、嵌入式脚本)。
如果你只记住一句话:没有”最好”的构建工具,只有”最匹配项目类型”的工具。选型的本质是先搞清楚自己在做什么类型的项目。
本系列其他文章:
相关主题:
- 打包后的代码在浏览器中如何执行:渲染流水线:从 HTML 到屏幕的八个步骤
- 打包工具的原理基础:打包工具全景 — 从 Webpack 到 Rolldown 的设计哲学