Webpack
# 1. Webpack 配置
webpack.config.js
// Node 内置路径模块 const { resolve } = require('path') // 生成 HTML const HtmlWebpackPlugin = require('html-webpack-plugin') // 提取 CSS 成单独文件 const MiniCssExtractPlugin = require('mini-css-extract-plugin') // 压缩 CSS const OptimizeCssAssetsWebpackPlugin = require('optimize-css-assets-webpack-plugin') const isProduction = process.env.NODE_ENV === 'production' const commonCssLoader = [ // style-loader 自带 HMR isProduction ? MiniCssExtractPlugin.loader : 'style-loader', 'css-loader', { loader: 'postcss-loader', options: { ident: 'postcss', plugins: () => [require('postcss-preset-env')], }, }, ] module.exports = { // 模式:development(开发)、production(生产) mode: 'production', // 入口:要打包的文件 entry: resolve(__dirname, './src/index.js'), // 出口 output: { // 资源引用路径前缀 publicPath: isProduction ? './' : '/', // 打包后的文件夹路径 path: resolve(__dirname, 'build'), // 打包后文件的名字 filename: 'js/[contenthash:10].js', // 非入口 chunk 名称(会影响文件命名缓存,可使用 optimization 中 runtimeChunk 配置可解决) chunkFilename: 'js/[name]_chunk.js', }, // 插件 plugins: [ new HtmlWebpackPlugin({ // 要在内存中生成的文件 template: './src/index.html', // 内存中生成文件的名字 filename: 'index.html', // 压缩HTML minify: { // 移除空格 collapseWhitespace: true, // 移除注释 removeComments: true, }, }), new MiniCssExtractPlugin({ filename: 'css/built.[contenthash:10].css', }), new OptimizeCssAssetsWebpackPlugin(), ], // loader规则配置(多个loader倒序使用use) module: { rules: [ // eslint { test: /\.js$/, loader: 'eslint-loader', // 优先执行 enforce: 'pre', exclude: /node_modules/, options: { // 自动修复 fix: true, }, }, // oneOf:只匹配一个 loader(不能有多个loader处理相同类型文件) { oneOf: [ // babel { test: /\.js$/, loader: 'babel-loader', exclude: /node_modules/, options: { presets: [ '@babel/preset-env', { // 按需加载 useBuiltIns: 'usage', // 指定 core-js 版本 corejs: { version: 3, }, // 指定兼容版本 targets: { chrome: '60', firefox: '60', ie: '9', safari: '10', edge: '17', }, }, ], // 开启 babel 缓存 cacheDirectory: true, }, }, // css { test: /\.css$/i, use: commonCssLoader, }, // less { test: /\.less$/i, use: [...commonCssLoader, 'less-loader'], }, // sass、scss { test: /\.s[ac]ss$/i, use: [...commonCssLoader, 'sass-loader'], }, // 图片 { test: /\.(png|jpe?g|gif)$/i, loader: 'url-loader', options: { // 图片小于 limit,打包成 base64 格式,大于 limit 生成 hash 名称的图片 limit: 10 * 1024, // 10 位 hash 名称图片 name: '[hash:10].[ext]', // 输出路径 outputPath: 'assets', // true:ESM、false:CJS、默认为 true esModule: false, }, }, // html图片 { test: /\.html$/i, loader: 'html-loader', }, // 其他资源 { // 排除 html|css|js|less|sass|jpe?g|png|gif 资源 exclude: /\.(html|css|js|less|sass|jpe?g|png|gif)$/i, loader: 'file-loader', options: { name: '[hash:10].[ext]', // 输出路径 outputPath: 'media', }, }, ], }, ], }, // 开发配置 devServer: { // 启动路径(打包出口路径) contentBase: resolve(_dirname, 'build'), // 开启 HMR hot: true, // 启动 gzip 压缩 compress: true, // 服务器外部可访问 host: '0.0.0.0', // 启动端口 port: 7777, // 自动打开浏览器 open: true, // 指定打开页面 openPage: '/home', // 可防止热更新失效 disableHostCheck: true, // 不显示启动日志 clientLogLevel: 'none', // 除了基本启动信息,其余都不显示 quiet: true, // 不全屏提示错误 overlay: false, // 代理 proxy: { // 要代理的请求路径 '/agency': { // 要跨域的域名(后端接口地址) target: 'http://www.coderljw.ga:4396', // 是否开启跨域 changeOrigin: true, // 将 /agency 替换成 ''(删除 /agency) pathRewrite: { '^/agency': '', }, }, }, }, // 解析模块规则 resolve: { // 别名 alias: { '@': resolve(__dirname, 'src'), }, // 省略后缀 extensions: ['.js', 'json', 'jsx'], }, }
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
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212package.json
// postcss "browserslist": { "development": [ "last 1 chrome version", "last 1 firefox version", "last 1 safari version" ], "production": [ ">0.2%", "not dead", "not op_mini all" ] } // eslint "eslintConfig": { // 继承 airbnb-base "extends": "airbnb-base", "env": { // 支持浏览器全局变量 "browser": true } }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
# 2. 提取 CSS 成单独文件
const MiniCssExtractPlugin = require('mini-css-extract-plugin')
module.exports = {
module: {
rules: [
// css
{
test: /\.css$/,
use: [MiniCssExtractPlugin.loader, 'css-loader'],
},
],
},
plugins: [
new MiniCssExtractPlugin({
filename: 'css/built.css',
}),
],
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# 3. CSS 兼容性处理
webpack.config.js
module.exports = { module: { rules: [ { test: /\.css$/, use: [ MiniCssExtractPlugin.loader, 'css-loader', { loader: 'postcss-loader', options: { ident: 'postcss', plugins: () => [require('postcss-preset-env')], }, }, ], }, ], }, }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20package.json
"browserslist": { "development": [ "last 1 chrome version", "last 1 firefox version", "last 1 safari version" ], "production": [ ">0.2%", "not dead", "not op_mini all" ] }
1
2
3
4
5
6
7
8
9
10
11
12
# 4. 压缩 CSS
const OptimizeCssAssetsWebpackPlugin = require('optimize-css-assets-webpack-plugin')
module.exports = {
plugins: [new OptimizeCssAssetsWebpackPlugin()],
}
2
3
4
5
# 5. eslint 语法检查
webpack.config.js
// eslint-config-airbnb(React) // eslint-config-airbnb-base(not React) module.exports = { module: { rules: [ { test: /\.js$/, loader: 'eslint-loader', // 优先执行 enforce: 'pre', exclude: /node_modules/, options: { fix: true, }, }, ], }, }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19package.json
"eslintConfig": { // 继承 airbnb-base "extends": "airbnb-base", "env": { // 支持浏览器全局变量 "browser": true } }
1
2
3
4
5
6
7
8
# 6. HMR 热模块替换
作用:重新打包变换的模块,提升构建速度。
- 样式文件:style-loader 内部实现了 HMR 功能。
- js 文件:默认不能使用 HMR 功能。
- html 文件:单页面只有一个 html,不需要实现 HMR 功能。
module.exports = {
// 开启 HMR 功能会导致html热更新失效,在 entry 引入 html 文件即可解决
entry: [
resolve(__dirname, './src/index.js'),
resolve(__dirname, './src/index.html'),
],
devServer: {
// 开启 HMR
hot: true,
},
}
2
3
4
5
6
7
8
9
10
11
12
# 7. source-map
作用:提供源代码与构建后代码的映射,方便追踪调试。
[inline- | hidden- | eval-][nosources-][cheap- [module- ]]source-map
外部:会单独生成.map 文件;内联:嵌套在 js 文件中。内联构建速度相对更快。
- source-map
外部
:提示错误信息与源代码错误位置。 - inline-source-map
内联
(只生成一个内联 source-map):与 source-map 一致。 - hidden-source-map
外部
:提示错误信息与构建后代码错误位置,无源代码错误位置。 - eval-source-map
内联
(每一个文件都生成对应的 source-map):与 source-map 一致。 - nosources-source-map
外部
:提示错误信息,无源代码和构建后代码信息。 - cheap-source-map
外部
:与 source-map 一致,但错误位置只精确到行,不包含 loader 的 source-map。 - cheap-module-source-map
外部
:与 cheap-source-map 一致,但包含 loader 的 source-map,module 会将 loader 的 sourcemap 简化为精确到行。
- source-map
开发环境:速度快,好调试。
- 速度(eval > inline > cheap > ...)。
- eval-cheap-source-map
- eval-source-map
- 调试。
- source-map
- cheap-module-source-map
- cheap-source-map
- 综上。
- eval-source-map
- eval-cheap-module-source-map
生产环境:隐藏源代码?好调试?因内联会增大打包体积,生产环境不考虑内联。
- source-map
- cheap-source-map
module.exports = {
devtool: 'source-map',
}
2
3
# 8. 缓存
babel 缓存:
cacheDirectory: true
。HardSourceWebpackPlugin:为模块提供中间缓存,缓存默认的存放路径是: node_modules/.cache/hard-source。首次构建时间没有太大变化,但是第二次开始,构建时间大约可以节约 80%。
文件资源缓存:打包文件名称加 hash(推荐使用 contenthash)。
- hash:根据 webpack 构建时生成的唯一 hash 值。
- chunkhash:根据 chunk 生成 hash 值,同一个 chunk 的 hash 值一致。
- contenthash:根据文件内容生成 hash 值。
# 9. tree-sharking
作用:去除无用的代码,减小代码体积。
开启条件:1. 使用 ESM、2. 生产环境。
webpack 版本差异,可能会将 css 文件去除。可在 package.json 中配置副作用文件:
"sideEffects: [ "*.css", "*.less" ]"
。
# 10. code split
作用:代码分割,按需加载。
多入口:应用于多页面。
module.exports = { // 多入口 entry: { index: resolve(__dirname, './src/index.js'), home: resolve(__dirname, './src/home.js'), }, // 出口 output: { // 打包后的文件夹路径 path: resolve(__dirname, 'build'), // 打包后文件的名字 filename: 'js/[name].[contenthash:10].js', }, }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15optimization。
module.exports = { // 单入口 entry: resolve(__dirname, './src/index.js'), /* // 多入口 entry: { index: resolve(__dirname, './src/index.js'), home: resolve(__dirname, './src/home.js'), }, */ // 出口 output: { // 打包后的文件夹路径 path: resolve(__dirname, 'build'), // 打包后文件的名字 filename: 'js/[contenthash:10].js', }, // 1. 将 node_modules 分别打包 // 2. 多入口使用相同文件时,只打包一个 chunk optimization: { splitChunks: { chunks: 'all', // 选择哪些 chunk 进行优化 minSize: 30000, // 生成 chunk 的最小体积 minChunks: 1, // 拆分前必须共享模块的最小 chunks 数 maxAsyncRequests: 30, // 限制异步模块最大并行数(import()) maxInitialRequests: 30, // 限制入口拆分数量 automaticNameDelimiter: '~', // 文件名称连接符 cacheGroups: { react: { name: 'react', // chunk 名称 test: /[\\/]node_modules[\\/](react|react-dom)[\\/]/, // 匹配条件 priority: 10, // 优先级 }, vendors: { name: 'vendors', test: /[\\/]node_modules[\\/]/, priority: -10, }, commons: { name: 'commons', minChunks: 2, priority: -20, }, }, }, // 将引用模块的 hash 单独打包(解决出口 chunkFilename 配置文件命名缓存失效) runtimeChunk: { name: entrypoint => `runtime-${entrypoint.name}`, }, }, }
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
53import 动态导入。
const home = await import(/* webpackChunkName: 'home' */ './home')
1
# 12. 懒加载和预加载
懒加载:使用时加载。
const home = await import(/* webpackChunkName: 'home' */ './home')
1预加载:等其他资源加载完后加载,使用时再从缓存中读取。
const home = await import( /* webpackChunkName: 'home', webpackPrefetch: true */ './home' )
1
2
3
# 13. PWA
作用:渐进式网络开发应用程序(离线可访问)。
webpack.config.js
const WorkboxWebpackPlugin = require('workbox-webpack-plugin') module.exports = { plugins: [ new WorkboxWebpackPlugin.GenerateSW({ // 清除旧 SW clientsClaim: true, // 快速启动 skipWaiting: true, }), ], }
1
2
3
4
5
6
7
8
9
10
11
12index.js
if ('serviceWorker' in navigator) window.addEventListener('load', () => navigator.serviceworker.register('/service-worker.js') )
1
2
3
4
# 14. 多进程打包
作用:提升打包耗时程序的速度,主要针对 babel loader 优化。
- 进程启动大概为 600ms,进程通信也有开销。
- 只有工作耗时长的程序,才需要多进程打包。
module.exports = {
module: {
rules: [
// babel
{
test: /\.js$/,
exclude: /node_modules/,
use: [
'thread-loader',
{
loader: 'babel-loader',
options: {
presets: [
'@babel/preset-env',
{
// 按需加载
useBuiltIns: 'usage',
// 指定 core-js 版本
corejs: {
version: 3,
},
// 指定兼容版本
targets: {
chrome: '60',
firefox: '60',
ie: '9',
safari: '10',
edge: '17',
},
},
],
// 开启 babel 缓存
cacheDirectory: true,
},
},
],
},
],
},
}
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
# 15. externals
- 作用:指定一些第三方库不进行打包,自行配置 CDN 资源。
module.exports = {
externals: {
vue: 'Vue',
},
}
2
3
4
5
# 16. dll
作用:指定一些第三方库单独打包。
webpack.config.js
const webpack = require('webpack') const AddAssetHtmlWebpackPlugin = require('add-assets-html-webpack-plugin') module.exports = { mode: 'production', entry: resolve(__dirname, './src/index.js'), output: { filename: 'js/[contenthash:10].js', path: resolve(__dirname, 'build'), }, plugins: [ // 通知 webpack 不打包的库,同时更改引入名称 new webpack.DllReferencePlugin({ manifest: resolve(__dirname, 'dll/manifest.json'), }), // 将文件打包输出,并在 html 自动引入 new AddAssetHtmlWebpackPlugin({ filepath: resolve(__dirname, 'dll/vue.js'), }), ], }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24webpack.dll.js
const webpack = require('webpack') module.exports = { mode: 'production', entry: { // [name]: 包名 vue: 'Vue', }, output: { filename: '[name].js', path: resolve(__dirname, 'dll'), // 打包后向外暴露的名称 library: '[name]_[hash]', }, plugins: [ // 打包成 manifest.json 文件,提供映射信息 new webpack.DllPlugin({ // 映射库暴露的名称 name: '[name]_[hash]', // 输出文件路径 path: resolve(__dirname, 'dll/manifest.json'), }), ], }
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
# 17. 批量导出文件、组件等
- CJS 与 ESM 不能混用,webpack 打包会报引用错误。
const files = require.context('.', false, /.js$/)
const hooks = {}
files.keys().forEach(key => {
if (key === './index.js') return
Object.assign(hooks, {
[key.replace(/^\.\/(.*?)\.js$/, '$1')]: files(key).default,
})
})
// CJS导出(webpack5 以下,无 tree-sharking 哟!)
module.exports = hooks
// 使用
const { useMatrix } = require('./hooks')
// ESM导出
export default hooks
// 使用
import hooks from './hooks'
const { useMatrix } = hooks
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19