Webpack学习笔记(优化篇)

共 7027字,需浏览 15分钟

 ·

2021-10-28 22:41

webpack构建速度和体积优化策略

初级分析:使用webpack内置的stats

stats:构建的统计信息

package.json中使用stats

"scripts":{"build:stats":"webpack --env production --json > stats.json"...}

Node.js中使用stats

const webpack =require("webpack");const config =require("./webpack.config.js")("production");webpack(config,(err, stats)=>{if(err){return console.error(err);}if(stats.hasErrors()){return console.error(stats.toString("errors-only"));}  console.log(Stats);})

ef9b4c0d47a567c5f00c429dadabe4af.webp


stats统计的缺陷:颗粒度比较粗,看不出问题所在

速度分析:使用speed-measure-webpack-plugin

使用步骤

安装

npm install --save-dev speed-measure-webpack-plugin

代码示例

constSpeedMeasurePlugin=require("speed-measure-webpack-plugin");const smp =newSpeedMeasurePlugin();const webpackConfig = smp.wrap({  plugins:[newMyPlugin(),newMyOtherPlugin()]})

9923564fb1736776e2cbbb0c51fb6973.webp


优点:可以看到每个loader和插件执行耗时

速度分析插件作用

分析整个打包总耗时

每个插件和loader的耗时情况

体积分析:使用webpack-bundle-analyzer

webpack-bundle-analyzer分析体积

代码示例:

constBundleAnalyzerPlugin=require('webpack-bundle-analyzer').BundleAnalyzerPlugin;module.exports ={  plugins:[newBundleAnalyzerPlugin();]}

构建完成后会在8888端口展示构建资源大小

可以分析哪些问题?

依赖的第三方模块文件大小业务里面的组件代码大小

7167ec9c606d3c16946666ca030ffd5f.webp


速度提升:使用高版本的webpack和Node.js

构建时间降低了60%-98%

使用webpack4:优化原因

V8带来的优化(for of 替代forEach、Map和Set替代Object,includes替代indexOf)默认使用更快的md4 hash算法webpacks AST可以直接从loader传递给AST,减少解析时间使用字符串方法替代正则表达式

速度提升:多进程/多实例构建

多进程/多实例构建:资源并行解析可选方案

thread-loaderparallel-webpackHappyPack

多进程/多实例构建:使用HappyPack解析资源

原理:每次webpack解析一个模块,HappyPack会将它及它的依赖分配给worker线程中

代码示例:

exports.plugins =[newHappyPack({    id:'jsx',    threads:4,    loaders:['babel-loader']}),newHappyPack({    id:'styles',    threads:2,    loaders:['style-loader','css-loader','less-loader']})]

fbb1a29546f5eb6a111a109aa9365ca5.webp


多进程/多实例构建:使用thread-loader解析资源

原理:每次webpack解析一个模块,thread-loader会将它及它的依赖分配给worker线程中

module.exports = smp.wrap({  entry: entry,  output:{    path: path.join(__dirname,'dist'),    filename:'[name]_[chunkhash:8].js'},  mode:'production',module:{    rules:[{        test:/.js$/,use:[{            loader:'thread-loader',            options:{              workers:3}},'babel-loader',]}]}})

对进程/多实例:并行压缩

方法一:使用parallel-uglify-plugin插件

constParallelUglifyPlugin=require('webpack-parallel-uglify-plugin');module.exports ={  plugins:[newParallelUglifyPlugin({      uglifyJS:{        output:{          beautify:false,          comments:false},        compress:{          warnings:false,          drop_console:true,          collapse_vars:true,          reduce_vars:true,}}})]}

方法二:uglifyjs-webpack-plugin开启parallel参数

constUglifyJsPlugin=require('uglifyjs-webpack-plugin');module.exports ={  plugins:[newUglifyJsPlugin({      uglifyOptions:{        warnings:false,        parse:{},        compress:{},        mangle:true,        output:null,        toplevel:false,        nameCache:null,        ie8:false,        keep_fnames:false},      parallel:true})]}

方法三:terser-webpack-plugin开启parallel参数

constTerserPlugin=require('terser-webpack-plugin');module.exports ={  optimization:{    minimizer:[newTerserPlugin({        paralles:4})]}}

进一步分包:预编译资源模块

分包:设置Externals

思路:将react、react-dom基础包通过cdn引入,不打入bundle中

方法:使用html-webpack-externals-plugin

constHtmlWebpackExternalsPlugin=require('html-webpack-externals-plugin');module.exports ={...  plugins:[newHtmlWebpackExternalsPlugin({            externals:[{module:'react',                    entry:'https://11.url.cn/now/lib/16.2.0/react.min.js',global:'React',},{module:'react-dom',                    entry:'https://11.url.cn/now/lib/16.2.0/react-dom.min.js',global:'ReactDOM',}]})]...}

分包:预编译资源模块

思路:将react、react-dom、redux、react-redux基础包和业务基础包打包成一个文件

方法:使用DLLPLugin进行分包,DllReferencePlugin对manifest.json引用

使用DLLPlugin进行分包

const path =require('path');const webpack =require('webpack');module.exports ={  context: process.cwd(),  resolve:{    extensions:['.js','.jsx','.json','.less','.css'],    modules:[__dirname,'node_modules']},  entry:{    library:['react','react-dom','redux','react-redux']},  output:{    filename:'[name].dll.js',    path: path.resolve(__dirname,'./build/library'),    library:'[name]'},  plugins:[new webpack.DllPlugin({      name:'[name]',      path:'./build/library/[name].json'})]}

使用DllReferencePlugin引用manifest.json

在webpack.config.js引入

module.exports ={  plugins:[new webpack.DllReferencePlugin({      manifest:require('./build/library/manifest.json')})]}

速度提升:充分利用缓存提升二次构建速度

缓存目的:提升二次构建速度

缓存思路:

babel-loader开启缓存terser-webpack-plugin开启缓存使用cache-loader或者hard-source-webpack-plugin

速度提升:缩小构建目标

目的:尽可能的少构建模块

比如babel-loader不解析node_modules

减少文件搜索范围

优化resolve.modules配置(减少模块搜索层级)优化resolve.mainFields配置优化resolve.extensions配置合理使用alis

module.exports ={  resolve:{alias:{      react: path.resolve(__dirname,'./node_modules/react/dist/react.min.js'),},    modules:[path.resolve(__dirname,'node_modules')],    extensions:['.js'],    mainFields:['main'],}}

使用Tree Shaking擦除无用的JavaScript和Css

tree shaking(摇树优化)复习

概念: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?

使用purgecss-webpack-plugin

https://github.com/FullHuman/purgecss-webpack-plugin

和mini-css-extract-plugin配合使用

const path =require('path');const glob =require('glob');constMiniCssExtractPlugin=require('mini-css-extract-plugin');constPurgecssPlugin=require('purgecss-webpack-plugin');const PATHS ={  src: path.join(__dirname,'src')}module.exports ={module:{    rules:[{        test:/\.css/,use:[MiniCssExtractPlugin.loader,'css-loader']}]},  plugins:[newMiniCssExtractPlugin({      filename:'[name].css',}),newPurgecssPlugin({      paths: glob.sync(`${PATHS.src}/**/*`,{nodir:true})})]}

使用webpack进行图片压缩

要求:基于Node库的imagemin或者tinypngAPI

使用:配置image-webpack-loader

return{  test:/\.(png|svg|jpg|gif|blob)$/,use:[{    loader:'file-loader',    options:{      name:`${filename}img/[name]${hash}.[ext]`}},{    loader:'image-webpack-loader',    options:{      mozjpeg:{        progressive:true,        quality:65},      optipng:{        enabled:false,},      pngquant:{        quality:'65-90',        speed:4},      gifsicle:{        interlaced:false},      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:打包后体积88k,占比较大

构建体积优化:动态Polyfill

方案优点缺点是否采用
babel-polyfillReact16官方推荐1、包体积200K+,难以单独抽离Map、Set;2、项目里react是单独引用的cdn,如果要用它,需要单独构建一份放在react前加载
babel-plugin-transform-runtime能只polyfill用到的类或方法,相对体积较小不能polyfill原型上的方法,不适用于业务项目的复杂开发环境
自己写Map、Set的polyfill定制化高,体积小1、重复造轮子,容易在日后年久失修成为坑;2、即使体积小,依然所有用户都要加载
polyfill-service只给用户返回需要的polyfill,社区维护部分国内奇葩浏览器UA可能无法识别(但可以降级返回所需全部polyfill)

Polyfill Service原理

识别User Agent,下发不同的Polyfill

a4ad7dea2fda60168eb0d889c86cdb4e.webp


如何使用动态Polyfill Service

1、polyfill.io 官方提供的服务

2、基于官方自建polyfill服务

//huayang.qq.com/polyfill_service/v2/polyfill.min.js?unknown=polyfill&features=Promise,Map,Set


浏览 49
点赞
评论
收藏
分享

手机扫一扫分享

分享
举报
评论
图片
表情
推荐
点赞
评论
收藏
分享

手机扫一扫分享

分享
举报