随着前端项目日渐的复杂,我们日常开发中使用的构建工具也不断演变、进化,既要满足我们开发时的便利,还要充当构建时各种资源优化、自动打包、自动测试的角色。目前,最受欢迎的当数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
接下来要介绍的是DllPlugin和DllReferencePlugin的结合,这两个插件必须配合使用才能发挥其巨大的作用,主要的作用是提升你开发时的构建速度。它的思路是这样的,首先使用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,这个插件可以让我们了解到构建出的文件的尺寸大小、依赖哪些模块等等,下面是一张效果图:

通过图我们可以知道:
- 能够看到输出的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构建的时间主要花在什么地方。下面是一张使用这个插件生成的效果图:

通过图,我们可以很清楚地了解到每个插件和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()
]
]
使用之后的效果:

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