十大webpack优化插件

3,577 阅读8分钟

随着前端项目日渐的复杂,我们日常开发中使用的构建工具也不断演变、进化,既要满足我们开发时的便利,还要充当构建时各种资源优化、自动打包、自动测试的角色。目前,最受欢迎的当数webpack。满足复杂的功能的同时必定带来一定的使用成本,繁杂的配置,构建的性能也成了我们使用webpack时需要考虑的问题。

在笔者看来,webpack有四大核心的配置:

1、入口

2、出口

3、loader

4、plugins

这四个配置,缺少任何一样,都不能满足我们平时构建的复杂的需求。插件更是打包优化的核心配置。本文主要从插件的角度,对webpack打包和构建做一些优化。

1、SplitChunks

这是webpack4升级的最重要的配置之一,它的作用是将动态加载的模块打包成common chunks,它的作用相当于之前的commonChunksPlugin,但是比之前的插件功能更加强大。因为几乎所有的项目打包都必备,所以webpack直接内置为一个配置,配置在optimization这个选项下。下面我们看下默认的配置:

splitChunks: {
  chunks: 'async', // 分割异步模块
  minSize: 30000, // 分割的文件最小大小
  maxSize: 0,     
  minChunks: 1, // 引用次数
  maxAsyncRequests: 5, // 最大异步请求数
  maxInitialRequests: 3, // 最大初始化请求数
  automaticNameDelimiter: '~', // 抽离的命名分隔符
  automaticNameMaxLength: 30, // 名字最大长度
  name: true,
  cacheGroups: { // 缓存组
    vendors: { // 先抽离第三方库
      test: /[\\/]node_modules[\\/]/,
      priority: -10
    },
    default: { 
      minChunks: 2,
      priority: -20, // 优先级
      reuseExistingChunk: true
    }
  }
}

使用cacheGroups我们可以任意定制我们想打包的common chunk,还可以控制每个chunk的大小,默认配置已经满足大多数开发的需求。

2、DllPlugin && DllReferencePlugin

接下来要介绍的是DllPluginDllReferencePlugin的结合,这两个插件必须配合使用才能发挥其巨大的作用,主要的作用是提升你开发时的构建速度。它的思路是这样的,首先使用DllPlugin预编译所有你项目中几乎不会变化的外部模块,然后在构建之前,把这些资源打包成一个vendor,之后直接在项目中使用,每次修改文件这些资源将不用重新打包,下面我们看下配置,单独创建webpack.dll.js:

const path = require('path');
const DllPlugin = require('webpack/lib/DllPlugin');

module.exports = {
    entry:['vue','vue-router', 'vuex'],
    mode:'production',
    output:{
        filename:'vue.dll.js',
        path:path.resolve(__dirname,'dll'),
        library:'vue'
    },
    plugins:[
        new DllPlugin({
            name:'vue',
            path:path.resolve(__dirname,'dll/manifest.json')
        })
    ]
}

执行webpack --config webpack.dll.js命令 ,我们将看到生成的dll目录下有两个文件:vue.dll.js和manifest.json ,manifest.json中保存了我们打包到vue.dll.js中的模块路径,然后接下来我们需要使用DllReferencePlugin插件通过manifest.json文件去找我们需要的依赖:

const DllReferencePlugin = require('webpack/lib/DllReferencePlugin');
const AddAssetHtmlWebpackPlugin = require('add-asset-html-webpack-plugin');
// 构建时会引用动态链接库的内容
new DllReferencePlugin({
  manifest:path.resolve(__dirname,'dll/manifest.json')
}),
// 通过插件将vue.dll.js自动引入到我们的index.html中
new AddAssetHtmlWebpackPlugin(
  { filepath: path.resolve(__dirname,'dll/vue.dll.js') }
)

到此,我们完成了两个插件的使用。

3、image-webpack-loader

这里是本文的一个特例,image-webpack-loader它不是一个插件,但是它的作用十分强大,像是插件的角色。前端开发中,我们少不了对图片资源的使用,那么在繁杂的各种图片资源中,我们需要一个工具对不同的图片格式进行压缩处理,这样构建处理的图片才能在尺寸上也符合我们的期望。image-webpack-loader可以对各种图片格式进行一定的压缩。话不多说直接看配置:

loader: "image-webpack-loader",
options: {
  mozjpeg: {
    progressive: true,
    quality: 65
  },
  // optipng.enabled: false will disable optipng
  optipng: {
    enabled: false,
  },
  pngquant: {
    quality: [0.90, 0.95],
    speed: 4
  },
  gifsicle: {
    interlaced: false,
  },
  // the webp option will enable WEBP
  webp: {
    quality: 75
  }
}

它可以让我们在保证图片清晰度的情况下,对图片进行压缩,可以自行根据项目的需要配置,图片压缩就是这么简单。

4、MiniCssExtractPlugin

这是一个既包含loader又可以作为插件的插件,它的作用是将css单独打包成一个文件。webpack一般常用的是通过style-loader将css通过style标签插入到html中,但是这样不利于缓存,也影响js的加载。所以,我们一般需要将一些css单独打包出来。下面看插件的使用:

const MiniCssExtractPlugin = require('mini-css-extract-plugin');
module.exports = {
  plugins: [
    new MiniCssExtractPlugin({
      // Options similar to the same options in webpackOptions.output
      // all options are optional
      filename: '[name].css',
      chunkFilename: '[id].css',
      ignoreOrder: false, // Enable to remove warnings about conflicting order
    }),
  ],
  module: {
    rules: [
      {
        test: /\.css$/,
        use: [
          {
            loader: MiniCssExtractPlugin.loader,
            options: {
              // you can specify a publicPath here
              // by default it uses publicPath in webpackOptions.output
              publicPath: '../',
              hmr: process.env.NODE_ENV === 'development',
            },
          },
          'css-loader',
        ],
      },
    ],
  },
};

相比于extract-text-webpack-plugin插件,它有一些优势:

  • 异步加载
  • 不会有重复的编译,在性能上有提升
  • 使用也更加方便
  • 专门处理css资源

5、purgecss-webpack-plugin

有时候我们参与开发的项目很大,参与的开发人员也很多,大家在开发的时候很可能写了一些无用的样式,或是因为需求变更忘记删除。这时候我们就需要一个插件:purgecss-webpack-plugin,帮助我们自动清理掉一些无用的样式:

const glob = require('glob');
const PurgecssPlugin = require('purgecss-webpack-plugin');

// 需要配合mini-css-extract-plugin插件
{
  plugins: [
    new PurgecssPlugin({
      // 不匹配目录,只匹配文件
    	paths: glob.sync(`${path.join(__dirname, "src")}/**/*`, { nodir: true }) 
		})
  ]
}

当然我们需要配合glob库使用,告诉插件需要匹配哪些目录。我们看一段代码:

import './style.css'
import React from 'react';
import ReactDOM from 'react-dom';
ReactDOM.render(<div>hello</div>,document.getElementById('root'));
// style.css
body{
    background: red
}

// 这个类的样式是无用的,将会被清除
.class1{
    background: red
}

6、happypack

使用webpack对项目进行构建时,会对大量的文件进行处理,文件越多构建速度越慢。其中一个原因就是运行在Node上的webpack是单线程的,它只能一个个任务排队处理。使用happypack可以将解析文件的任务分成多个子进程并发执行,每个子进程处理完再将结果返回给主进程,从而能大大提升webpack的构建项目速度。下面来看下happypack的使用方法:

首先将你想使用多进程处理的loader做一个修改:

const HappyPack = require('happypack');

module.exports = {
    ...
    module: {
        rules: [
            test: /\.js$/,
            // use: ['babel-loader?cacheDirectory'] 之前是使用这种方式直接使用 loader
            // 现在用下面的方式替换成 happypack/loader,并使用 id 指定创建的 HappyPack 插件
            use: ['happypack/loader?id=babel'],
            // 排除 node_modules 目录下的文件
            exclude: /node_modules/
        ]
    }
}

然后在插件中配置happypack:

const HappyPack = require('happypack');

module.exports = {
    ...
    module: {
        rules: [
            test: /\.js$/,
            // use: ['babel-loader?cacheDirectory'] 之前是使用这种方式直接使用 loader
            // 现在用下面的方式替换成 happypack/loader,并使用 id 指定创建的 HappyPack 插件
            use: ['happypack/loader?id=babel'],
            // 排除 node_modules 目录下的文件
            exclude: /node_modules/
        ]
    },
    plugins: [
        ...,
        new HappyPack({
            /*
             * 必须配置
             */
            // id 标识符,要和 rules 中指定的 id 对应起来
            id: 'babel',
            // 需要使用的 loader,用法和 rules 中 Loader 配置一样
            // 可以直接是字符串,也可以是对象形式
            loaders: ['babel-loader?cacheDirectory']
        })
    ]
}

这样就完成了happypack的使用。happypack有很多配置项,详细可以查看happypack

7、hard-source-webpack-plugin

使用缓存来有优化性能在前端领域是一种很常见的手段,既然webpack每次都要构建大量的文件,是不是可以通过一个插件对前一次的构建结果做一些缓存了?答案当然是可以的,hard-source-webpack-plugin就是这样一个webpack插件。既然是缓存,所以当然要第二次才能看到效果,第一次构建插件就会默认把缓存结果存到node_modules下的.cache目录下,第二次构建的时候再取出缓存使用。它还可以配置缓存目录,缓存时间,缓存资源的最大尺寸等等。下面我们看下它的一些配置和使用方法:

new HardSourceWebpackPlugin({
  // Either an absolute path or relative to webpack's options.context.
  cacheDirectory: 'node_modules/.cache/hard-source/[confighash]',
  // Either a string of object hash function given a webpack config.
  configHash: function(webpackConfig) {
    // node-object-hash on npm can be used to build this.
    return require('node-object-hash')({sort: false}).hash(webpackConfig);
  },
  // Either false, a string, an object, or a project hashing function.
  environmentHash: {
    root: process.cwd(),
    directories: [],
    files: ['package-lock.json', 'yarn.lock'],
  },
  // An object.
  info: {
    // 'none' or 'test'.
    mode: 'none',
    // 'debug', 'log', 'info', 'warn', or 'error'.
    level: 'debug',
  },
  // Clean up large, old caches automatically.
  cachePrune: {
    // Caches younger than `maxAge` are not considered for deletion. They must
    // be at least this (default: 2 days) old in milliseconds.
    maxAge: 2 * 24 * 60 * 60 * 1000,
    // All caches together must be larger than `sizeThreshold` before any
    // caches will be deleted. Together they must be at least this
    // (default: 50 MB) big in bytes.
    sizeThreshold: 50 * 1024 * 1024
  },
})

8、webpack-bundle-analyz

工欲善其事必先利其器,我们想要对webpack构建或者构建的资源做一些优化,那我们首先得知道,优化的方向在哪?很多的优化建议可能只是针对某些项目有效,不一定适合你的项目。所以,我们需要借助一些工具,通过工具产出的数据来指导我们进行构建的优化。webpack-bundle-analyz,这个插件可以让我们了解到构建出的文件的尺寸大小、依赖哪些模块等等,下面是一张效果图:

webpack bundle analyzer zoomable treemap

通过图我们可以知道:

  • 能够看到输出的bundle依赖哪些模块
  • 找出你的bundle中尺寸最大的模块
  • 找出是不是因为失误而引多余的依赖模块
  • 知道怎么去优化你的bundle

它的使用也很简单:

const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin;

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

当然它有一些的具体参数可以配置,详情参考webpack-bundle-analyz

9、speed-measure-webpack-plugin

上面我们介绍了一个可以对webpack构建出的bundle进行分析的插件,从而找到合适的一些优化途径。这里要介绍的是另一个测量插件--speed-measure-webpack-plugin,它的作用是测量webpack构建过程中使用的loader和各插件所花费的时间,从而让我们清楚地知道webpack构建的时间主要花在什么地方。下面是一张使用这个插件生成的效果图:

Preview of Speed Measure Plugin's output

通过图,我们可以很清楚地了解到每个插件和loader所花费的时间。我们来看下它的使用:

const SpeedMeasurePlugin = require("speed-measure-webpack-plugin");

const smp = new SpeedMeasurePlugin();

module.export = smp.wrap({
  plugins: [
    new MyPlugin(),
    new MyOtherPlugin()
  ]
})

使用也非常简单,如果你还想了解更多的配置项,可以查看SpeedMeasurePlugin

10、preload-webpack-plugin

这个插件的作用是向html中注入<link rel='preload|prefetch'> 标签,从而到达优化页面静态资源预加载的功能,而且支持异步的chunk。此插件必须配合html-webpack-plugin插件使用,而且配置的时候紧跟其后,下面是一个简单的配置:

const PreloadWebpackPlugin = require('preload-webpack-plugin');
const HtmlWebpackPlugin = require('html-webpack-plugin');

module.exports = [
  plugins: [
  	new HtmlWebpackPlugin(),
  	new PreloadWebpackPlugin()
	]
]

使用之后的效果:

preloads-plugin-compressor

在vue-cli3中就内置了这个插件,默认帮你将资源优先级高的资源加上preload的功能。更多配置参考:preload-webpack-plugin