webpack性能优化

基于webpack的性能优化

基本优化方案

  1. 使用高版本的webpack和node
  2. 静态资源优化
    1. JavaScript文件压缩、合并、TreeShaking
    2. CSS文件压缩、优化、TreeShaking
    3. Img压缩、合并雪碧图:
    4. HTML文件压缩
    5. 上传CDN
    6. 使用prefetch、preload预加载资源文件
      1. preload:本次加载需要的资源,告诉浏览器,提高下载速度权重
      2. prefetch:未来可能使用到的资源文件,在浏览器空闲的时候下载
  3. 代码分割Code Spliting
    1. entry:入口文件手动分割
    2. splitChunks:optimization.splitChunks,将相同的代码抽离到一个单独的文件;也可以将第三方库文件抽离
    3. import():动态导入。
  4. 利用缓存
    1. 使用cache-loader,缓存每次打包之后的文件。
  5. 多核
    1. 使用happypack(已不维护)、thread-laoderwebpack-parallel-uglify-plugin等,开启多核执行。
  6. 分包加载:使用DllPlugin 和 DLLReferencePlugin
  7. 缩小搜索范围:使用exclude、include尽量缩小webapck的搜索范围。
  8. 配置resolve .extensions 尽量将优先级高的放在前面,缩短命中时间。
  9. 预渲染:prenderer-spa-plugin 将重要页面采用预渲染功能,有利于SEO。
  10. 服务端渲染:vue-server-render 在服务端将页面渲染完成,直接返回html页面给客户端。
  11. 同构:SSR + CSR,刷新SSR ,路由切换CSR。

静态资源优化

JavaScript

使用DllPluginDLLReferencePlugin,拆分bundles。具体使用请看这一篇文章。也可以使用hard-source-webpack-plugin替换DllPluginDLLReferencePlugin
生产环境webpack默认开启uglify-js

压缩js
optimization: {
    minimizer: [
      new UglifyJsPlugin({
        uglifyOptions: {
          mangle: {
            safari10: true
          },
          // 清除生产环境的控制台输出
          compress: {
            warnings: false,
            drop_debugger: true,
            drop_console: true
          }  
        },
        sourceMap: config.build.productionSourceMap,
        cache: true,
        parallel: true
      })
    ]
  }
抽离公共js文件
splitChunks: {
    chunks: "all",//进行代码分割的时候,all:针对所有的导入  async:只针对异步导入 initial:针对同步代码导入。
    minSize: 30000,//设置最小阀值,只有大于该阀值,才会进行代码分割。
    minChunks: 1,//在分割模块之前共享一个模块的最小块数(设置代码最少被引用次数)
    maxAsyncRequests: 5,//按需加载时的最大并行请求数 超过就不会在做代码分割打包
    maxInitialRequests: 3,//一个入口点的最大并行请求数  超过就不会做代码分割
    automaticNameDelimiter: '~',//打包生成之后,默认情况下,webpack将使用块的来源和名称来生成名称,比如vendor~main.js。
    name: true,//使得cacheGroups中打包生成的文件名称
    cacheGroups: {//缓存组 打包分组
      vendors: {//配置同步导入 
        test: /[\\/]node_modules[\\/]/,//只有node_module中的才会进入
        priority: -10,//值越大 优先级越高
        filename: 'vendors.js'
      },
      default: {
        // minChunks: 2,
        priority: 0,
        reuseExistingChunk: true,// 如果模块已经被打包了,在此遇到的时候 直接忽略,直接使用以打包好的模块。
        filename:'default.js'
      }
    }
  }
关于代码分离

请移步https://www.yuque.com/walter-glskq/zf7grn/zcamxf

CSS

extract-text-webpack-pluginmini-css-extract-plugin使用该插件将css文件从js中剥离出来,进行优化压缩。配合postcss-loader进行css预处理和后处理。

//压缩css 并且将css从js抽离出来
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
//优化css
const OptimizeCssAssetsPlugin = require('optimize-css-assets-webpack-plugin');

module.exports = {
    module: {
   rules: [{
             test: /\.css$/,
       use: [{
         loader: MiniCssExtractPlugin.loader,
         options: {
           esModule: true,
           filename: '/css/[name].[hash:7].css'
         },
       }, 'css-loader', 'postcss-loader','less-loader']
   }]
 },
  plugins:[
      new MiniCssExtractPlugin({
      filename: 'css/[name].[hash:5].css',
      chunkFilename: 'css/[id].[hash:5].css',
      ignoreOrder: false
    }),
    new OptimizeCssAssetsPlugin({
      assetNameRegExp: /\.css$/g,
      cssProcessor: require('cssnano'),
      cssProcessorPluginOptions: {
        preset: ['default', { discardComments: { removeAll: true } }],
      },
      canPrint: true
    })
  ]
}

Img

使用file-loader和image-webpack-loader处理图片文件,进行压缩。

module.exports = {
  // ...
  module: {
    rules: [
      {
        test: /.*\.(gif|png|jpe?g|svg|webp)$/i,
        use: [
          {
            loader: 'file-loader',
            options: {}
          },
          {
            loader: 'image-webpack-loader',
            options: {
              mozjpeg: { // 压缩 jpeg 的配置
                progressive: true,
                quality: 65
              },
              optipng: { // 使用 imagemin-optipng 压缩 png,enable: false 为关闭
                enabled: false,
              },
              pngquant: { // 使用 imagemin-pngquant 压缩 png
                quality: '65-90',
                speed: 4
              },
              gifsicle: { // 压缩 gif 的配置
                interlaced: false,
              },
              webp: { // 开启 webp,会把 jpg 和 png 图片压缩为 webp 格式
                quality: 75
              },
          },
        ],
      },
    ],
  },
}

使用url-loader将一些小图片转为base64格式,减少http请求:

{
  test: /\.(png|jpg|jpeg|gif)(\?.+)?$/,
        exclude: /favicon\.png$/,
      use: [{
        loader: 'url-loader',
        options: { //动态抽离img 给一个阀值 小于该阀值的 以base64的形式存在与js中
          esModule: false,
          limit: 10000,
          name: 'image/[name].[hash:7].[ext]'
        }
      }]
}

压缩HTML

配置minify,优化html文件。生产环境生效。

new HtmlWebpackPlugin({
  filename: 'index.html',
  template: 'index.html',
  inject: true,
  title: 'admin',
  minify: {
    // 合并空格
    collapseWhitespace: true,
    // 移除注解
    removeComments: true,
    // 移除多余的属性
    removeRedundantAttributes: true,
    // 移除脚本类型属性
    removeScriptTypeAttributes: true,
    // 移除样式类型属性
    removeStyleLinkTypeAttributes: true,
    // 使用简短的文档类型
    useShortDoctype: true
    // more options:
    // https://github.com/kangax/html-minifier#options-quick-reference
  }
}),

使用DllPlugin和DLLReferencePlugin

  1. 使用DllPluginDLLReferencePlugin
    1. DLLPluginDLLReferencePlugin 用某种方法实现了拆分 bundles,同时还大大提升了构建的速度。

包含大量复用模块的动态链接库只需被编译一次,在之后的构建过程中被动态链接库包含的模块将不会重新编译,而是直接使用动态链接库中 的代码 由于动态链接库中大多数包含的是常用的第三方模块。例如 vue、vue-router ,所以只要不升级这些模块的版本,动态链接库就不用重新编译。

优化Loader配置

由于 Loader 对文件的转换操作很耗时,所以需要让尽可能少的文件被 Loader 处理。可以通过 test include exclude 三个配置项来命中 Loader 要应用规则的文件。

module.exports = { 
  module : { 
    rules : [{
      //如果项目源码中只有js文件,就不要写成/\jsx?$/,以提升正则表达式的性能
      test: /\.js$/, 
      //babel -loader 支持缓存转换出的结果,通过 cacheDirectory 选项开启
      use: ['babel-loader?cacheDirectory'] , 
      //只对项目根目录下 src 目录中的文件采用 babel-loader
      include: path.resolve(__dirname,'src'),
    }],
  }
}

优化 resolve.extensions 配置

在导入语句没带文件后缀时,Webpack 会自动带上后缀去尝试询问文件是否存在。如果这个列表越长,或者正确的后缀越往后,就会造成尝试的次数越多,所以resolve .extensions 的配置也会影响到构建的性能。
建议:

  • 后缀尝试列表要尽可能小,不要将项目中不可能存在的情况写到后缀尝试列表中。
  • 频率出现最高的文件后缀要优先放在最前面,以做到尽快退出寻找过程。
  • 在源码中写导入语句时,要尽可能带上后缀 从而可以避免寻找过程
module.exports = { 
  resolve : { 
    //尽可能减少后缀尝试的可能性
    extensions : ['js'],
  }
}

优化打包加速

使用webpack-parallel-uglify-plugin。webpack默认提供了UglifyJS插件来压缩JS代码,但是它使用的是单线程压缩代码,也就是说多个js文件需要被压缩,它需要一个个文件进行压缩。所以说在正式环境打包压缩代码速度非常慢(因为压缩JS代码需要先把代码解析成用Object抽象表示的AST语法树,再去应用各种规则分析和处理AST,导致这个过程耗时非常大)。当webpack有多个JS文件需要输出和压缩时候,原来会使用UglifyJS去一个个压缩并且输出,但是ParallelUglifyPlugin插件则会开启多个子进程,把对多个文件压缩的工作分别给多个子进程去完成,但是每个子进程还是通过UglifyJS去压缩代码。无非就是变成了并行处理该压缩了,并行处理多个子任务,效率会更加的提高。

// 引入 ParallelUglifyPlugin 插件
const ParallelUglifyPlugin = require('webpack-parallel-uglify-plugin');

module.exports = {
  plugins: [
    // 使用 ParallelUglifyPlugin 并行压缩输出JS代码
    new ParallelUglifyPlugin({
      // 传递给 UglifyJS的参数如下:
      uglifyJS: {
        output: {
          /*
           是否输出可读性较强的代码,即会保留空格和制表符,默认为输出,为了达到更好的压缩效果,
           可以设置为false
          */
          beautify: false,
          /*
           是否保留代码中的注释,默认为保留,为了达到更好的压缩效果,可以设置为false
          */
          comments: false
        },
        compress: {
          /*
           是否在UglifyJS删除没有用到的代码时输出警告信息,默认为输出,可以设置为false关闭这些作用
           不大的警告
          */
          warnings: false,

          /*
           是否删除代码中所有的console语句,默认为不删除,开启后,会删除所有的console语句
          */
          drop_console: true,

          /*
           是否内嵌虽然已经定义了,但是只用到一次的变量,比如将 var x = 1; y = x, 转换成 y = 1, 默认为不
           转换,为了达到更好的压缩效果,可以设置为false
          */
          collapse_vars: true,

          /*
           是否提取出现了多次但是没有定义成变量去引用的静态值,比如将 x = 'xxx'; y = 'xxx'  转换成
           var a = 'xxxx'; x = a; y = a; 默认为不转换,为了达到更好的压缩效果,可以设置为false
          */
          reduce_vars: true
        }
      }
    }),
  ]
}
在通过 new ParallelUglifyPlugin() 实列化时,支持以下参数配置如下:

test: 使用正则去匹配哪些文件需要被 ParallelUglifyPlugin 压缩,默认是 /.js$/.
include: 使用正则去包含被 ParallelUglifyPlugin 压缩的文件,默认为 [].
exclude: 使用正则去不包含被 ParallelUglifyPlugin 压缩的文件,默认为 [].
cacheDir: 缓存压缩后的结果,下次遇到一样的输入时直接从缓存中获取压缩后的结果并返回,cacheDir 用于配置缓存存放的目录路径。默认不会缓存,想开启缓存请设置一个目录路径。

workerCount:开启几个子进程去并发的执行压缩。默认是当前运行电脑的 CPU 核数减去1。
sourceMap:是否为压缩后的代码生成对应的Source Map, 默认不生成,开启后耗时会大大增加,一般不会将压缩后的代码的
sourceMap发送给网站用户的浏览器。
uglifyJS:用于压缩 ES5 代码时的配置,Object 类型,直接透传给 UglifyJS 的参数。

优化小插件

speed-measure-webpack-plugin

//添加打包时各种资源构建小时钟提示 有助于分析
const SpeedMeasurePlugin = require("speed-measure-webpack-plugin");
const smp = new SpeedMeasurePlugin();
module.exports = smp.wrap({})

webpack-bundle-analyzer

打包分析工具

npm install --save-dev webpack-bundle-analyzer

在webpack的plugins中配置: new BundleAnalyzerPlugin()package.json 中增加: "analyz": "NODE_ENV=production npm_config_report=true npm run build"

webpack-dashboard

更加直观的显示webpack的构建。

npm install webpack-dashboard --save-dev

const Dashboard = require('webpack-dashboard');
const DashboardPlugin = require('webpack-dashboard/plugin');
const dashboard = new Dashboard();

plugins: [
  new DashboardPlugin(dashboard.setData)
]

image.png

  • 版权声明: 本博客所有文章除特别声明外,著作权归作者所有。转载请注明出处!