构建工具踩坑记录
构建工具踩坑记录:那些年我跟 Webpack、Vite 和 Rollup 的“相爱相杀”

大家好,我是阿强,一个经历过前端构建从刀耕火种到现代工程化飞跃的码农。
今天我想聊的是我们在使用构建工具过程中踩过的那些坑,以及从中总结出的一些经验和教训。这些经历都来自于我实际参与的多个中大型项目的构建优化工作,包括企业级后台系统、SSR 应用以及多端统一打包方案等。
希望通过这篇文章,能帮你少走点弯路,也欢迎你在评论区分享你的构建踩坑经历!
一、开篇:为什么构建工具这么重要?
在项目初期,我们可能只需要一个简单的 npm start 就能跑起来,但随着业务发展,你会发现:
- 打包速度越来越慢
- 构建产物越来越大
- 开发服务器启动时间拉长
- 不同环境配置错综复杂
这时候,你就需要一个靠谱的构建工具了。
我在几个项目中分别尝试过 Webpack、Vite 和 Rollup,每种都有其优劣,也有各自的“坑”。接下来我会结合具体项目场景,讲讲我们都遇到了哪些问题,又是怎么解决的。
二、故事开始:一个电商后台系统的升级之旅
1. 项目背景
2021 年,我们接手了一个老电商后台系统,技术栈是 Vue.js(当时是 2.x),构建工具还是老旧的 Webpack 4。项目体量已经膨胀到超过 200 个组件,30 多个路由页面,还有不少历史包袱代码。
开发人员抱怨:
- 开发模式启动慢(平均 6s)
- 热更新延迟明显
- 打包后 JS 文件体积臃肿(接近 8MB)
老板说:“你们搞个新项目吧”,但我们很清楚,如果不把构建这块优化好,新项目迟早也会走上老路。
三、初次尝试:Webpack 5 + SplitChunks 优化
1. 技术选型思路
首先,我们决定继续使用 Webpack,理由如下:
- 团队熟悉程度高
- 插件生态丰富,适合 SSR 场景
- 支持 Code Splitting、Tree Shaking 成熟
我们的目标是:
- 缩短开发服务启动时间
- 减小生产构建体积
- 优化热更新响应速度
于是我们做了以下改动:
// webpack.prod.js
module.exports = {
optimization: {
splitChunks: {
chunks: 'all',
minSize: 20000, // 20KB
maxSize: 0,
minChunks: 1,
maxAsyncRequests: 30,
maxInitialRequests: Infinity,
automaticNameDelimiter: '~',
cacheGroups: {
vendors: {
test: /[\\/]node_modules[\\/]/,
priority: -10
},
default: {
minChunks: 2,
priority: -20,
reuseExistingChunk: true
}
}
}
}
}
同时将 Webpack 从 4 升级到 5,并启用了内置的缓存机制和持久化缓存。
2. 遇到的问题
虽然打包体积下降了不少(最终主包控制在 3MB 左右),但开发体验并没有明显改善,反而更差了:
- 第一次启动仍然要 5s+
- 修改任意文件都会触发全量 HMR
- 某些第三方库修改后不会自动刷新
排查发现,问题出在以下几个方面:
- Babel loader 版本不兼容:有些插件还没适配 Webpack 5,导致缓存失效频繁
- Vue 单文件组件 HMR 不稳定:某些情况下会丢失上下文
- Node.js 版本太低:很多现代特性无法启用
四、转向 Vite:更快的开发体验,但代价也不小
2022 年底,我们决定尝试引入 Vite 作为开发阶段的构建工具,用于新模块快速迭代,而保留 Webpack 做生产构建。
1. 技术选型对比
| 项目 | Webpack | Vite |
|---|---|---|
| 开发启动速度 | 较慢(5~7s) | 很快(<1s) |
| 热更新速度 | 快 | 极快 |
| 插件生态 | 丰富 | 正在快速增长 |
| 对 ES Module 支持 | 一般 | 原生支持 |
| 生产打包能力 | 强大 | 初期较弱,现在已不错 |
2. 具体实践:实现 DevServer 多入口共存
为了渐进式迁移,我们让 Vite 只负责新模块的开发服务,老模块继续用 Webpack。为此写了个中间层代理服务:
const express = require('express');
const { createServer } = require('vite');
async function start() {
const app = express();
const vite = await createServer({
server: { middlewareMode: true },
appType: 'custom'
});
// 新模块走 Vite
app.use('/new', vite.middlewares);
// 老模块代理到 Webpack Dev Server
app.all('*', (req, res) => {
proxyToWebpackDevServer(req, res);
});
app.listen(3000, () => {
console.log('Server is running on port 3000');
});
}
start();
这样就可以做到:
- 新功能用 Vite 快速开发
- 老功能平稳过渡
- 最终合并到 Webpack 打包流程中
不过,这个方案也不是没有问题。
3. 踩坑现场
问题 1:Vite 在 SSR 模式下对全局变量处理不稳定
例如我们在模板中直接使用了 window.__SSR_DATA__,但在 Vite SSR 服务中有时取不到值。
✅ 解决方式:统一通过 context 注入,并封装成一个可复用的 SSR 工具函数
问题 2:CSS Modules 使用冲突
Vite 默认使用 PostCSS 自动加前缀,但和我们现有的 CSS Modules 冲突,导致类名重复。
✅ 解决方式:在
postcss.config.js中禁用不必要的插件,只保留必要部分
// vite.config.js
export default defineConfig({
css: {
modules: {
localsConvention: "camelCaseOnly"
}
}
});
五、小插曲:Rollup 上线记 —— 为组件库准备的轻量打包方案
我们还有一个内部 UI 组件库,希望以 npm 包形式发布,供其他项目引用。于是我们尝试了 Rollup。
1. 为什么选择 Rollup?
- 更适合打包库,输出格式多样(ESM、UMD、CJS)
- Tree-shaking 更精准
- 配置相对简单
import vue from '@vitejs/plugin-vue';
import { defineConfig } from 'rollup';
import typescript from '@rollup/plugin-typescript';
export default defineConfig({
input: 'src/index.ts',
output: [
{
file: 'dist/index.cjs',
format: 'cjs'
},
{
file: 'dist/index.esm.js',
format: 'es'
}
],
plugins: [typescript(), vue()]
});
看起来很美好,结果上线就翻车了!
2. 常见踩坑点
问题 1:Vue 组件打包后样式缺失
原来是没显式引入样式插件,Rollup 并不会自动处理 .vue 文件中的 <style> 标签。
✅ 解决方法:添加
@rollup/plugin-postcss插件,并启用extract模式
import postcss from '@rollup/plugin-postcss';
plugins: [
postcss({
extract: true,
minimize: true
})
]
问题 2:打包后的组件引用路径错误
因为用了动态导入或异步组件,Rollup 输出的路径处理不够智能。
✅ 解决方法:统一改用静态导入,避免使用
defineAsyncComponent
六、经验总结:别盲目追求“最快的”工具
经过这几个项目的折腾,我对构建工具的选择有了更深的理解:
| 场景 | 推荐工具 | 原因 |
|---|---|---|
| 开发体验优先 | Vite | 快速启动、HMR 体验极佳 |
| 生产构建 | Webpack / Rollup | 插件生态成熟,定制化能力强 |
| 打包组件库 | Rollup | 拆包精细、tree-shaking 更彻底 |
| SSR 应用 | Webpack / Vite | 根据团队熟悉度选择 |
🧠 构建工具不是越快越好,关键是贴合团队现状和项目需求
七、一些实用建议和踩坑总结
✅ 最佳实践建议:
不要一开始就搞一套“银弹”构建系统
- 先保证核心流程通顺,再逐步打磨
- 构建优化是个长期过程,不要急于求成
合理使用缓存策略
- Webpack 的
cache-loader - Vite 的依赖预解析缓存
- Webpack 的
注意 Node.js 版本兼容性
- 不少构建工具的新特性(如 ESM、Top-Level Await)需要高版本 Node 支持
- 提前做好版本管理策略
构建日志可视化很重要
- 推荐配合
webpack-bundle-analyzer做体积分析 - 或者使用 Vite 官方提供的构建报告插件
- 推荐配合
npm install --save-dev webpack-bundle-analyzer
const { BundleAnalyzerPlugin } = require('webpack-bundle-analyzer');
plugins: [
new BundleAnalyzerPlugin()
]
❌ 常见踩坑提醒:
- 不要随便复制别人的
webpack.config.js,每个项目的结构不一样,配置也要调整 - Vite 的插件体系和 Webpack 不兼容,不能混用
- 滥用
resolve.alias导致模块加载混乱 - 误删缓存目录导致反复下载依赖(尤其使用 pnpm/hoist 模式时)
八、效果总结与收益回顾
在一系列优化之后,我们取得了以下成果:
| 指标 | 优化前 | 优化后 |
|---|---|---|
| 开发启动时间 | 5s+ | <1s |
| 热更新响应 | 1~2s | <500ms |
| 主包体积 | 8MB+ | ~3MB |
| 构建缓存命中率 | ~50% | >80% |
| 构建失败率 | 高频 | 明显下降 |
更重要的是,开发同学反馈:
“终于不用等打包了,改完就能看到效果,工作效率提升了不少。”
九、写在最后:构建不只是工具的事
构建工具只是手段,不是目的。真正的目标是:
- 让开发者专注编码本身
- 提升构建质量
- 降低维护成本
无论你选择 Webpack、Vite 还是 Rollup,记住一句话:
“没有最好的构建工具,只有最适合的解决方案。”
构建之路虽有坑,但我们终究会爬出来,并在不断踩坑中成长。
如果你也在构建的路上挣扎,不妨留言聊聊,我们一起探讨。
毕竟,踩过坑的人最懂彼此 😄
如果你觉得这篇文章对你有帮助,欢迎点赞、收藏、转发,也欢迎关注我的个人博客或者 GitHub~咱们下篇文章再见!

评论 0