webpack 生产环境优化
# 生产环境优化
# 优化打包速度
# oneOf
当 webpack
所配置的 loader
处理文件时,会依次的过一遍所有的 loader
,即使找到了所匹配的 loader
也不停止,任然继续向后匹配直至结束。因此使用 oneOf
包裹 loader
。当匹配到一个对应的 loader
后就停止向后匹配,加快了打包后加速度。注意 :不能有两个 loader
处理同一种文件 。如果出现两个,就从 oneOf
中提出去一个
配置示例
// webpack.config.js 中的 module 配置
module: {
rules: [
{
// 以下loader只会匹配一个
// 注意:不能有两个配置处理同一种类型文件
oneOf: [
{
test: /\.css$/,
use: [...commonCssLoader]
},
{
test: /\.less$/,
use: [...commonCssLoader, 'less-loader']
},
// js babel-loader 处理
// ...
// ..... 其他配置....
]
},
{ // 由于 oneOf 中有处理 js 的 loader ,于是提出来。
test: /\.js$/,
exclude: /node_modules/,
// 优先执行
enforce: 'pre',
loader: 'eslint-loader',
options: {
fix: 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
# babel 缓存处理
为 babel-loader
配置 cacheDirectory : true
开启 babel
缓存,将兼容性处理代码保存起来,下一次使用的时候直接从缓存读取,加快语法转换速度。
// babel-loader 的配置
{
test: /\.js$/,
exclude: /node_modules/,
loader: 'babel-loader',
options: {
presets: [
[
'@babel/preset-env',
{
useBuiltIns: 'usage',
corejs: { version: 3 },
targets: {
chrome: '60',
firefox: '50'
}
}
]
],
// 开启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
# 多进程打包
在比较大的项目中开启多进程打包,可以大大加快打包构建速度。不推荐在较小的项目中使用,进程启动大概为约 600ms
,进程通信也有开销,只有工作消耗时间比较长,才需要多进程打包。
// webpack.config.js 部分配置
{
test: /\.js$/,
exclude: /node_modules/,
use: [
{
loader: 'thread-loader',
options: {
workers: 2 // 创建 2 个进承对文件处理
}
},
{
loader: 'babel-loader',
options: {
presets: [
[
'@babel/preset-env',
{
useBuiltIns: 'usage',
corejs: { version: 3 },
targets: {
chrome: '60',
firefox: '50'
}
}
]
],
}
}
]
},
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
# externals
可以设置一些模块禁止被打包进入 bundle.js
文件,减小打包文件的体积提升速度。例如:拒绝打包 node_modules
中的模块,首先安装:webpack-node-externals
,然后在 externals
中配置使用。
// webpack.config.js 部分配置
const { resolve } = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const nodeExternals = require('webpack-node-externals');
// 拒绝打包 jQuery
externals: {
//拒绝 jQuery 被打包进来,需要手动引入在线cd,或者本地jquery库
jquery: 'jQuery' // 文件中使用的引用名 :node_modules中库的包名
}
// 拒绝打包 node_modules 中的模块
externals: [nodeExternals()]
2
3
4
5
6
7
8
9
10
11
12
13
14
# dll (动态链接库 )
使用
dll
可以对某些第三方库jquery
、react
、vue
... 进行单独打包,当项目构建打包时就不需要反复打包那些巨大的第三方包,加快了打包构建速度。
首先单独配置一个 webpack.dll.js
文件,用来给需要单独打包的第三方库进行处理。在该文件中使用 webpack.DllPlugin
打包生成一个 manifest.json
提供单独打包的库的映射 (明确此库不需要打包,并得到打包后向外暴露的库名称,在项目中使用该库的名称也会在项目打包时通过映射改为此名称)
在项目打包前先运行如下代码对第三方库进行打包:
webpack --config webpack.dll.js
webpack.dll.js
配置文件
// webpack.dll.js
const { resolve } = require('path');
const webpack = require('webpack');
module.exports = {
entry: {
// 最终打包生成的 [name] --> jquery
// ['jquery'] --> 要打包的库是jquery
jquery: ['jquery'],
},
output: {
filename: '[name].js',
path: resolve(__dirname, 'dll'),
library: '[name]_[hash]' // 打包的库里面向外暴露出去的内容叫什么名字
},
plugins: [
// 打包生成一个 manifest.json --> 提供和jquery映射 (明确jQuery 不需要打包,并得到第一次打包后向外暴露的库名称)
new webpack.DllPlugin({
name: '[name]_[hash]', // 映射库的暴露的内容名称
path: resolve(__dirname, 'dll/manifest.json') // 输出文件路径
})
],
mode: 'production'
};
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
文件的配置
使用 webpack.DllReferencePlugin
配置告诉 webpack
哪些库不参与打包,同时使用时的名称也得变。(靠的就是前面打包生成的 manifest.json
文件)
// webpack.config.js 部分配置
const { resolve } = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const webpack = require('webpack');
const AddAssetHtmlWebpackPlugin = require('add-asset-html-webpack-plugin');
module.exports = {
entry: './src/index.js',
output: {
filename: 'built.js',
path: resolve(__dirname, 'build')
},
plugins: [
new HtmlWebpackPlugin({
template: './src/index.html'
}),
// 告诉webpack哪些库不参与打包,同时使用时的名称也得变~
new webpack.DllReferencePlugin({
manifest: resolve(__dirname, 'dll/manifest.json')
}),
// 将某个文件打包输出去,!并在html中自动引入该资源,!
new AddAssetHtmlWebpackPlugin({
filepath: resolve(__dirname, 'dll/jquery.js')
})
],
mode: 'production'
}
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
# 优化运行的性能
# 文件资源缓存
每一次加载都页面所有资源都会从新加载,为减小服务器开销、加快客户端的加载速度就必须对文件进行缓存。当服务器文件发生改变时客户端就会从服务器加载资源。那么如何标识问价改变呢?使用文章名称的改变来标识文件的改变。通过在文件名中加入一段 hash
值来实实现,一共有三种 hash
值
hash :每次 wepack
构建时会生成一个唯一的 hash
值,将此 hash 值添加在输出文件的文件名中。问题:当一个文件修改,重新打包构建项目产生新的 hash
值,但是所有文件的 文件名都变了,于是所有文件都需要重新加载,缓存做的不完美。
// webpack.config.js 部分配置
output: {
filename: 'js/built.[hash:10].js',
path: resolve(__dirname, 'build')
}
plugins: [
new MiniCssExtractPlugin({
filename: 'css/built.[hash:10].css'
})
]
2
3
4
5
6
7
8
9
10
11
chunkhash :chunk
是一个入口文件下的所有引用文件打包后的集合。根据 chunk
生成的 hash
值,如果打包来源于同一个 chunk
,那么 hash
值就一样。问题:js
和 css
的 hash
值还是一样的。因为 css
是在 js
中被引入的,所以同属于一个 chunk
。任会出现第一种方式的情况。
// webpack.config.js 部分配置
output: {
filename: 'js/built.[chunkhash:10].js',
path: resolve(__dirname, 'build')
}
plugins: [
new MiniCssExtractPlugin({
filename: 'css/built.[chunkhash:10].css'
})
]
2
3
4
5
6
7
8
9
10
11
contenthash :根据文件的内容生成 hash
值,不同文件 hash
值一定不一样 。这样一来每当文件内容修改,对应文件的 hash
值就会改变,在客户端加载时就会只加载跟新过的文件。
// webpack.config.js 部分配置
output: {
filename: 'js/built.[contenthash:10].js',
path: resolve(__dirname, 'build')
}
plugins: [
new MiniCssExtractPlugin({
filename: 'css/built.[contenthash:10].css'
})
]
2
3
4
5
6
7
8
9
10
11
# tree shaking
Tree shaking
顾名思义“摇树”,将代码数中的没有使用的代码“摇掉”,达到减少代码体积的作用。使用前提:必须使用 ES6
模块化、开启 production
环境 后就可自动开启。
注意
不同版本可能会有差异
在 package.json
中配置
"sideEffects": false
打包后的所有代码都是有用的(未构建的项目所有的文件都可以进行 tree shaking
),由此可能会把 css / @babel / polyfill
等等文件干掉,起到副作用。通过设置 "sideEffects": ["*.css", "*.less"] //不希望被干掉
排除对样式文件进行 tree shaking
。
# code split
多入口文件代码分割
设置几个入口文件,就会打包多少个 chunk
出来,达到代码分割效果。但多入口文件的情况比较少见,现在大多数应用都是单页面应用。
const { resolve } = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
module.exports = {
// 单入口
// entry: './src/js/index.js',
entry: {
// 多入口:有一个入口,最终输出就有一个bundle
index: './src/js/index.js',
test: './src/js/test.js'
},
output: {
// [name]:取文件名,为入口处指定的名称
filename: 'js/[name].[contenthash:10].js',
path: resolve(__dirname, 'build')
},
plugins: [
new HtmlWebpackPlugin({
template: './src/index.html',
minify: {
collapseWhitespace: true,
removeComments: true
}
})
],
mode: 'production'
}
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
optimization
可以将 node_modules
中模块单独打包一个 chunk
最终输出。还可以在多入口文件的配置的 chunk
中,自动分析有没有公共的文件。如果有会打包成单独一个 chunk
避免重复打包 ,减小打包文件的体积。
module.exports = {
mode: 'production'
entry: // ... ,
output: // ...,
module: // ...,
optimization: {
splitChunks: {
chunks: 'all'
}
},
};
2
3
4
5
6
7
8
9
10
11
12
通过 js 进行代码分割
通过 js
代码,import
动态导入语法能将某个文件单独打包成一个 chunk
。通过添加 /* webpackChunkName: 'test' */
注释给打包的 chunk
命名。
function sum(...args) {
return args.reduce((p, c) => p + c, 0);
}
//取名
import(/* webpackChunkName: 'test' */'./test')
.then(({ mul, count }) => {
// 文件加载成功~
// eslint-disable-next-line
console.log(mul(2, 5));
})
.catch(() => {
// eslint-disable-next-line
console.log('文件加载失败~');
});
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# 懒加载/预加载
懒加载:通过在 js
文件中写特定代码,当需要使用某个文件的时候才加载。通过添加 /* webpackPrefetch: true */
注释标注开启预加载。
预加载:当文件需要使用时才加载。预加载:prefetch
等其他资源加载完毕,浏览器空闲了,再偷偷加载资源。兼容性不好 慎用!
document.getElementById('btn').onclick = function() {
import(/* webpackChunkName:'test', webpackPrefetch:true */'./test').then(({ mul }) => {
console.log(mul(4, 5));
});
}
2
3
4
5
# serviceworker
利用缓存可以实现网页的离线访问。需要用到 workbox-webpack-plugin
插件。
配置:
const WorkboxWebpackPlugin = require('workbox-webpack-plugin');
plugins: [
new WorkboxWebpackPlugin.GenerateSW({
/*
1. 帮助 serviceworker 快速启动
2. 删除旧的 serviceworker
生成一个 serviceworker 配置文件~
*/
clientsClaim: true,
skipWaiting: true
})
]
2
3
4
5
6
7
8
9
10
11
12
13