# webpack构建速度及体积优化
# 初级分析:stats构建统计信息分析
通过webpack添加stats可设置在构建过程中打印统计信息。
亦可通过package.json中使用stats,生成stats.json日志。
{
"scripts":{
"build:stats": "webpack --config webpack.prod.js --json > stats.json"
}
}
亦或通过新建stats.js运行脚本打印统计信息。
const webpack = require('webpack')
const config = require('./webpack.prod')
webpack(config,(err,stats) => {
if(err){
return console.error(err)
}
if(stats.hasErrors()){
return console.error(stats.toString('errors-only'))
}
console.log(stats)
})
通过stats进行统计信息分析得出的数据相对比较粗糙,比较难以分析有效信息。
# 构建速度分析:speed-measure-webpack-plugin
通过speed-measure-webpack-plugin插件进行分析,可以很方便的分析出每个 loader和plugin执行时的耗时,其能有效的分析了整个打包的总耗时及插件和loader 耗时情况,能有效的提供有效信息。
安装依赖
npm i speed-measure-webpack-plugin -D
对webpack.prod.js进行分析
//webpack.prod.js
const SpeedMeasureWebpackPlugin = require('speed-measure-webpack-plugin')
const smp = new SpeedMeasureWebpackPlugin()
modue.exports = smp.wrap({...})
# 构建体积分析:webpack-bundle-analyzer
通过webpack-bundle-analyzer将打包后项目进行可视化的分析,其中可分析依赖 的第三方模块文件大小和业务组件代码大小,进而对大文件分析是否可进行组件 替换或者CDN方式抽取。
安装依赖
npm i webpack-bundle-anaylzer -D
webpack.prod.js添加插件,然后监听8888端口即可得到对于的体积分析。
//webpack.prod.js
const WebpackBundleAnalyzer = require('webpack-bundle-analyzer').BundleAnalyzerPlugin
module.exports = {
plugins: [
new WebpackBundleAnalyzer()
]
}
# 构建速度优化
# 使用更高版本的webpack和node
通过使用更高版本的webpack和node,在实际项目上,通过webpack4的使用也是比webpack3
的构建速度有近40%的提升。
其中V8带来的优化措施有:for of替代forEach、Map和Set替代Object、includes替代indexOf
等。
webpack4中也通过了使用更快的md4 hash算法替代md5;webpack AST直接从loader传递给AST,减少
解析时间;使用字符串方法替代正则表达式等。
# 多进程/多实例构建
# 解析资源
- 使用HappyPack解析资源
HappyPack解析原理即每次webpack解析一个模块,HappyPack会将它及它的依赖分配给worker线程。
其实质是HappyPack会在webpack执行compiler.run后,进行初始化并创建HappyThreadPool线程池, 线程池则会将构建模块的任务进行分配线程,所以一个线程池中会运行多个线程,并对模块或者依赖进行处理, 处理完成后会将资源传说给HappyPack主进程,完成构建。
安装happypack依赖
npm i happypack -D
配置webpack
//webpack.prod.js
const Happypack = require('happypack')
module.exports ={
module: {
rules: [
{
test: /.js$/,
use: [
'happypack/loader'
/*'babel-loader'*/
]
},
]
},
plugins: [
new Happypack({
loaders: ['babel-loader']
})
]
}
- 使用thread-loader解析资源(推荐使用)
thread-loader是webpack4官方提供的,其原理也是通过每次webpack解析一个模块,thread-loader 将它及它的依赖分配给worker线程实现多线程构建。
安装thread-loader
npm i thread-loader -D
配置webpack
// webpack.prod.js
module.exports = {
module:{
rules:[
{
test: /.js$/,
exclude: /node_modules/,
use: [
{
loader: 'thread-loader',
options: {
workers:3
}
},
'babel-loader'
]
},
]
}
}
# 并行压缩
const ParallelUglifyPlugin = 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参数
const UglifyJsPlugin = require('uglifyjs-webpack-plugin')
module.exports = {
plugins: [
new UglifyJsPlugin({
uglifyOptions:{
warnings: false,
parse: {},
compress: {},
mangle:true,
output: null,
nameCache: null,
ie8: false,
keep_fnames: false
},
parallel: true
})
]
}
- 使用terser-webpack-plugin开启parallel参数(推荐使用)
terser-webpack-plugin与uglifyjs-webpack-plugin的区别就是terser-webpack-plugin 插件是支持ES6语法压缩,其中,webpack4默认推荐使用该插件。
安装terser-webpack-plugin插件
npm i terser-webpack-plugin -D
配置webpack
//webpack.prod.js
const TerserPlugin = require('terser-webpack-plugin')
module.exports = {
optimization: {
minimizer: [
new TerserPlugin({
parallel: true
})
]
}
}
# 分包
- 设置Externals
通过将react、react-dom,UI库等基础包通过CDN方式引入,不打入到bundle中,实现基础库的分离, 但是,这样也会由于一个基础库指向一个CDN,则需要配置多个scripts引入导致多次的请求。而如果借助 之前说的splitChunks实现分包也会使构建项目时进行基础包的分析,加大构建时长。
安装html-webpack-externals-plugin插件
npm i html-webpack-externals-plugin -D
配置webpack
//webpack.prod.js
const HtmlWebpackExternalsPlugin = require('html-webpack-externals-plugin')
module.exports = {
plugins: [
new HtmlWebpackExternalsPlugin({
externals:[
{
module:'react',
entry: 'https://cdnjs.cloudflare.com/ajax/libs/react/16.12.0/cjs/react.production.min.js',
global: 'react',
},
{
module:'react-dom',
entry: 'https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.11.0/cjs/react-dom.production.min.js',
global: 'ReactDom',
}
]
})
]
}
- 预编译资源模块:DLLPlugin + DllReferencePlugin(推荐)
通过将react,react-dom,UI库等基础包和业务基础包打包成一个文件。其中DLLPlugin通过将 多个基础包或者业务基础包提取,并生成一个包文件和manifest.json(对分离包的一个描述),然后 在实际应用中可以借助DllReferencePlugin对manifest.json引用即可实现分离包的关联。
新建webpack.dll.js并配置scripts命令,使用DLLPlugin进行分包
//package.json
{
"scripts":{
"dll": "webpack --config webpack.dll.js"
}
}
//webpack.dll.js
const path = require('path')
const webpack = require('webpack')
module.exports = {
entry: {
library: [ // 分离基础包,如果需要分离业务基础包可以配置多个
'react',
'react-dom'
]
},
output: {
filename: '[name]_[chunkhash:8].dll.js',
path: path.join(__dirname, './build/library'), //避免build时候清理dist目录
library: '[name]'
},
plugins: [
new webpack.DllPlugin({ //提供manifest引用
name: '[name]_[hash:8]',
path: path.join(__dirname, './build/library/[name].json')
})
]
}
使用DllReferencePlugin引用manifest.json
//webpack.prod.js
module.exports = {
plugins: [
new webpack.DllReferencePlugin({
manifest: require('./build/library/library.json')
})
]
}
# 缓存
开启构建项目时候的缓存可以提升二次构建的速度。其中,可以通过babel-loader开启缓存
则下次进行babel转换JS、JSX语法时直接读取缓存的内容;在代码压缩阶段可以使用UglifyJsPlugin
或TerserWebpackPlugin开启缓存;通过cache-loader或者hard-source-webpack-plugin
可以提升模块转换阶段的缓存。
对于的缓存内容可以在node_modules下的.cache目录。
//webpack.prod.js
const HardSourceWebpackPlugin = require('hard-source-webpack-plugin')
module.exports = {
module: {
rules:[
{
test: /.js$/,
use:[
{
loader: 'thread-loader',
options: {
workers:3
}
},
'babel-loader?cacheDirectory=true' //开启babel缓存
]
}
]
},
plugins: [
new HardSourceWebpackPlugin()
],
optimization:{
minimizer: [
new TerserPlugin({
parallel: true,
cache: true // 开启缓存
})
]
}
}
# 缩小构建目标
- 少构建模块
对于一些第三方的模块,我们可以不需要进行进一步的解析,比如babel-loader可以不用解析node_modules 的一些例如UI库等一些第三方包,因为其质量也是有了保证的。
module.exports = {
module:{
rules:[
test: /.js$/,
loader: 'happypack/loader',
exclude: 'node_modules' //include:path.resolve('src')
]
}
}
- 减少文件搜索范围
优化resolve.modules配置,较少模块搜索层级;优化resolve.mainFields配置,优化入口配置; 优化resolve.extensions配置,优化查找文件对于的后缀;合理使用alias等。
//webpack.prod.js
module.exports ={
resolve: {
alias: {
'react': path.resolve(__dirname,'./node_modules/react/umd/react.production.min.js'),
'react-dom': path.resolve(__dirname,'./node_modules/react/umd/react-dom.production.min.js')
},
extensions: ['.js'],
mainFields: ['main']
}
}
# 构建体积优化
# tree shaking 摇树优化
前面已经有说到过,webpack4设置mode:production默认开启tree shaking,通过 将模块中被使用到的方法打包到bundle文件,而没有用到的方法则在代码压缩阶段被擦除。 并且方法必须是ES6的语法。
# 删除无效的CSS
- PurifyCSS
遍历代码,识别已经用到的CSS class,通过对用到和没有使用到的class标记。 目前purifycss-webpack已停止更新,需替换为在webpack4中目前需要通过 purgecss-webpack-plugin结合mini-css-extract-plugin配合使用。
安装purgecss-webpack-plugin
npm i purgecss-webpack-plugin -D
配置webpack
//webpack.prod.js
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]_[contenthash:8].css'
}),
new PurgecssPlugin({
path: glob.sync(`${PATHS.src}/**/*`, {nodir:true}) // 绝对路径
}),
]
}
- uncss
其中要求HTML需要通过jsdom加载并且所有的样式通过PostCSS解析,这样才能通过 document.querySelector来识别在html文件里面不存在的选择器。
# 图片压缩
在项目上,js和css资源其实对比使用到的图片资源来说是小得多的,如果项目上存在大量
的图片的话,通过对图片的压缩来实现项目体积的优化的权重是比较高的。
基于Node的imagemin,可以通过配置image-webpack-loader来实现,其中,Imagemin可提供定制选项,例如可以
引入更多第三方优化插件,pngquant等,并且也支持多种图片格式压缩。
安装image-webpack-loader
npm i image-webpack-loader -D
配置webpack
module.exports ={
module:{
rules:[
{
test: /.(png|jpg|gif|jpeg)$/,
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.90],
speed: 4
},
gifsicle: {
interlaced: false,
},
// the webp option will enable WEBP
webp: {
quality: 75
}
}
},
]
},
]
}
}
# 设置动态Polyfill
对于一些es6的语法,不同浏览器可能需要不同的兼容处理,而polyfill就为我们提供了
兼容的方法,但是,完整的polyfill需要处理的兼容语法比较多,这就会导致构建的
体积比较大。
目前常用的polyfill方案:
方案 | 优点 | 缺点 |
---|---|---|
babel-polyfill | React官方推荐 | 包体积200k+,难以单独抽离Map、Set;项目里react是单独引用的cdn,如果要用到它,需要单独构建一份放到react前加载 |
babel-plugin-transform-runtime | 只能polyfill用到类或者方法,体积小 | 不能polyfill原型上的方法,不适合业务项目复杂的开发环境 |
自建Map、Set的polyfill | 定制化高,体积小 | 重复造轮子,需要维护;即使体积小,需全部加载 |
polyfill-service | 只给用户所需要的polyfill,社群维护 | 国内部分浏览器可能无法识别(可通过降级返回所需全部polyfill) |
推荐使用polyfill-service实现,其通过识别User Agent,下发不同的polyfill进行按需引用。
可通过polyfill.io官方提供的服务:
<script src="https://cdn.polyfill.io/v3/polyfill.min.js"></script>
线上代码可参考:https://github.com/PCAaron/blogCode/tree/master/webpack/webpack-improveMore