Webpack构建优化实战

前言

随着前端项目规模的增长,Webpack构建时间越来越长,打包体积越来越大。本文将系统讲解Webpack优化技巧,从构建速度到运行性能,帮助你打造高效的前端工程化方案。


一、构建速度优化

1.1 缩小文件搜索范围

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
// webpack.config.js
module.exports = {
resolve: {
// 指定扩展名
extensions: ['.js', '.jsx', '.json'],

// 指定模块搜索目录
modules: [path.resolve(__dirname, 'src'), 'node_modules'],

// 别名配置
alias: {
'@': path.resolve(__dirname, 'src'),
'components': path.resolve(__dirname, 'src/components'),
'utils': path.resolve(__dirname, 'src/utils')
},

// 对于node_modules中的包,优先使用es模块
mainFields: ['module', 'main']
},

module: {
rules: [
{
test: /\.js$/,
// 明确include,减少搜索范围
include: path.resolve(__dirname, 'src'),
// 排除node_modules
exclude: /node_modules/,
use: 'babel-loader'
}
]
}
};

1.2 使用DLL优化

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
// webpack.dll.config.js
const webpack = require('webpack');
const path = require('path');

module.exports = {
mode: 'production',
entry: {
vendor: ['react', 'react-dom', 'axios', 'lodash']
},
output: {
filename: '[name].dll.js',
path: path.resolve(__dirname, 'dll'),
library: '[name]_library'
},
plugins: [
new webpack.DllPlugin({
name: '[name]_library',
path: path.resolve(__dirname, 'dll/[name].manifest.json')
})
]
};

// webpack.config.js
const webpack = require('webpack');

module.exports = {
plugins: [
new webpack.DllReferencePlugin({
manifest: require('./dll/vendor.manifest.json')
})
]
};

// 构建DLL
// npm run build:dll

1.3 多进程构建

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
// 使用thread-loader
module.exports = {
module: {
rules: [
{
test: /\.js$/,
use: [
{
loader: 'thread-loader',
options: {
workers: 4 // 线程数量
}
},
'babel-loader'
]
}
]
}
};

// 或使用HappyPack(较老的方案)
const HappyPack = require('happypack');
const os = require('os');
const happyThreadPool = HappyPack.ThreadPool({ size: os.cpus().length });

module.exports = {
module: {
rules: [
{
test: /\.js$/,
use: 'happypack/loader?id=babel'
}
]
},
plugins: [
new HappyPack({
id: 'babel',
loaders: ['babel-loader'],
threadPool: happyThreadPool
})
]
};

1.4 缓存配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
module.exports = {
module: {
rules: [
{
test: /\.js$/,
use: [
{
loader: 'babel-loader',
options: {
cacheDirectory: true, // 开启babel缓存
cacheCompression: false // 不压缩缓存
}
}
]
}
]
},

// Webpack 5 内置缓存
cache: {
type: 'filesystem', // 'memory' | 'filesystem'
cacheDirectory: path.resolve(__dirname, '.temp_cache'),
buildDependencies: {
config: [__filename] // 配置文件变化时重新缓存
}
}
};

二、打包体积优化

2.1 Tree Shaking

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
// webpack.config.js
module.exports = {
mode: 'production', // production模式自动开启

optimization: {
usedExports: true, // 标记未使用的导出
minimize: true, // 压缩代码
sideEffects: false // 告诉webpack可以安全删除未用代码
}
};

// package.json
{
"sideEffects": [
"*.css",
"*.scss"
]
}

// 使用ES6模块导入
// ✅ 支持Tree Shaking
import { debounce } from 'lodash-es';

// ❌ 不支持Tree Shaking
import _ from 'lodash';

2.2 代码分割

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
module.exports = {
optimization: {
splitChunks: {
chunks: 'all', // 'async', 'initial', 'all'

cacheGroups: {
// 第三方库
vendor: {
test: /[\\/]node_modules[\\/]/,
name: 'vendors',
priority: 10
},

// 公共模块
common: {
minChunks: 2,
priority: 5,
reuseExistingChunk: true,
name: 'common'
},

// React相关
react: {
test: /[\\/]node_modules[\\/](react|react-dom)[\\/]/,
name: 'react',
priority: 20
},

// UI组件库
antd: {
test: /[\\/]node_modules[\\/]antd[\\/]/,
name: 'antd',
priority: 15
}
}
},

// 运行时代码单独打包
runtimeChunk: {
name: 'runtime'
}
}
};

2.3 动态导入

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// 路由懒加载
const Home = () => import(/* webpackChunkName: "home" */ './pages/Home');
const About = () => import(/* webpackChunkName: "about" */ './pages/About');

// React.lazy
import React, { Suspense, lazy } from 'react';

const LazyComponent = lazy(() => import('./LazyComponent'));

function App() {
return (
<Suspense fallback={<div>Loading...</div>}>
<LazyComponent />
</Suspense>
);
}

// 条件加载
button.addEventListener('click', () => {
import('./module').then(module => {
module.doSomething();
});
});

2.4 压缩优化

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
const TerserPlugin = require('terser-webpack-plugin');
const CssMinimizerPlugin = require('css-minimizer-webpack-plugin');

module.exports = {
optimization: {
minimizer: [
// JS压缩
new TerserPlugin({
parallel: true, // 多进程
terserOptions: {
compress: {
drop_console: true, // 删除console
drop_debugger: true,
pure_funcs: ['console.log'] // 删除特定函数
},
format: {
comments: false // 删除注释
}
},
extractComments: false // 不提取注释到单独文件
}),

// CSS压缩
new CssMinimizerPlugin({
parallel: true
})
]
}
};

三、资源优化

3.1 图片优化

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
module.exports = {
module: {
rules: [
{
test: /\.(png|jpg|jpeg|gif)$/,
type: 'asset',
parser: {
dataUrlCondition: {
maxSize: 10 * 1024 // 10KB以下转base64
}
},
generator: {
filename: 'images/[name].[hash:8][ext]'
}
},

// 图片压缩
{
test: /\.(png|jpg|jpeg|gif)$/,
use: [
{
loader: 'image-webpack-loader',
options: {
mozjpeg: {
progressive: true,
quality: 65
},
optipng: {
enabled: false
},
pngquant: {
quality: [0.65, 0.90],
speed: 4
},
gifsicle: {
interlaced: false
},
webp: {
quality: 75
}
}
}
]
}
]
}
};

3.2 字体优化

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
module.exports = {
module: {
rules: [
{
test: /\.(woff|woff2|eot|ttf|otf)$/,
type: 'asset/resource',
generator: {
filename: 'fonts/[name].[hash:8][ext]'
}
}
]
}
};

// CSS中使用
@font-face {
font-family: 'CustomFont';
src: url('./fonts/custom-font.woff2') format('woff2'),
url('./fonts/custom-font.woff') format('woff');
font-display: swap; /* 优化字体加载 */
}

3.3 CSS优化

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
const PurgeCSSPlugin = require('purgecss-webpack-plugin');
const glob = require('glob');

module.exports = {
module: {
rules: [
{
test: /\.css$/,
use: [
// 生产环境提取CSS
process.env.NODE_ENV === 'production'
? MiniCssExtractPlugin.loader
: 'style-loader',
'css-loader',
'postcss-loader'
]
},

{
test: /\.scss$/,
use: [
MiniCssExtractPlugin.loader,
'css-loader',
'postcss-loader',
'sass-loader'
]
}
]
},

plugins: [
// 提取CSS
new MiniCssExtractPlugin({
filename: 'css/[name].[contenthash:8].css',
chunkFilename: 'css/[id].[contenthash:8].css'
}),

// 清除未使用的CSS
new PurgeCSSPlugin({
paths: glob.sync(`${path.join(__dirname, 'src')}/**/*`, {
nodir: true
}),
safelist: {
standard: [/^ant-/, /^el-/] // 保留antd、element-ui的类名
}
})
]
};

四、缓存策略

4.1 文件名Hash

1
2
3
4
5
6
7
8
9
10
11
12
13
14
module.exports = {
output: {
filename: '[name].[contenthash:8].js',
chunkFilename: '[name].[contenthash:8].chunk.js',
path: path.resolve(__dirname, 'dist'),
clean: true // 清理旧文件
},

plugins: [
new MiniCssExtractPlugin({
filename: 'css/[name].[contenthash:8].css'
})
]
};

4.2 ModuleIds优化

1
2
3
4
5
6
module.exports = {
optimization: {
moduleIds: 'deterministic', // 确定性ID
chunkIds: 'deterministic'
}
};

五、开发体验优化

5.1 HMR配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
module.exports = {
devServer: {
hot: true, // 启用HMR
port: 3000,
open: true,
compress: true,
historyApiFallback: true,

// 代理配置
proxy: {
'/api': {
target: 'http://localhost:8080',
changeOrigin: true,
pathRewrite: {
'^/api': ''
}
}
}
}
};

// 代码中使用HMR
if (module.hot) {
module.hot.accept('./module', () => {
// 模块更新时的处理
});
}

5.2 Source Map配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
module.exports = {
// 开发环境
devtool: 'eval-cheap-module-source-map',

// 生产环境
// devtool: 'hidden-source-map', // 或 'nosources-source-map'
};

// Source Map类型对比:
// - eval: 最快,但代码映射不准确
// - cheap: 不包含列信息
// - module: 包含loader的source map
// - hidden: 不在bundle中引用source map
// - nosources: 不包含源代码

六、分析工具

6.1 Webpack Bundle Analyzer

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin;

module.exports = {
plugins: [
new BundleAnalyzerPlugin({
analyzerMode: 'static',
openAnalyzer: false,
reportFilename: 'bundle-report.html'
})
]
};

// package.json
{
"scripts": {
"analyze": "webpack --config webpack.config.js --profile --json > stats.json && webpack-bundle-analyzer stats.json"
}
}

6.2 Speed Measure Plugin

1
2
3
4
5
6
7
8
const SpeedMeasurePlugin = require('speed-measure-webpack-plugin');
const smp = new SpeedMeasurePlugin();

module.exports = smp.wrap({
// webpack配置
});

// 输出每个loader和plugin的耗时

七、实战案例

7.1 完整配置示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
const TerserPlugin = require('terser-webpack-plugin');
const CssMinimizerPlugin = require('css-minimizer-webpack-plugin');
const { BundleAnalyzerPlugin } = require('webpack-bundle-analyzer');

const isProduction = process.env.NODE_ENV === 'production';

module.exports = {
mode: isProduction ? 'production' : 'development',

entry: {
app: './src/index.js'
},

output: {
filename: isProduction
? 'js/[name].[contenthash:8].js'
: 'js/[name].js',
chunkFilename: isProduction
? 'js/[name].[contenthash:8].chunk.js'
: 'js/[name].chunk.js',
path: path.resolve(__dirname, 'dist'),
clean: true,
publicPath: '/'
},

resolve: {
extensions: ['.js', '.jsx', '.json'],
alias: {
'@': path.resolve(__dirname, 'src')
}
},

module: {
rules: [
{
test: /\.jsx?$/,
include: path.resolve(__dirname, 'src'),
use: [
{
loader: 'thread-loader',
options: { workers: 4 }
},
{
loader: 'babel-loader',
options: {
cacheDirectory: true
}
}
]
},
{
test: /\.css$/,
use: [
isProduction ? MiniCssExtractPlugin.loader : 'style-loader',
'css-loader',
'postcss-loader'
]
},
{
test: /\.(png|jpg|jpeg|gif|svg)$/,
type: 'asset',
parser: {
dataUrlCondition: {
maxSize: 10 * 1024
}
}
}
]
},

plugins: [
new HtmlWebpackPlugin({
template: './public/index.html',
minify: isProduction ? {
removeComments: true,
collapseWhitespace: true,
removeAttributeQuotes: true
} : false
}),

isProduction && new MiniCssExtractPlugin({
filename: 'css/[name].[contenthash:8].css'
}),

process.env.ANALYZE && new BundleAnalyzerPlugin()
].filter(Boolean),

optimization: {
moduleIds: 'deterministic',
runtimeChunk: 'single',

splitChunks: {
chunks: 'all',
cacheGroups: {
vendor: {
test: /[\\/]node_modules[\\/]/,
name: 'vendors',
priority: 10
}
}
},

minimizer: [
new TerserPlugin({
parallel: true,
terserOptions: {
compress: {
drop_console: true
}
}
}),
new CssMinimizerPlugin()
]
},

cache: {
type: 'filesystem'
},

devServer: {
hot: true,
port: 3000,
compress: true
},

devtool: isProduction
? false
: 'eval-cheap-module-source-map'
};

八、最佳实践

优化清单

构建速度:

  • ✅ 缩小文件搜索范围
  • ✅ 使用DLL或externals
  • ✅ 多进程构建
  • ✅ 开启缓存

打包体积:

  • ✅ Tree Shaking
  • ✅ 代码分割
  • ✅ 动态导入
  • ✅ 压缩优化

运行性能:

  • ✅ 长期缓存
  • ✅ CDN加载
  • ✅ 预加载/预获取
  • ✅ 懒加载

注意事项

  1. 不同环境使用不同配置
  2. 定期分析bundle,找出优化点
  3. 根据项目规模选择合适的优化方案
  4. 平衡构建速度和打包体积

九、总结

Webpack优化是一个系统工程,需要从多个维度入手:

  1. 构建速度:缓存、多进程、减少搜索范围
  2. 打包体积:Tree Shaking、代码分割、压缩
  3. 运行性能:缓存策略、资源优化
  4. 开发体验:HMR、Source Map

掌握这些技巧,让你的Webpack构建飞起来!


参考资料