Webpack 高级优化实战 —— 生产级性能优化指南
Webpack 高级优化实战 —— 生产级性能优化指南
系列文章第 5 篇(最终篇) | 预计阅读时间:22 分钟
一、核心优化思路
生产级优化从三个维度入手:
- 构建速度优化 — 让打包更快
- 打包体积优化 — 让文件更小
- 运行性能优化 — 让代码更快
二、构建速度优化
2.1 多进程打包
适用场景: 大型项目(100+ 文件)
安装依赖:
npm i thread-loader -D
配置:
const os = require("os");
const threads = os.cpus().length; // 获取 CPU 核心数
module.exports = {
module: {
rules: [
{
test: /\.js$/,
use: [
{
loader: "thread-loader",
options: {
workers: threads, // 进程数量
},
},
{
loader: "babel-loader",
},
],
},
],
},
plugins: [
new ESLintWebpackPlugin({
threads, // ESLint 也开启多进程
}),
],
optimization: {
minimizer: [
new TerserPlugin({
parallel: threads, // 压缩也开启多进程
}),
],
},
};
⚠️ 注意事项:
- 进程启动开销约 600ms,小项目不建议使用
- 进程数量 = CPU 核心数,不要设置过多
2.2 缓存优化
Babel 缓存:
{
loader: "babel-loader",
options: {
cacheDirectory: true, // 开启缓存
cacheCompression: false, // 不压缩缓存(更快)
},
}
ESLint 缓存:
new ESLintWebpackPlugin({
cache: true,
cacheLocation: path.resolve(
__dirname,
"node_modules/.cache/.eslintcache"
),
});
2.3 缩小处理范围
使用 include 限定目录:
{
test: /\.js$/,
include: path.resolve(__dirname, "src"), // 只处理 src 目录
loader: "babel-loader",
}
使用 exclude 排除目录:
{
test: /\.js$/,
exclude: /node_modules/, // 排除第三方代码
loader: "babel-loader",
}
推荐: 使用 include 更精确,性能更好。
三、打包体积优化
3.1 Tree Shaking
原理: 移除未使用的 ES Module 代码
启用条件:
- ✅ 使用 ES Module 语法(
import/export) - ✅ 生产模式(Webpack 默认开启)
- ✅
package.json配置"sideEffects"
配置:
{
"sideEffects": false // 所有文件无副作用
}
排除有副作用的文件(如 CSS):
{
"sideEffects": [
"*.css",
"*.scss",
"*.less"
]
}
3.2 Babel 优化
问题: Babel 为每个文件插入重复的辅助代码
解决方案: 使用 @babel/plugin-transform-runtime
安装依赖:
npm i @babel/plugin-transform-runtime @babel/runtime -D
babel.config.js 配置:
module.exports = {
presets: ["@babel/preset-env"],
plugins: [
["@babel/plugin-transform-runtime", {
"corejs": 3, // 启用 core-js polyfill
}],
],
};
效果: 所有文件共享同一份辅助代码,体积减少 30%-50%。
3.3 代码分割
多入口分割
module.exports = {
entry: {
main: "./src/main.js",
admin: "./src/admin.js",
},
output: {
filename: "js/[name].js",
},
};
提取公共代码
module.exports = {
optimization: {
splitChunks: {
chunks: "all",
cacheGroups: {
default: {
minChunks: 2, // 至少被引用 2 次
priority: -20,
reuseExistingChunk: true,
},
},
},
},
};
动态导入(按需加载)
// 点击按钮时才加载
document.getElementById("btn").onclick = async () => {
const { sum } = await import("./math.js");
console.log(sum(1, 2, 3, 4));
};
// 自定义 chunk 名称
import(/* webpackChunkName: "math" */ "./math.js")
.then(({ sum }) => {
console.log(sum(1, 2, 3, 4));
});
输出: math.chunk.js
四、运行性能优化
4.1 Preload / Prefetch
Preload: 立即加载当前页面需要的资源
Prefetch: 浏览器空闲时加载下一个页面需要的资源
安装插件:
npm i @vue/preload-webpack-plugin -D
配置:
const PreloadWebpackPlugin = require("@vue/preload-webpack-plugin");
module.exports = {
plugins: [
new PreloadWebpackPlugin({
rel: "preload", // 或 prefetch
as: "script",
}),
],
};
HTML 输出:
<!-- Preload:立即加载 -->
<link rel="preload" href="js/math.chunk.js" as="script">
<!-- Prefetch:空闲时加载 -->
<link rel="prefetch" href="js/next-page.chunk.js">
4.2 网络缓存优化
Hash 类型对比:
| Hash 类型 | 说明 | 适用场景 |
|---|---|---|
hash |
整个项目的 hash | ❌ 不推荐 |
chunkhash |
根据入口生成 | ⚠️ JS 文件 |
contenthash |
根据内容生成 | ✅ 推荐 |
配置:
module.exports = {
output: {
filename: "js/[name].[contenthash:8].js",
chunkFilename: "js/[name].[contenthash:8].chunk.js",
assetModuleFilename: "media/[name].[contenthash:8][ext]",
},
plugins: [
new MiniCssExtractPlugin({
filename: "css/[name].[contenthash:8].css",
chunkFilename: "css/[name].[contenthash:8].chunk.css",
}),
],
};
4.3 Runtime 文件
问题: 修改一个模块,主文件 hash 也会变化
解决方案: 提取 runtime 到独立文件
module.exports = {
optimization: {
runtimeChunk: {
name: "runtime",
},
},
};
效果: 修改业务代码时,只有业务文件和 runtime 变化。
五、PWA 离线缓存
5.1 什么是 PWA?
PWA(Progressive Web App) 是一种使用 Web 技术开发的应用,具有:
- ✅ 离线访问能力
- ✅ 可添加到主屏幕
- ✅ 推送通知
- ✅ 类似原生应用的体验
5.2 Workbox 配置
安装依赖:
npm i workbox-webpack-plugin -D
配置:
const WorkboxPlugin = require("workbox-webpack-plugin");
module.exports = {
plugins: [
new WorkboxPlugin.GenerateSW({
clientsClaim: true, // 立即接管客户端
skipWaiting: true, // 跳过等待阶段
runtimeCaching: [
{
urlPattern: /\.(png|jpe?g|gif|svg)$/,
handler: "CacheFirst",
options: {
cacheName: "images-cache",
expiration: {
maxEntries: 50,
maxAgeSeconds: 30 * 24 * 60 * 60, // 30 天
},
},
},
{
urlPattern: /\.js$/,
handler: "StaleWhileRevalidate",
options: {
cacheName: "js-cache",
},
},
],
}),
],
};
5.3 注册 Service Worker
// main.js
if ("serviceWorker" in navigator) {
window.addEventListener("load", () => {
navigator.serviceWorker
.register("/service-worker.js")
.then((registration) => {
console.log("SW registered:", registration);
})
.catch((error) => {
console.log("SW registration failed:", error);
});
});
}
5.4 测试 PWA
使用 serve 启动本地服务器:
npm i serve -g
serve dist
注意: 直接打开 file:// 无法测试 Service Worker,必须使用 HTTP 服务器。
六、Core-js Polyfill
6.1 为什么需要 Polyfill?
问题: 新语法(如 Promise、Array.includes)在旧浏览器不支持
解决方案: 使用 core-js 注入 polyfill
6.2 配置方式
安装依赖:
npm i core-js -D
babel.config.js 配置:
module.exports = {
presets: [
["@babel/preset-env", {
"useBuiltIns": "usage", // 按需引入
"corejs": 3, // core-js 版本
"targets": { // 目标浏览器
"chrome": "60",
"firefox": "60",
"ie": "11",
},
}],
],
};
效果: 自动检测代码中使用的 ES 新特性,按需注入 polyfill。
6.3 三种模式对比
| 模式 | 配置 | 特点 |
|---|---|---|
false |
不注入 | 体积最小,兼容性差 |
entry |
手动引入 core-js |
体积较大 |
usage |
自动检测 | 推荐,按需注入 |
七、性能分析工具
7.1 Webpack Bundle Analyzer
安装:
npm i webpack-bundle-analyzer -D
配置:
const { BundleAnalyzerPlugin } = require("webpack-bundle-analyzer");
module.exports = {
plugins: [
new BundleAnalyzerPlugin({
analyzerMode: "static", // 生成 HTML 报告
openAnalyzer: false, // 不自动打开
}),
],
};
查看报告: 打开 dist/report.html
7.2 分析内容
- 📊 每个模块的体积
- 📊 依赖关系图
- 📊 找出体积最大的包
- 📊 优化建议
八、生产模式完整配置
8.1 最终配置
const os = require("os");
const path = require("path");
const ESLintWebpackPlugin = require("eslint-webpack-plugin");
const HtmlWebpackPlugin = require("html-webpack-plugin");
const MiniCssExtractPlugin = require("mini-css-extract-plugin");
const CssMinimizerPlugin = require("css-minimizer-webpack-plugin");
const TerserPlugin = require("terser-webpack-plugin");
const PreloadWebpackPlugin = require("@vue/preload-webpack-plugin");
const WorkboxPlugin = require("workbox-webpack-plugin");
const threads = os.cpus().length;
const getStyleLoaders = (preProcessor) => {
return [
MiniCssExtractPlugin.loader,
"css-loader",
{
loader: "postcss-loader",
options: {
postcssOptions: {
plugins: ["postcss-preset-env"],
},
},
},
preProcessor,
].filter(Boolean);
};
module.exports = {
entry: "./src/main.js",
output: {
path: path.resolve(__dirname, "dist"),
filename: "js/[name].[contenthash:8].js",
chunkFilename: "js/[name].[contenthash:8].chunk.js",
assetModuleFilename: "media/[name].[contenthash:8][ext]",
clean: true,
},
module: {
rules: [
{
oneOf: [
{
test: /\.css$/,
use: getStyleLoaders(),
},
{
test: /\.less$/,
use: getStyleLoaders("less-loader"),
},
{
test: /\.s[ac]ss$/,
use: getStyleLoaders("sass-loader"),
},
{
test: /\.(png|jpe?g|gif|webp)$/,
type: "asset",
parser: {
dataUrlCondition: {
maxSize: 10 * 1024,
},
},
},
{
test: /\.(ttf|woff2?)$/,
type: "asset/resource",
},
{
test: /\.js$/,
include: path.resolve(__dirname, "src"),
use: [
{
loader: "thread-loader",
options: { workers: threads },
},
{
loader: "babel-loader",
options: {
cacheDirectory: true,
cacheCompression: false,
plugins: ["@babel/plugin-transform-runtime"],
},
},
],
},
],
},
],
},
plugins: [
new ESLintWebpackPlugin({
context: path.resolve(__dirname, "src"),
exclude: /node_modules/,
cache: true,
threads,
}),
new HtmlWebpackPlugin({
template: path.resolve(__dirname, "public/index.html"),
minify: {
collapseWhitespace: true,
removeComments: true,
},
}),
new MiniCssExtractPlugin({
filename: "css/[name].[contenthash:8].css",
chunkFilename: "css/[name].[contenthash:8].chunk.css",
}),
new PreloadWebpackPlugin({
rel: "preload",
as: "script",
}),
new WorkboxPlugin.GenerateSW({
clientsClaim: true,
skipWaiting: true,
}),
],
optimization: {
minimizer: [
new CssMinimizerPlugin(),
new TerserPlugin({
parallel: threads,
}),
],
splitChunks: {
chunks: "all",
},
runtimeChunk: {
name: "runtime",
},
},
mode: "production",
devtool: "source-map",
};
8.2 打包脚本
{
"scripts": {
"build": "npx webpack --config webpack.prod.js",
"analyze": "npx webpack --config webpack.analyze.js"
}
}
九、系列总结
9.1 五篇文章回顾
| 篇目 | 核心内容 | 关键技能 |
|---|---|---|
| 第 1 篇 | 入门与基础 | Webpack 基本概念、快速上手 |
| 第 2 篇 | 开发模式 | HMR、Source Map、OneOf、Cache |
| 第 3 篇 | 生产模式 | Tree Shaking、代码分割、网络缓存 |
| 第 4 篇 | 资源处理 | CSS/JS/HTML/图片/字体处理 |
| 第 5 篇 | 高级优化 | PWA、Core-js、性能分析 |
9.2 性能提升总结
| 优化项 | 效果 |
|---|---|
| 多进程打包 | 大型项目 2-3 倍 |
| Tree Shaking | 体积减少 20%-40% |
| 代码分割 | 初始加载减少 50%+ |
| 网络缓存 | 二次加载快 80%+ |
| Babel 优化 | 体积减少 30%-50% |
9.3 最佳实践清单
- ✅ 开发模式使用
cheap-module-source-map - ✅ 开启 HMR 热更新
- ✅ 使用
oneOf优化 loader 匹配 - ✅ 用
include限定处理范围 - ✅ 开启 Babel 和 ESLint 缓存
- ✅ 生产模式开启 Tree Shaking
- ✅ 使用
contenthash做长期缓存 - ✅ 代码分割 + 动态导入
- ✅ 大型项目开启多进程
- ✅ 使用 Bundle Analyzer 分析体积
十、结语
🎉 恭喜!你已经完成了 Webpack 从入门到精通的完整学习!
接下来你可以:
- 📚 深入学习 Webpack 原理(Loader/Plugin 开发)
- 🛠️ 实战项目应用(React/Vue 项目配置)
- 🔧 自定义 Loader 和 Plugin
- 📖 阅读 Webpack 官方文档和源码
学习资源推荐:
系列导航:
- 📌 第 1 篇:Webpack 入门与基础
- 📌 第 2 篇:开发模式完全指南
- 📌 第 3 篇:生产模式与性能优化
- 📌 第 4 篇:资源处理大全
- 📌 第 5 篇:高级优化实战(本文)
🎊 系列完结,感谢阅读!
本文基于 Webpack 5 编写,如有问题欢迎留言讨论。
评论 (0)