webpack配置与优化实践
# webpack配置与优化实践
- webpack.common.js
const chalk = require('chalk')
const HtmlWebpackPlugin = require('html-webpack-plugin');
const ProgressBarPlugin = require('progress-bar-webpack-plugin');
const MiniCssExtractPlugin = require("mini-css-extract-plugin");
const paths = require('./paths');
const ctx = {
isEnvDevelopment: process.env.NODE_ENV === 'development',
isEnvProduction: process.env.NODE_ENV === 'production',
}
const {
isEnvDevelopment,
isEnvProduction
} = ctx
// 小型项目无需 thread-loader,因此注释了
// const threadLoader = require('thread-loader');
// threadLoader.warmup(
// {
// // 池选项,例如传递给 loader 选项
// // 必须匹配 loader 选项才能启动正确的池
// },
// [
// 'sass-loader',
// ]
// );
module.exports = {
// 入口
entry: {
index: './src/index.tsx',
},
// 输出
output: {
// 仅在生产环境添加 hash
filename: ctx.isEnvProduction ? '[name].[contenthash].bundle.js' : '[name].bundle.js',
path: paths.appDist,
// 编译前清除目录
clean: true,
// publicPath: ctx.isEnvProduction ? 'https://xxx.com' : '', 关闭该 CDN 配置,因为示例项目,无 CDN 服务。
},
resolve: {
alias: {
'@': paths.appSrc,
},
extensions: ['.tsx', '.js'],
modules: [
'node_modules',
paths.appSrc,
],
symlinks: false,
},
plugins: [
// 生成html,自动引入所有bundle
new HtmlWebpackPlugin({
title: 'release_v1',
}),
// 进度条
new ProgressBarPlugin({
format: ` :msg [:bar] ${chalk.green.bold(':percent')} (:elapsed s)`
}),
],
module: {
rules: [
{
test: /\.(png|svg|jpg|jpeg|gif)$/i,
include: paths.appSrc,
type: 'asset/resource',
},
{
test: /.(woff|woff2|eot|ttf|otf)$/i,
include: paths.appSrc,
type: 'asset/resource',
},
{
test: /\.module\.(scss|sass)$/,
include: paths.appSrc,
use: [
// 将 JS 字符串生成为 style 节点
'style-loader',
isEnvProduction && MiniCssExtractPlugin.loader, // 仅生产环境
// 将 CSS 转化成 CommonJS 模块
{
loader: 'css-loader',
options: {
// Enable CSS Modules features
modules: true,
importLoaders: 2,
// 0 => no loaders (default);
// 1 => postcss-loader;
// 2 => postcss-loader, sass-loader
},
},
// 将 PostCSS 编译成 CSS
{
loader: 'postcss-loader',
options: {
postcssOptions: {
plugins: [
[
// postcss-preset-env 包含 autoprefixer
'postcss-preset-env',
],
],
},
},
},
// {
// loader: 'thread-loader',
// options: {
// workerParallelJobs: 2
// }
// },
// 将 Sass 编译成 CSS
'sass-loader',
].filter(Boolean),
},
{
test: /\.(js|ts|jsx|tsx)$/,
include: paths.appSrc,
use: [
{
loader: 'esbuild-loader',
options: {
loader: 'tsx',
target: 'es2015',
},
}
]
}
],
},
cache: {
type: 'filesystem', // 使用文件缓存
},
}
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
133
134
135
136
137
138
139
140
141
142
143
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
133
134
135
136
137
138
139
140
141
142
143
- webpack.dev.js
const webpack = require('webpack')
const { merge } = require('webpack-merge');
const SpeedMeasurePlugin = require('speed-measure-webpack-plugin');
const ReactRefreshWebpackPlugin = require('@pmmmwh/react-refresh-webpack-plugin');
const common = require('./webpack.common')
const smp = new SpeedMeasurePlugin();
const isNeedSpeed = true
const config = merge(common, {
// 模式
mode: 'development',
// 开发工具,开启 source map,编译调试
devtool: 'eval-cheap-module-source-map',
// 开发模式,自动更新改动
devServer: {
contentBase: './dist',
hot: true, // 热更新
},
plugins: [
new webpack.HotModuleReplacementPlugin(),
new ReactRefreshWebpackPlugin(),
],
})
module.exports = isNeedSpeed ? smp.wrap(config) : config
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
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
- webpack.prod.js
const glob = require('glob')
const { merge } = require('webpack-merge');
const TerserPlugin = require('terser-webpack-plugin');
// const CssMinimizerPlugin = require("css-minimizer-webpack-plugin");
const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin;
const MiniCssExtractPlugin = require("mini-css-extract-plugin");
const PurgeCSSPlugin = require('purgecss-webpack-plugin')
const common = require('./webpack.common')
const paths = require('./paths')
module.exports = merge(common, {
// 模式
mode: 'production',
plugins: [
// 打包体积分析
// new BundleAnalyzerPlugin(),
// 提取 CSS
new MiniCssExtractPlugin({
filename: "[hash].[name].css",
}),
// CSS Tree Shaking
new PurgeCSSPlugin({
paths: glob.sync(`${paths.appSrc}/**/*`, { nodir: true }),
}),
],
optimization: {
runtimeChunk: true,
moduleIds: 'deterministic',
minimizer: [
// 在 webpack@5 中,你可以使用 `...` 语法来扩展现有的 minimizer(即 `terser-webpack-plugin`),将下一行取消注释
// `...`,
// new CssMinimizerPlugin({
// parallel: 4,
// }),
new TerserPlugin({
parallel: 4,
terserOptions: {
parse: {
ecma: 8,
},
compress: {
ecma: 5,
warnings: false,
comparisons: false,
inline: 2,
},
mangle: {
safari10: true,
},
output: {
ecma: 5,
comments: false,
ascii_only: true,
},
},
}),
],
splitChunks: {
// include all types of chunks
chunks: 'all',
// 重复打包问题
cacheGroups:{
vendors:{ // node_modules里的代码
test: /[\\/]node_modules[\\/]/,
chunks: "all",
// name: 'vendors', 一定不要定义固定的name
priority: 10, // 优先级
enforce: true
}
}
},
},
})
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
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
- path.js
const fs = require('fs')
const path = require('path')
const appDirectory = fs.realpathSync(process.cwd());
const resolveApp = relativePath => path.resolve(appDirectory, relativePath);
module.exports = {
resolveApp,
appPublic: resolveApp('public'),
appHtml: resolveApp('public/index.html'),
appSrc: resolveApp('src'),
appDist: resolveApp('dist'),
appTsConfig: resolveApp('tsconfig.json'),
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
2
3
4
5
6
7
8
9
10
11
12
13
14
- package.json
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"dev": "cross-env NODE_ENV=development webpack serve --open --config config/webpack.dev.js",
"build": "cross-env NODE_ENV=production webpack --config config/webpack.prod.js"
},
1
2
3
4
5
2
3
4
5