【webpack4系列】webpack构建速度和体积优化策略(五)
速度分析:使用 speed-measure-webpack-plugin
使用speed-measure-webpack-plugin
插件。
官网地址:https://github.com/stephencookdev/speed-measure-webpack-plugin#readme
示例效果:
安装:
npm i speed-measure-webpack-plugin -D
使用:
const SpeedMeasurePlugin = require("speed-measure-webpack-plugin");
const smp = new SpeedMeasurePlugin();
module.exports = smp.wrap({
// 其他省略
plugins: [
new MyPlugin(), new MyOtherPlugin()
]
});
速度分析插件作用:
- 分析整个打包总耗时
- 每个插件和loader的耗时情况
体积分析:使用webpack-bundle-analyzer
示例代码:
const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin;
module.exports = {
plugins: [
new BundleAnalyzerPlugin()
]
}
安装:
npm i webpack-bundle-analyzer -D
可以分析哪些问题?
- 依赖的第三方模块文件大小
- 业务里面的组件代码大小
使用高版本的 webpack 和 Node.js
高版本的webpack和node.js降低了构建时间。
使用webpack4的优化原因:
- V8 带来的优化(for of 替代 forEach、Map 和 Set 替代 Object、includes 替代 indexOf)
- 默认使用更快的 md4 hash 算法
- webpacks AST 可以直接从 loader 传递给 AST,减少解析时间
- 使用字符串方法替代正则表达式
多进程/多实例构建
资源并行解析可选方案
- parallel-webpack
- HappyPack
- thread-loader
使用 HappyPack 解析资源
原理:每次 webapck 解析一个模块,HappyPack 会将它及它的依赖分配给 worker 线程中。
安装:
npm i happypack -D
使用示例:
const HappyPack = require('happypack');
exports.module = {
rules: [
{
test: /.js$/,
// 1) replace your original list of loaders with "happypack/loader":
// loaders: [ 'babel-loader?presets[]=es2015' ],
use: 'happypack/loader',
include: [ /* ... */ ],
exclude: [ /* ... */ ]
}
]
};
exports.plugins = [
// 2) create the plugin:
new HappyPack({
// 3) re-add the loaders you replaced above in #1:
loaders: [ 'babel-loader?presets[]=es2015' ]
})
];
使用 thread-loader 解析资源
由于webpack4.x目前只能安装thread-loader@3.0.0
版本,3.0.0以后的版本需要webpack5.x。
原理:每次 webpack 解析一个模块,thread-loader 会将它及它的依赖分配给 worker 线程中
npm i thread-loader@3.0.0 -D
配置:
module: {
rules: [
{
test: /.js$/,
use: [
{
loader: "thread-loader",
options: {
workers: 3
}
},
"babel-loader"
]
}
]
}
多进程并行压缩代码
方法一:使用 parallel-uglify-plugin 插件
const ParalleUglifyPlugin = require("webpack-parallel-uglify-plugin");
module.exports = {
plugins: [
new ParalleluglifyPlugin({
uglifyjs: {
output: {
beautify: false,
comments: false
},
compress: {
warnings: false,
drop_console: true,
collapse_vars: true,
reduce_vars: true
}
}
})
]
};
方法二:uglifyjs-webpack-plugin 开启 parallel 参数
建议webpack3.x使用该插件。
const UglifyJsPlugin = require("uglifyjs-webpack-plugin");
module.exports = {
plugins: [
new UglifyJsPlugin({
uglifyoptions: {
warnings: false,
parse: {},
compress: {},
mangle: true,
output: null,
toplevel: false,
nameCache: null,
ie8: false,
keep_fnames: false
},
parallel: true
})
]
};
方法三:terser-webpack-plugin 开启 parallel 参数
webpack4.x及以上建议使用terser-webpack-plugin插件
注:Using Webpack v4, you have to install terser-webpack-plugin v4.
安装:
npm i terser-webpack-plugin@4 -D
配置:
const TerserPlugin = require("terser-webpack-plugin");
module.exports = {
optimization: {
minimizer: [
new TerserPlugin({
parallel: true
})
]
}
};
进一步分包:预编译资源模块
分包:设置 Externals
思路:将 vue、react、react-dom 等基础包通过cdn 引入,不打入 bundle 中。
方法:使用html-webpack-externals-plugin
。
安装:
npm i html-webpack-externals-plugin -D
配置:
const HtmlWebpackExternalsPlugin = require('html-webpack-externals-plugin');
module.exports = {
plugins: [
new HtmlWebpackExternalsPlugin({
externals: [
{
module: "react",
entry: "https://unpkg.com/react@18.2.0/umd/react.production.min.js",
global: "React"
},
{
module: "react-dom",
entry: "https://unpkg.com/react-dom@18/umd/react-dom.production.min.js",
global: "ReactDOM"
}
]
})
]
};
进一步分包:预编译资源模块
思路:将react、react-dom基础包和业务基础包打包成一个文件
方法:使用DLLPlugin
进行分包,DllReferencePlugin
对 manifest.json
引用
- DllPlugin:负责抽离第三方库,形成第三方动态库dll。
- DllReferencePlugin:负责引用第三方库。
使用 DLLPlugin 进行分包
新建一个webpack.dll.js:
const path = require("path");
const webpack = require("webpack");
module.exports = {
entry: {
library: ["react", "react-dom"]
},
output: {
filename: "[name].dll.js",
path: path.join(__dirname, "build/library"),
library: "[name]_[hash:8]" // 保持与webpack.DllPlugin中name一致
},
plugins: [
new webpack.DllPlugin({
name: "[name]_[hash:8]", // 保持与output.library中名称一致
path: path.join(__dirname, "build/library/[name].json")
})
]
};
在package.json中添加命令:
"scripts": {
"dll": "webpack --config webpack.dll.js"
}
最后执行npm run dll
,结果在工程根目录下有如下文件:
- build
- library.dll.js
- library.json
使用 DllReferencePlugin 引用 manifest.json
在webpack.prod.js中插件中配置如下:
plugins: [
new webpack.DllReferencePlugin({
manifest: require("./build/library/library.json")
})
]
当执行npm run build
后其实index.html页面中没有引入library.dll.js文件,我们可以通过安装add-asset-html-webpack-plugin
插件,webpack4.x版本使用add-asset-html-webpack-plugin@3
npm i add-asset-html-webpack-plugin@3 -D
在webpack.prod.js中插件中配置如下:
plugins: [
new webpack.DllReferencePlugin({
manifest: require("./build/library/library.json")
}),
new AddAssetHtmlPlugin({
filepath: path.resolve("./build/library", "library.dll.js")
})
]
起作用 就是把build/library/library.dll.js拷贝到编译后的dist文件夹下,并且通过script标签引入到index.html中。
最终页面生成的效果:
<!DOCTYPE html>
<html lang="en">
<meta charset="utf-8">
<head>
<title>Document</title>
<link href="search_42937580.css" rel="stylesheet">
</head>
<body>
<div id="root"></div>
<script src="library.dll.js"></script>
<script src="search_c1f12d25.js"></script>
</body>
</html>
add-asset-html-webpack-plugin参考地址:https://www.npmjs.com/package/add-asset-html-webpack-plugin/v/3.2.2?activeTab=versions
充分利用缓存提升二次构建速度
目的:提升二次构建速度。
缓存思路:
- babel-loader 开启缓存
- terser-webpack-plugin 开启缓存
- 使用 cache-loader 或者 hard-source-webpack-plugin
开启了对应方式的缓存,会在node_modules目录下的cache文件夹看到缓存的内容,如下结构:
- node_modules
- .cache
- babel-loader
- hard-source
- terser-webpack-plugin
- .cache
1、babel-loader 开启缓存
rules: [
{
test: /.js$/,
use: ["babel-loader?cacheDirectory=true"
]
}
]
如果是使用的HappyPack,配置如下:
new HappyPack({
loaders: ["babel-loader?cacheDirectory=true"]
})
2、terser-webpack-plugin 开启缓存
optimization: {
minimizer: [
new TerserPlugin({
parallel: true,
cache: true
})
]
}
3、hard-source-webpack-plugin开启缓存
安装:
npm i hard-source-webpack-plugin -D
配置:
const HardSourceWebpackPlugin = require('hard-source-webpack-plugin');
module.exports = {
plugins: [
new HardSourceWebpackPlugin()
]
}
在webpack4.x中会报错。
缩小构建目标与减少文件搜索范围
缩小构建目标
目的:尽可能的少构建模块
比如 babel-loader 不解析 node_modules
rules: [
{
test: /.js$/,
include: [path.resolve(__dirname, "src")],
use: [
"babel-loader"
]
}
当然也可以使用exclude,来缩小构建范围。
减少文件搜索范围
- 优化 resolve.modules 配置(减少模块搜索层级)
- 优化 resolve.mainFields 配置
- 优化 resolve.extensions 配置
- 合理使用 alias
示例代码:
resolve: {
alias: {
"@": path.resolve(__dirname, "src"),
react: path.resolve(__dirname, "./node_modules/react/umd/react.production.min.js"),
"react-dom": path.resolve(__dirname, "./node_modules/react-dom/umd/react-dom.production.min.js")
extensions: [".js"],
mainFields: ["main"]
},
使用Tree Shaking擦除无用的JavaScript和CSS
概念:1 个模块可能有多个方法,只要其中的某个方法使用到了,则整个文件都会被打到
bundle 里面去,tree shaking 就是只把用到的方法打入 bundle ,没用到的方法会在
uglify 阶段被擦除掉。
使用:webpack 默认支持,在 .babelrc 里设置 modules: false 即可
- production mode的情况下默认开启
要求:必须是 ES6 的语法,CJS 的方式不支持
无用的 CSS 如何删除掉?
- PurifyCSS: 遍历代码,识别已经用到的 CSS class
- uncss: HTML 需要通过 jsdom加载,所有的样式通过PostCSS解析,通过document.querySelector 来识别在 html 文件里面不存在的选择器
在 webpack 中如何使用 PurifyCSS?
PurifyCSS官网已经不再维护了,使用 purgecss-webpack-plugin
这个插件和 mini-css-extract-plugin
配合使用。
安装purgecss-webpack-plugin
插件:
npm i purgecss-webpack-plugin@4 -D
配置:
const MiniCssExtractPlugin = require('mini-css-extract-plugin')
const PurgeCSSPlugin = require('purgecss-webpack-plugin')
const PATHS = {
src: path.join(__dirname, 'src')
}
module.exports = {
// 其他省略
module: {
rules: [
{
test: /\.css$/,
use: [
MiniCssExtractPlugin.loader,
"css-loader"
]
}
]
},
plugins: [
new MiniCssExtractPlugin({
filename: "[name].css",
}),
new PurgeCSSPlugin({
paths: glob.sync(`${PATHS.src}/**/*`, { nodir: true }),
}),
]
}
使用webpack进行图片压缩(压缩有问题)
基于 Node 库的 imagemin
或者 tinypng API
。
使用:配置 image-webpack-loader
。
安装image-webpack-loader
:
npm i image-webpack-loader@6 -D
其中imagemin-mozjpeg
对node版本有要求:
"engines": {
"node": "^12.20.0 || ^14.13.1 || >=16.0.0"
}
配置:
{
test: /.(png|jpe?g|gif)$/,
use: [
{
loader: "file-loader",
options: { name: "[name]_[hash:8].[ext]" }
},
{
loader: "image-webpack-loader",
options: {
mozjpeg: {
progressive: true,
quality: 65
},
// optipng.enabled: false will disable optipng
optipng: {
enabled: false
},
pngquant: {
quality: [0.65, 0.9],
speed: 4
},
gifsicle: {
interlaced: false
},
// the webp option will enable WEBP
webp: {
quality: 75
}
}
}
]
}
Imagemin的优点分析:
- 有很多定制选项
- 可以引入更多第三方优化插件,例如pngquant
- 可以处理多种图片格式
Imagemin的压缩原理:
- pngquant: 是一款PNG压缩器,通过将图像转换为具有alpha通道(通常比24/32位PNG
- 文件小60-80%)的更高效的8位PNG格式,可显著减小文件大小。
- pngcrush:其主要目的是通过尝试不同的压缩级别和PNG过滤方法来降低PNG IDAT数据
- 流的大小。
- optipng:其设计灵感来自于pngcrush。optipng可将图像文件重新压缩为更小尺寸,而不
- 会丢失任何信息。
- tinypng:也是将24位png文件转化为更小有索引的8位图片,同时所有非必要的metadata
- 也会被剥离掉
构建体积优化:动态 Polyfill
直接把babel-polyfill打包到工程,一般会很大。
Polyfill Service原理:识别 User Agent,下发不同的 Polyfill
如何使用动态 Polyfill service?
polyfill.io 官方提供的服务
<script src="https://cdn.polyfill.io/v3/polyfill.js"></script>