无论使用 Webpack 还是 Vite,打包工具的最终目标都是:产出一个尽可能小、加载尽可能快的生产包。这需要开发者理解并善用三个核心优化手段:Tree Shaking(摇树优化)、代码分割(Code Splitting)与各类打包体积优化策略。本节以 Webpack 为主视角,同时标注 Vite 中的对应方案,因为原理相通。
Tree Shaking:剔除“你写了但没用到的代码”
Tree Shaking 依赖于 ES Modules 的静态导入导出特性。在编译阶段,打包工具可以分析出哪些导出的函数、变量、组件并未被任何模块引用,从而安全地删除它们。最终构建产物里不会包含任何“死代码”。
在 Webpack 中启用 Tree Shaking:
- 使用 ES Modules:确保项目代码使用
import/export语法,而非 CommonJS 的require/module.exports。 - 生产模式自动开启:
mode: 'production'会默认启用TerserPlugin进行代码压缩,同时 Webpack 会自动标记未使用的导出并将其移除。 - 配置
sideEffects:在package.json中声明哪些文件有副作用(如全局 CSS、polyfill),否则 Webpack 可能误删。
{
"sideEffects": ["*.css", "*.scss"]
}
对于 Vue 单文件组件,vue-loader 会自动处理,无需额外标记。
实际效果:
- 引入
lodash时,如果只用了debounce,以前整个库都会打包。改用lodash-es并配合 Tree Shaking,只会保留debounce及其依赖的几个函数。 - Vue 3 的模块设计就是 Tree Shaking 友好的:像
v-model、<Transition>这类可选功能,如果组件中没用到,最终包体并不会包含它们。
实用建议:
- 永远使用
import { xxx } from 'lib'的方式引入工具函数,避免整体导入。 - 组件库(如 Element Plus) 使用
unplugin-vue-components实现按需自动导入,既能享受 Tree Shaking,也无需手动注册。 - 检查第三方库是否提供 ES Module 版本,否则 Tree Shaking 无法生效。
代码分割:按需加载,而不是一次性全给
代码分割是将打包产物拆分成多个 chunk(代码块),让浏览器按需加载或并行加载,避免首屏加载一个巨大的 bundle。
Webpack 代码分割的三种常用方式:
- 动态导入(Dynamic Import):使用
import()语法,返回一个 Promise。Webpack 会自动将动态导入的模块拆分成独立的 chunk,并在需要时发起网络请求。这是实现路由懒加载的基础。
// 路由配置中
const UserProfile = () => import('./views/UserProfile.vue')
Vue Router 官方推荐在路由定义中使用这种写法,每个页面组件都会被拆成单独的 chunk。
- SplitChunksPlugin 配置:Webpack 内置的
optimization.splitChunks可以提取公共依赖,避免多个 chunk 重复包含相同的库。
// vue.config.js 或 webpack.config.js 中的典型配置
module.exports = {
configureWebpack: {
optimization: {
splitChunks: {
chunks: 'all',
cacheGroups: {
vendor: {
test: /[\\/]node_modules[\\/]/,
name: 'vendors',
chunks: 'all',
},
common: {
minChunks: 2,
priority: -20,
reuseExistingChunk: true,
},
},
},
},
},
}
这样会产生三个核心文件:vendors.js(第三方依赖)、common.js(业务公共模块)以及各页面自己的 chunk。浏览器可以长期缓存 vendors.js,因为业务代码更新不会改动它。
- Magic Comments:在动态导入时可以添加注释来指定 chunk 名称或预加载策略。
import(/* webpackChunkName: "my-component" */ './MyComponent.vue')
在 Vite 中:代码分割基于 Rollup,动态导入天然支持,无需额外配置。生产构建时 Rollup 会自动拆分 chunk,也可以手动配置 build.rollupOptions.output.manualChunks。
打包优化:从配置到线上的全链路瘦身
除了 Tree Shaking 和代码分割,还有一系列“组合拳”能显著提升加载性能。
1. 压缩与混淆
- JavaScript:Webpack 5 内置
TerserWebpackPlugin,生产模式自动开启。Vite 使用 esbuild 压缩,速度极快。 - CSS:使用
css-minimizer-webpack-plugin(Webpack)或 Vite 的css.minify配置。 - HTML:可借助
html-webpack-plugin的minify选项。
2. 静态资源处理
- 图片/字体:Webpack 5 内置 Asset Modules,可配置小于 8KB 的资源转为 base64 内联,减少 HTTP 请求。超出限制的输出为文件并添加哈希以利于缓存。
module: {
rules: [{
test: /\.(png|jpe?g|gif|svg)$/i,
type: 'asset',
parser: { dataUrlCondition: { maxSize: 8 * 1024 } },
generator: { filename: 'images/[name].[hash:8][ext]' },
}],
}
- 雪碧图:可以通过 PostCSS 插件自动合成,减少图标类图片的请求次数。
3. 外部化与 CDN
对于大型第三方库(如 Vue、Vue Router、Pinia),可以通过 externals 配置将它们排除在构建产物之外,然后通过 CDN 的 <script> 标签引入。这样既加快了构建速度,又利用了公共 CDN 的缓存优势。
// webpack externals 配置
externals: {
vue: 'Vue',
'vue-router': 'VueRouter',
pinia: 'Pinia',
}
4. Gzip / Brotli 压缩
打包后的文件通常还需要在服务端开启压缩传输。可以用 compression-webpack-plugin 生成预压缩的 .gz 文件,但更推荐在 Nginx/Caddy 等反向代理层动态压缩,灵活且节省体积。
5. 可视化分析与持续监控
- Webpack:使用
webpack-bundle-analyzer生成可交互的树状图,一眼看出哪些模块占据了最大的体积。 - Vite:有对应的
rollup-plugin-visualizer。 - 将分析步骤集成到 CI 中,当包体积超过阈值时报警,防止不知不觉间包体膨胀。
6. 持久化缓存(Webpack 5)
通过设置 cache.type: 'filesystem',Webpack 会将构建缓存写入磁盘,第二次构建时只重新编译变更模块,显著提升开发增量编译和重复生产构建的速度。
最终检查清单
- 所有路由都用了动态
import()了吗? - 组件库是按需引入的吗?
package.json中正确设置了sideEffects吗?- 打包后的 chunk 是否合理,公共依赖是否被提取了?
- 静态资源大小阈值和内联策略合适吗?
- 是否产出了构建分析报告并检查过大块依赖?
打包优化不是一次性工作,而应作为项目持续集成的一部分。保持在每个迭代中观察包体变化,才能让应用始终“轻量高效”。