webpack构建速度和体积优化策略转载
前言
对自己性能优化以及webpack优化进行总结,目前文章对webpack进行了描述,后续会对webpack之前的性能进行总结。
整体脑图如下:
初级分析:使用webpack内置的stats
在package.json中使用stats
"scripts": {
"stats": "webpack --json > stats.json"
},
通过npm run stats 即可生产stas.json文件,json文件内容包含了构建的统计信息
速度分析:使用speed-measure-webpack-plugin
使用webpack内置的stas分析,颗粒度比较粗,很难发现文件大小、构建速度的问题所在 使用speed-measure-webpack-plugin可以分析每个loader、plugin的耗时所在
const SpeedMeasurePlugin = require("speed-measure-webpack-plugin");
const **p = new SpeedMeasurePlugin();
const webpackConfig = **p.wrap({
plugins: [new MyPlugin(), new MyOtherPlugin()],
});
体积分析: 使用webpack-bundle-****yzer 分析体积
const BundleAnalyzerPlugin = require('webpack-bundle-****yzer').BundleAnalyzerPlugin;
module.exports = {
plugins: [
new BundleAnalyzerPlugin()
]
}
性能提升的方法(针对构建速度和体积优化)
使用高版本的 webpack 和 Node.js
webpack4刚出来时,和webpack3在推特上做了比较,webpack4的构建时间减低了60%-98%
使用webpack4的原因:
-
V8带来的优化(for of 代替了forEach、Map和set代替了Object、includes代替indexOf)
-
默认使用更快的md4 hash算法 去替代 MD5
-
webpacks AST 可以直接从loader传递给AST, 减少解析时间
-
使用字符串方法替代正则表达式
多进程、多实例:解析构建
可选方案:
- thread-loader(官方)
- parallel-webpack
- HappyPack
使用HappyPack去解析资源 原理:每次webpack解析一个模块,HappyPack会将它及它的依赖分配给worker线程中 webpack - HappyLoader - HappyPlguin (创建线程池) - HappyThreadPool - HappyThread[1...N] (各线程会处理各自负责的模块和依赖) - HappyWorkerChannel[1...N] - HappyWorker[1..N] (HappyPack工作流)
使用thread-loader去解析资源
原理:和HappyPack一致,每次webpack解析一个模块,thread-loader会将它及它的依赖分配给worker线程中
module.exports = {
module: {
rules: [
{
test: /\.js$/,
include: path.resolve('src'),
use: [
'thread-loader',
// your expensive loader (e.g babel-loader)
],
},
],
},
};
如果需要配置three-loader可以通过配置项的形式
use: [
{
loader: 'thread-loader',
// loaders with equal options will share worker pools
options: {
// the number of spawned workers, defaults to (number of cpus - 1) or
// fallback to 1 when require('os').cpus() is undefined
workers: 2,
// number of jobs a worker processes in parallel
// defaults to 20
workerParallelJobs: 50,
// additional node.js arguments
workerNodeArgs: ['--max-old-space-size=1024'],
// Allow to respawn a dead worker pool
// respawning slows down the entire compilation
// and should be set to false for development
poolRespawn: false,
// timeout for killing the worker processes when idle
// defaults to 500 (ms)
// can be set to Infinity for watching builds to keep workers alive
poolTimeout: 2000,
// number of jobs the poll distributes to the workers
// defaults to 200
// decrease of less efficient but more fair distribution
poolParallelJobs: 50,
// name of the pool
// can be used to create different pools with elsewise identical options
name: 'my-pool',
},
},
// your expensive loader (e.g babel-loader)
];
多进程、多实例:并行压缩
方法一: 使用parallel-uglify-plugin插件
方法二:uglifyjs-webpack-plugin开启parallel参数 (webpack4.0以前推荐)
方法三:terser-webpack-plugin 开启parallel参数 (推荐)
module.exports = {
optimization: {
minimize: true,
minimizer: [
new TerserPlugin({
parallel: true, // parallel默认值是当前电脑环境CPU数量的2倍减1
}),
],
},
};
如果你使用的是 webpack v5 或以上版本,你不需要安装这个插件。webpack v5 自带最新的 terser-webpack-plugin。如果使用 webpack v4,则必须安装 terser-webpack-plugin v4 的版本
进一步分包:预编译资源模块
方法一:使用html-webpack-externals-plugin (例如将react,react-dom基础包通过cdn引入,不打入bundle中)
方法二:使用SplitChunksPlugin
方法三:使用DLLPlugin进行分包,DIIReferencePlugin对mainfest.json引用 (官方内置的插件)
一般先创建webpack.dll.js文件对公共基础包、业务基础包进行分包 然后在webpack.config.js通过
webpack.DllReferenctPlugin进行引用
充分利用缓存提升二次构建速度
目的:提升二次构建速度 缓存思路:
- babel-loader 开启缓存
- terser-webpack-plugin开启缓存
- 使用 cache-loader 或者 hard-source-webpack-plugin
缩小构建目标
目的:尽可能的少构建模块 比如babel-loader不解析node_modules
减少文件搜索范围
- 优化resolve.modules 配置 (减少模块搜索层级)
- 优化resolve.mainFields配置
- 优化resolve.extensions配置
- 合理使用alias (自己标记一下,还是不错的这个alias)
使用Tree Shaking 擦除无用的JavaScript和CSS
关于 tree shaking
概念: 1个模块可能有多个方法,只要其中的某个方法使用到了,则整个文件都会被打到bundle里面去,tree shaking就是只把用到的方法打入bundle,没用到的方法会在uglify阶段被擦除掉。
使用:webpack默认支持,在.babelrc里面设置modules:false即可 production mode 的情况下默认开启
要求:必须是ES6语法,CJS的方法不支持
擦除无用的CSS
使用purgecss-webpack-plugin和mini-css-extract-plugin配合使用
const path = require('path');
const glob = require('glob')
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
const SpeedMeasureWebpackPlugin = require('speed-measure-webpack-plugin');
const **p = new SpeedMeasureWebpackPlugin();
const BundleAnalyzerPlugin = require('webpack-bundle-****yzer').BundleAnalyzerPlugin;
const TerserPlugin = require("terser-webpack-plugin");
const webpack = require('webpack');
const PurgeCSSPlugin = require('purgecss-webpack-plugin')
const PATHS = {
src: path.join(__dirname)
}
module.exports = **p.wrap({
// 开发者工具 不需要开发调试
devtool: false,
// 开发模式 不进行代码压缩
mode: 'development',
// 入口文件
entry: './index.js',
output: {
// 输出文件名称
filename: 'bundle.js',
// 输出文件路径
path: path.join(__dirname, './'),
},
module: {
rules: [
{
// 正则匹配后缀名为 .css 的文件
test: /\.css$/,
use: [MiniCssExtractPlugin.loader, 'css-loader'],
}
]
},
plugins: [
new MiniCssExtractPlugin(),
new PurgeCSSPlugin({
paths: glob.sync(`${PATHS.src}/*`, { nodir: true }), // 只支持绝对路径
}),
],
});
使用动态Polyfill服务
使用webpack进行图片压缩
使用image-webpack-loader
体积优化策略总结
- scope Hoisting
- Tree-shaking
- 公共资源分离
- 图片压缩
- 动态polyfill
作者:左耳咚