统计分析
要会优化必须先学会统计分析。
初级分析
使用 webpack 内置的 stats构建统计信息。
package.json 中使用 stats,在scripts选项中配置:
"build:stats": "webpack --config webpack.prod.js --json > stats.json"
stats.json中的内容:
速度分析
使用 speed-measure-webpack-plugin可以看到每个 loader 和插件执行耗时。
安装:
yarn add speed-measure-webpack-plugin --dev
配置
// 引入和实例化
const SpeedMeasureWebpackPlugin = require("speed-measure-webpack-plugin");
const smp = new SpeedMeasureWebpackPlugin();
// 使用smp包裹module.exports
module.exports = smp.wrap({
...loader,
...plugins
});
loader 和插件速度分析:
文件大小分析
webpack-bundle-analyzer 分析体积 安装:
yarn add webpack-bundle-analyzer --dev
配置:
const WebpackBundleAnalyzer = require("webpack-bundle-analyzer")
.BundleAnalyzerPlugin;
// 在插件里面配置
plugins:[
new WebpackBundleAnalyzer(),
]
构建完成后会在 8888 端口展示大小:
可以分析的问题:
- 依赖的第三方模块文件大小;
- 业务里面的组件代码大小。
使用高版本的 webpack
优化原因:
- V8 带来的优化(for of 替代 forEach、Map 和 Set 替代 Object、includes 替代 indexOf);
- 默认使用更快的 md4 hash 算法;
- webpacks AST 可以直接从 loader 传递给 AST,减少解析时间;
- 使用字符串方法替代正则表达式。
使用 thread-loader 多线程解析资源:
原理:每次 webpack 解析一个模块,thread-loader 会将它及它的依赖分配给 worker 线程中。 安装:
yarn add thread-loader --dev
以解析js为例,配置:
{
test: /\.js$/,
exclude: /node_modules/,
// "eslint-loader"
use: [{ loader: "thread-loader", options: { workers: 3 } }],
use: ["babel-loader"],
},
并行压缩
使用terser-webpack-plugin并开启 parallel 参数。
安装:
const TerserWebpackPlugin = require("terser-webpack-plugin");
在optimization里面配置:
optimization: {
minimizer: [
new TerserWebpackPlugin({
parallel: 4, // 或者true
cache:true
}),
],
},
使用 DLLPlugin 进行分包
将 react、react-dom、redux、react-redux等等的 基础包和业务基础包打包成一个文件。
方法:使用 DLLPlugin 进行分包,DllReferencePlugin 对 manifest.json 引用。
配置webpack.dll.js:
const path = require("path");
const webpack = require("webpack");
const { CleanWebpackPlugin } = require("clean-webpack-plugin");
module.exports = {
// 要抽离成dll文件的模块
entry: { library: ["react", "react-dom"] },
output: {
// 文件输出的目录
path: path.join(__dirname, "build/library"),
// 生成的dll文件
filename: "[name].dll.js",
// 暴露出的全局变量
output: "[name]_[chunkhash]",
},
plugins: [
new CleanWebpackPlugin(),
new webpack.DllPlugin({
// json文件的存放位置
path: path.join(__dirname, "build/library/[name].json"),
// dll文件的全局变量名,要跟output.output保持一致
name: "[name]_[chunkhash]",
}),
],
};
在package.json中配置webpack.dll.js文件的启动命令:
"scripts": {
"dll": "webpack --config webpack.dll.js"
},
运行Webpack,会输出两个文件:
library.json文件内容:
Webpack将每个库都进行了编号索引,之后的dll user可以读取这个文件,直接用id来引用。
webpack.prod.js中plugins的配置:
new webpack.DllReferencePlugin({
manifest: require("./build/library/library.json"),
}),
启动之后index文件明显变小:
在html模板文件中设置引入dll文件:
缓存
目的:提升二次构建速度
缓存思路: 目的:提升二次构建速度
- babel-loader 开启缓存
- terser-webpack-plugin 开启缓存
- 使用 cache-loader 或者 hard-source-webpack-plugin
我建议使用hard-source-webpack-plugin,因为方便,直接在插件引入就行。 安装
yarn add hard-source-webpack-plugin --dev
配置:
const HardSourceWebpackPlugin = require("hard-source-webpack-plugin");
plugins:[
new HardSourceWebpackPlugin(),
]
第一次打包:
第二次打包:
可以看到时间有明显缩短。
缩小构建目标
在打包时,尽可能的少构建模块,比如 babel-loader 不解析 node_modules。
减少文件搜索范围
Resolve
Webpack 在启动后会从配置的入口模块出发找出所有依赖的模块,Resolve 配置 Webpack 如何寻找模块所对应的文件。 Webpack 内置 JavaScript 模块化语法解析功能,默认会采用模块化标准里约定好的规则去寻找,但你也可以根据自己的需要修改默认的规则。
Resolve参数:
- alias配置项通过别名来把原导入路径映射成一个新的导入路径。例如使用以下配置:
resolve:{
alias:{
components: './src/components/'
}
}
当你通过 import Button from 'components/button 导入时,实际上被 alias 等价替换成了 import Button from './src/components/button' 。 2. mainFields,Webpack 会根据 mainFields 的配置去决定优先采用那份代码, mainFields 默认如下:
mainFields: ['browser', 'main']
- extensions:在导入语句没带文件后缀时,Webpack 会自动带上后缀后去尝试访问文件是否存在。 resolve.extensions 用于配置在尝试过程中用到的后缀列表,默认是:
extensions: ['.js', '.json']
- modules:resolve.modules用于配置 Webpack 去哪些目录下寻找第三方模块。默认值是['node_modules'],含义是先去当前目录下的./node_modules目录下去找想找的模块,如果没找到就去上一级目录../node_modules中找。
综合配置:
resolve: {
alias: {
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"],
},
删除无用的 CSS
使用 PurifyCSS,遍历代码,识别已经用到的 CSS class。使用 purgecss-webpack-plugin和 mini-css-extract-plugin 配合使用。它会删除没有用到的css class
安装:
yarn add purgecss-webpack-plugin --dev
配置:
const PurgecssPlugin = require("purgecss-webpack-plugin");
plugins:[
// 使用glob.sync遍历指定目录下的文件,只匹配文件,不匹配目录
new PurgecssPlugin({
paths: glob.sync(`${path.join(__dirname, "src")}/**/*`, { nodir: true }),
}),
]
图片压缩
使用image-webpack-loader,url-loader 和 image-webpack-loader 不能一起使用,否则会导致图片出不来。一定要先写 'file-loader' 才能使用 'image-webpack-loader'有各种配置,可以调整你要压缩后图片的质量。
提示:如果使用了 webp 会大大减少体积,但是ios并不支持这个格式,会导致在ios上看不见图片。
{
test: /\.(png|jpe?g|gif|svg)(\?.*)?$/,
use: [
{
loader: 'file-loader',
options: {
name: '[name].[hash:7].[ext]',
outputPath: 'mobile/img'
}
},
{
loader: 'image-webpack-loader',
options: {
mozjpeg: {
progressive: true,
quality: 50
},
// optipng.enabled: false will disable optipng
optipng: {
enabled: false,
},
pngquant: {
quality: [0.5, 0.65],
speed: 4
},
gifsicle: {
interlaced: false,
},
//ios不支持
// webp: {
// quality: 100
// }
}
}
]
}
动态 Polyfill
Polyfill Service原理:
识别 User Agent,下发不同的 Polyfill,只返回用户需要的Polyfill。
在html模板引入动态Polyfill。
<script crossorigin="anonymous" src="https://polyfill.io/v3/polyfill.min.js"></script>
体积优化策略总结
-
Scope Hoisting
-
Tree-shaking
-
图片压缩
-
动态 Polyfill