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

webpack分析构建速度和体积的工具

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

● 直接在package.json中使用

json 复制代码
"scripts": {
  "build:stats": "webpack --config webpack.prod.js --json > stats.json",
},

stats:可以初步查看构建的统计信息,看出来文件大小,但是构建出来的内容信息比较粗

● 在node.js中使用

javascript 复制代码
const webpack = require("webpack")
const cofig = require("./webpack.config.js")("production")

webpack(config,(err,stats) => {
  if(err){
    return consoel.error(err)
  }

  if(stats.hasError()){
    return console.error(stats.toString("errors-only"))
  }
})
速度分析 使用speed-measure-webpack-plugin

speed-measure-webpack-plugin:可以查看到每个插件的loader的执行耗时,通过红色绿色黄色显示耗时的不同时间,对于耗时比较高的插件,我们可以更换。

javascript 复制代码
const SpeedMeasureWebpackPlugin = require('speed-measure-webpack-plugin')

const smp = new SpeedMeasureWebpackPlugin()

const webpackConfig = smp.wrap({
  plugin:[
    new MyPlugin()
  ]
})
体积分析 webpack-bundle-analyzer

webpack-bundle-analyzer:使用这个插件之后会在8888端口展示页面内容的变化,这可以用来分析依赖的第三方模块文件的大小以及业务里面组件代码的大小

javascript 复制代码
const BundleAnalyzerPlugin = require('webpack-bundle-analyzer')

module.exports={
  plugins:[
    new BundleAnalyzerPlugin()
  ]
}

优化

速度优化,使用高版本的webpack和node.js

webpack4:

  1. v8带来的优化(for of代替了forEach、Map和Set代替Object、includes代替indexOf)
  2. 默认使用更快的md4 hash算法
  3. webpack AST可以直接从loader传递给AST,减少解析时间
  4. 使用字符串方法替代正则表达式
bulid时间优化 开启多线程/多实例的一个构建:资源并行解析可选方案

● thread-loader

● parallel-webpack

● HappyPack

  1. 并行处理任务 :将构建过程中的任务(如代码转换、压缩等)分配给多个线程同时处理,减少总耗时。
  2. 充分利用CPU资源 :特别是在多核CPU环境下,多线程可以更好地利用硬件资源。
  3. 提高大型项目构建速度 :对于包含大量文件的项目,并行处理能显著减少构建时间。
  4. 优化耗时操作 :如Babel转译、代码压缩等耗时任务,多线程可以有效分担工作负载。
  5. 改善开发体验 :更快的构建速度意味着开发者可以更快地看到代码变更的效果,提高开发效率。
HappyPack插件

之前我们都是一个线程进行一个打包,使用webpack解析一个模块,HappyPack会将它以及它的依赖分配给worker线程中,会开启多个线程,从而优化打包速度。

原理:Happypack 的作用就是将文件解析任务分解成多个子进程并发执行。子进程处理完任务后再将结果发送给主进程。所以可以大大提升 Webpack 的项目构件速度

  1. 安装
javascript 复制代码
$ npm install happypack --save-dev
const HappyPack = require('happypack')

export.module = {
  rules:[
    {
      test:/.js$/,
      use:'happypack/loader?id=babel',
    }
  ],
  plugin:[
    new HappyPack({
      id:'babel',
      loaders:['babel-loader']
    })
  ]
}
  1. HappyPack配置项
    a. id:String类型,对于happypack来说,唯一的id标识,用来关联 module.rules 中的 happypack
    b. loaders:Array类型,包含各种loader配置
    c. threads:Number类型,指示对应 loader 编译源文件时同时使用的进程数,默认是 3
    d. threadPool:HappyThreadPool对象,代表共享进程池,即多个 HappyPack 实例都使用同一个共享进程池中的子进程去处理任务,以防止资源占用过多
    e. verbose:是否允许happypack输出日志,默认为true
    f. verboseWhenProfiling:是否允许happypack再允许webpack --profile时输出日志,默认false
    g. debug:是否允许happypack打印log分析信息,默认false
thread-loader

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

  1. 安装
    $ npm install --save-dev thread-loader
    使用时,需将此 loader 放置在其他 loader 之前。放置在此 loader 之后的 loader 会在一个独立的 worker 池中运行
javascript 复制代码
export.module = {
  rules:[
    {
      test:/.js$/,
      use:[
        {
          loader:'thread-loader',
          options:{
            worker:3
          }
        },
        'babel-loader'
      ],
    }
  ]
}
  1. 配置项
javascript 复制代码
use: [
  {
    loader: "thread-loader",
    // 有同样配置的 loader 会共享一个 worker 池
    options: {
      // 产生的 worker 的数量,默认是 (cpu 核心数 - 1),或者,
      // 在 require('os').cpus() 是 undefined 时回退至 1
      workers: 2,

      // 一个 worker 进程中并行执行工作的数量
      // 默认为 20
      workerParallelJobs: 50,

      // 额外的 node.js 参数
      workerNodeArgs: ['--max-old-space-size=1024'],

      // 允许重新生成一个僵死的 work 池
      // 这个过程会降低整体编译速度
      // 并且开发环境应该设置为 false
      poolRespawn: false,

      // 闲置时定时删除 worker 进程
      // 默认为 500(ms)
      // 可以设置为无穷大,这样在监视模式(--watch)下可以保持 worker 持续存在
      poolTimeout: 2000,

      // 池分配给 worker 的工作数量
      // 默认为 200
      // 降低这个数值会降低总体的效率,但是会提升工作分布更均一
      poolParallelJobs: 50,

      // 池的名称
      // 可以修改名称来创建其余选项都一样的池
      name: "my-pool"
    },
  },
  // 耗时的 loader(例如 babel-loader)
];
多进程/多实例:并行压缩

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

方法二:uglifyjs-webpack-plugin开启parallel参数(这个是不支持的)

方法三:terser-webpack-plugin开启parallel参数(支持ES6语法,会压缩为ES6的代码)

方法一和方法二都有一个parallel参数 只要将这个参数设置为true 就可以开启并行压缩了

javascript 复制代码
//并行压缩
const TerserPlugin = require('terser-webpack-plugin')

const smp = new SpeedMeasurePlugin()

module.exports = {
  optimization: {
    minimizer: [
      new TerserPlugin({
        parallel: true,//这里默认的是当前电脑CPU的两倍减去1
      })
    ]
  },
}
速度优化:分包

● splitChunksPlugin

● 设置externals:使用html-webpack-externals-plugin插件

使用html-webpack-externals-plugin插件

思路:将一些基础包通过cdn引入,不打入bundle中

缺点:一个包就会用一个script标签,当包很多的时候,会产生很多的script标签

javascript 复制代码
// webpack.config.js
module.exports = {
  externals: {
    react: 'React',
    'react-dom': 'ReactDOM',
  },
};

// index.html (需手动添加script标签)

splitChunksPlugin

分离基础包会对每个包进行一个分析

javascript 复制代码
module.exports = {
  optimization: {
    splitChunks: {
      chunks: 'all',
      minSize: 20000, // 最小chunk size
      minRemainingSize: 0,
      minChunks: 1,
      maxAsyncRequests: 30,
      maxInitialRequests: 30,
      enforceSizeThreshold: 50000,
      cacheGroups: {
        defaultVendors: {
          test: /[\/]node_modules[\/]/,
          priority: -10,
          reuseExistingChunk: true,
        },
        default: {
          minChunks: 2,
          priority: -20,
          reuseExistingChunk: true,
        },
      },
    },
  },
  };
预编译资源模块

webpack内置的插件,使用DDLPlugin进行分包,DllReferencePlugin对manifest.json引用

思路:将一些基础包和基础业务打包成一个文件

DDLPlugin是对多个组件和框架库进行的一个提取,生成一个包文件,同时生成一个manifest.json文件,manifest.json是对分离包的描述

webpack.dll.js

利用缓存来提升二次构建速度

缓存思路:

babel-loader:针对babel转换的一些js,css等的语法进行缓存

terser-webpack-plugin:代码压缩阶段进行缓存

cache-loader或者hard-source-webpack-plugin:提升模块转换阶段的缓存

javascript 复制代码
const HappyPack = require('happypack')

export.module = {
  plugin:[
    new HappyPack({
      id:'babel'
      loaders:['babel-loader?cacheDirectory=true']
    })
  ]
}
javascript 复制代码
//并行压缩
const TerserPlugin = require('terser-webpack-plugin')

module.exports = {
  optimization: {
    minimizer: [
      new TerserPlugin({
        parallel: true,//这里默认的是当前电脑CPU的两倍减去1
        cache:true
      })
    ]
  },
}
javascript 复制代码
const HardSourceWebpackPlugin = require('hard-source-webpack-plugin')

export.module = {
  plugin:[
    new HappyPack({
      new HardSourceWebpackPlugin()
    })
  ]
}
缩小构建目标

目的:尽可能少的构建模块,比如babel-loader 不解析node_modules

javascript 复制代码
module.exports = {
  rules:{
    test:/\.js$/,
    loader:'happypack/loader',
    exclude:'node_modules'
  }
}
减少文件搜索范围

● 优化resolve.modules配置(减少模块搜索层级)

● 优化resove.mainFields配置

● 优化resolve.extensions配置

● 合理使用alias

javascript 复制代码
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

无用的CSS如何删掉:
PurifyCSS:遍历代码,识别已经用到的CSS class
unCss :HTML需要通过jsdom加载,所有的样式通过PostCSS解析,通过document.querySelector来识别在html文件里面不存在的选择器
使用:purgecss-webpack-plugin和min-css-extract-plugin配合使用

javascript 复制代码
const PATHS = {
  src:path:join(_dirname,'src')
}

module.exports={
  plugins:[
  new MiniCssExtractPlugin({
    filename:"[name].css"
  }),
  new PurgecssPlugin({
    path:glob.sync(`$PATHS.src/**/*`,{nodir:true})//这里一定要是绝对路径
  })
]
}
图片压缩

使用基于Node库的imagemin:拥有很多定制选项,可以引入更多的第三方优化插件,例如pngquant,可以处理多种图片格式

image-webpack-loader

powershell 复制代码
cnpm install --save-dev image-webpack-loader
powershell 复制代码
module: {
  rules: [
   {
	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
      // }
     }
    }
   ]
   }
   ...
  ]
}