Webpack常用插件索引
这篇文章用来梳理Webpack使用各个流程中常用的一些插件并长期更新,以便以后使用的时候能够快速索引。
自动化
html-webpack-plugin
这个插件的功能可以说就是Webpack项目成功启动的最后一步。毕竟资源无论怎么样打包,最终成品都会输出为JS,CSS和静态资源,而这些文件必然要被HTML引用才能在浏览器中作为项目启动,这个插件的作用就是如此,将生成HTML模板并插入打包后的文件的流程自动化。使用方法相当简单,直接在plugins选项中实例化即可。
const webpackConfig = {
...
plugins: [
new HtmlWebpackPlugin()
]
};
如果不传入任何配置项,会将通过entry
配置的所有入口thunk和extract-text-webpack-plugin
抽出的css文件都插入到一个默认的html文件中,当然我们也可以通过一些配置项使这个过程实现得更灵活。
配置项
- title: 给HTML模板注入title标签,需要注意的是如果模板中已有title,则不会替换。
- filename: 输出HTML的文件名称 ,默认为
index.html
,还可以指定输入位置,例如src/index.html
,路径相对于webpackConfig.output.path
。 - template: 指定自定义的HTML模板,支持加载器配置如ejs,undersore,jade等。
- inject: 在模板中注入静态资源的位置。当为true或body时JS资源会被注射到body底部,head会注射到head中,false则不会注入。
- favicon: 注射favicon图标文件的路径。
- meta: 注射自定义meta标签。
- minify: 传递给 html-minifier 的选项用于压缩html。
- hash: 为所有注入的资源在webpack中的每次编译生成唯一hash,这一点在破坏缓存时很有用,比如每次发布时,自然不希望用户仍然使用缓存的旧文件。
- showErrors: 将报错输出到html页面中。这个在开发时比较方便,便于调试。
- chunks: 自定义插件到模板的一些chunk。不配置时则默认为所有。
- chunksSortMode: 控制chunk插入顺序。
- excludeChunks: 和chunks相反,配置不插入的chunks
- xhtml: 将标签渲染为自闭合。
下面分别介绍几种该插件的常用场景。
多页配置
在单页应用中如果需要多个页面入口,多页应用配置多个入口时,或者生成主入口或测试入口时,多页配置就能派上用场。使用也很简单,在plugins配置项中实例化多次传入不同配置项即可。
const webpackConfig = {
...
plugins: [
new HtmlWebpackPlugin({
filename: 'index.html',
template: 'src/assets/index.html',
...
}),
new HtmlWebpackPlugin({
filename: 'test.html',
template: 'src/assets/test.html',
...
})
]
};
自定义通用模板
大部分情况下,将thunk插入到html中可能就已经够用了,但有时候我们仍然需要更灵活的插入配置,例如多页面入口时,可能入口模板大部分数据是一样的,只是少部分数据根据配置会不一样,这种情况下很容易想到使用模板引擎来实现,也就是自定义通用模板了。例子如下:
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-type" content="text/html; charset=utf-8"/>
<title><%= htmlWebpackPlugin.options.title %></title>
</head>
<body>
</body>
</html>
可以看到在以上模板中我们使用了htmlWebpackPlugin数据,模板语法中能够访问到插件提供的相关数据来差异化生成HTML。具体能访问到的内容如下:
- compilation: webpack编译对象,这是webpack每次编译都会生成的保存编译相关内容的对象,在webpack工作流程介绍中也提到过,详细内容可以查阅相关资料。
- webpack: webpack的html文件生成时对应的stats对象。
- webpackConfig: webpack的相关配置项。
- htmlWebpackPlugin: 该插件工作时对应的数据内容,包括配置的注射chunk和css文件以及传递给插件的配置项。
事件钩子
我们前面已经提到过,webpack整套机制都是运行于插件事件机制之上的。htmlwebpackplugin插件同样也提供了一些事件钩子,供我们在该插件运行过程中使用其他插件自定义一些逻辑。事件具体如下:
- 同步钩子:htmlWebpackPluginAlterChunks
- 异步钩子:htmlWebpackPluginBeforeHtmlGeneration, htmlWebpackPluginBeforeHtmlProcessing,htmlWebpackPluginAlterAssetTags,htmlWebpackPluginAfterHtmlProcessing,htmlWebpackPluginAfterEmit
clean-webpack-plugin
随着每次构建,构建目录中会持续积累文件,这肯定不是开发者想要的。这个问题当然可以通过npm脚本解决,但更合理的办法还是将所有特性都集成到webpack的工作中,这个插件就是用来做这件事。使用上也没什么特别需要注意的,配置目录实例化即可。
copy-webpack-plugin
有的时候某些数据可能并不需要webpack直接处理,但需要带到当前构建目录中使用,这种情况下就需要用到该插件来执行复制粘贴操作。使用上通常在实例化时传入源文件地址和目录地址即可。
ProvidePlugin
该插件用来设置自由变量和模块依赖的对应关系,当主代码中使用到自由变量时,能够自动化加载对应依赖。实例如下:
new webpack.ProvidePlugin({
$: 'jquery',
jquery: 'jquery'
})
然后在我们的主代码中使用到$
和jQuery
的部分都能够自动化引用jquery
模块了。
开发优化
HotModuleReplacementPlugin
HMR可以说是Webpack的看家特性之一了,主要表现就是当你对代码进行修改并保存后,在不刷新浏览器的情况下就能对应用进行更新,大大加快了开发效率。在HMR出现之前,主要是通过live reload监听文件变化,然后在浏览器端刷新页面,而HMR相对于其的优势在于更新手段并不是刷新页面只是实现局部替换,由此可以保存应用的当前状态,保证开发的流畅性。
使用方法
由于结合webpack开发时通常配合webpack-dev-server
使用,其内部已经集成了HMR相关功能,所以使用起来相当简单,只需要两步:
- 在plugin选项中实例化HotModuleReplacementPlugin
- devServer选项中将hot选项设置为true
如此就开启了HMR,但需要注意一点,这只是HMR的前置工作,要在业务代码中实现指定模块的HMR还需要使用webpack提供的HMR相关API,好在目前社区中已经有工具帮我们自动化了这一过程,比如React Hot Loader
,Vue-loader
等,这样一套工作流能够完全自动化HMR相关配置,使得我们集中精力于业务逻辑中。
原理
webpack-dev-server
中的webpack-dev-middleware
调用webpack的API监听文件系统,当模块文件变动时,webpack重新编译打包并利用memory-fs
输出到内存中而不输出到磁盘,减少写入开销。- 启动
webpack-dev-server时
,会利用sockjs
在服务端和浏览器端建立一个webSocket
长连接,当重编译完成后,通过_sendStatus
方法将新模块的hash值发送到客户端。 webpack-dev-server/client
修改webpack配置的entry
使得客户端bundle中有接收webSocket
消息的逻辑代码,当客户端bundle接收到hash型消息后将其暂存,当接受到ok型消息后执行接下来的HMR步骤,如果未配置HMR,则回退到reload方法刷新页面。- 上一步
webpack-dev-server/client
接受到ok型消息后,发送webpackHotUpdate
消息给webpack/hot/dev-server
调用HMR runtime中的check方法检测是否在新的模块中有代码更新。该方法会向服务端发送hotDownloadManifest
请求确认所有更新模块的hash值列表,然后再根据该列表使用名为hotDownloadUpdateChunk
的jsonp请求获取最新模块代码。 - HMR runtime执行HMR替换步骤,首先找出过期模块和过期依赖,再从缓存从删除,最后添加新的模块。如果替换过程中出现错误,则回退到reload方法刷新浏览器。
error-overlay-webpack-plugin
我们知道配置devServer时可以通过overlay选项设置编译时错误提示,通过该插件可以更加精确和美观的显示错误来源。使用同样只需要实例化。
new ErrorOverlayPlugin()
构建时优化
duplicate-package-checker-webpack-plugin
当项目变大时,一种很可能出现的情况是,由于依赖复杂,某个包的不同版本被多次引用导致重复打包,该插件就会发出警告。
HappyPack
当项目变大时,构建过程中就会出现构建速度过慢的问题,毕竟构建是对文件进行读写和密集型操作的过程。HappyPack就是为了解决这一点。
原理
构建流程中,最耗时的任务是loader中的文件转换操作,而Webpack的运行又是基于JS的单线程模型,使得文件转换只能单队列处理,由此造成了性能瓶颈。HappyPack将这部分任务分解到多个并行进程,其核心调度器的逻辑在主进程中,每一次实例化该插件时,都会分配一个空闲子进程处理loader任务,处理完毕后通过进程通信通知核心调度器,核心调度器再通知Webpack。
配置
简单的示例用法配置如下:
module: {
rules: [
{
test: /\.js$/,
use: ['happypack/loader?id=babel']
},
{
test: /\.css$/,
use: ['happypack/loader?id=css']
}
]
},
plugins: [
new HappyPack({
id: 'babel',
loaders: [
...
]
}),
new HappyPack({
id: 'css',
loaders: [
...
]
})
]
可以看到和平常配置loaders时的主要几点区别如下:
- loader配置中将处理代理给happypack并通过id确认使用哪个happypack实例处理
- happypack实例化中传入id标识对应真实loader配置,loader选项和真实loader配置完全相同,happypack插件可以看作是一层代理
除了如上简单配置外,还可以通过相关参数更精确的配置处理不同loader的进程数量或者共享进程处理,详细使用方法就不赘述了,可查阅官方文档。
DllPlugin/DllReferencePlugin
原理
在Windows操作系统中有很多.dll类型的文件,这种文件被称作动态链接库,即可以在其他模块中被调用的函数。这个插件在Webpack中的作用也是如此,使用该插件可以生成一个manifest.json
文件存储依赖模块索引并将依赖模块单独打包,打包后的文件本身不能运行,只是给主代码通过manifest.json
存储的索引关系来引用。
这样抽出依赖代码的构建形式有如下几点好处:
- 只要依赖库没有变化,主代码更新后再构建速度能够明显提升
- 如果多个项目有重叠依赖库,可以共用dll管理依赖
- 主代码更新时如果不更新依赖,依赖bundle的hash不会变化,降低更新频率,用户能够有效利用缓存加载依赖bundle
使用
这个插件的使用分两步,第一步先使用DllPlugin
打包依赖,生成manifest.json
。简单配置如下,假设依赖为antd
和react
等。
const webpack = require('webpack');
const vendors = ['antd', 'react', ...];
module.exports = {
output: {
path: 'build',
filename: '[name].[chunkhash].js',
library: '[name]_[chunkhash]' // dll包名,用于主代码调用
},
entry = {
vendor: vendors
},
plugins: [
new webpack.Dllplugin({
path: 'manifest.json', // 存放manifest.json的路径
name: '[name]_[chunkhash]', // dll暴露对象名,需要和library保持一致
context: __dirname // 解析包路径的上下文,需要和接下来配置的dll user保持一致
})
]
};
运行后会输出两个文件,一个是依赖bundle,一个是记录索引关系的manifest.json
,数据结构如下:
{
"name": "vendor_....",
"content": {
"./node_modules/antd/dist/antd.js": 1,
"./node_nodules/react/react.js": 2,
...
}
}
可以看到对每个依赖都设置了id索引,之后的dll user就是通过该id来查找引用。下面设置dll user的打包配置,这一步需要用到DllReferencePlugin
查找引用。
const webpack = require('webpack');
module.exports = {
output: {
path: 'build',
filename: '[name].[chunkhash].js'
},
entry: {
app: './src/index.js'
},
plugins: [
new webpack.DllReferencePlugin({
context: __dirname, // 和前一步打包保持一致
manifest: require('./manifest.json')
})
]
}
至此就完成了三方依赖和主代码的分离打包。
构建后优化
OccurenceOrderPlugin
该插件的作用是为模块分配ID,使用频率越多的模块ID越小,被查找到的速度就会越快。使用很简单,直接实例化即可。
new webpack.optimize.OccurenceOrderPlugin()
mini-css-extract-plugin
在Webpack3中使用ExtractTextPlugin
提取公共CSS模块为单一CSS文件,Webpack4中使用了该插件取代。该插件应该只用于生产环境并且不能和styles-loader
共同作用。使用起来也很简单,在插件选项中实例化后,再在rules配置链中设置即可。
plugins: [
new MiniCssExtractPlugin({
filename: '[name].css'
})
],
module: {
rules: [
{
test: /\.css$/,
use: [
MiniCssExtractPlugin.loader,
'css-loader'
]
}
]
}
purifycss-webpack
类似于tree shaking,对于CSS也有移除未使用部分的功能就是PurifyCSS
,该插件就是在Webpack中集成该功能。使用时需要注意的几点如下:
- 不能和CSS Modules类共同作用,可以通过whitelist选项设置白名单隔离CSS Modules作用部分。
- 必须先提取CSS再purify化。
- 使用该特性会丢失CSS source map。
uglifyjs-webpack-plugin
生产环境中压缩代码减小体积可以说是必备的,webpack4默认使用该插件处理这个问题。使用上需要配合webpack选项的optimization。实例如下:
{
optimization: {
minimizer: [new UglifyWebpackPlugin({sourceMap: true})]
}
}
需要注意如果构建时启用了sourceMap,在使用该插件压缩时也应该传入sourceMap选项。
optimize-css-assets-webpack-plugin
同样的,CSS压缩也是必不可少的一部分,目前最好的解决方案就是该插件,其可以将选定的压缩器应用于CSS,通常使用cssnano压缩器来处理。实例如下:
{
plugins: [
new OptimizeCSSAssetsPlugin({
cssProcessor: cssnano
})
]
}
DefinePlugin
应用中很可能会有区分生产环境和开发环境执行的代码,比如生产环境中记录日志,开发环境中开启实验性功能等等。通过控制环境变量,区分出这部分代码打包能够很好的优化性能,这个插件就是做这件事。常用场景可以分为如下几类。
- 区分环境
new webpack.DefinePlugin({
'process.env.NODE_ENV': JSON.stringify(process.env.NODE_ENV),
'process.env.DEBUG': JOSN.stringify(process.env.DEBUG)
})
也可以通过EnvironmentPlugin
更方便的做这件事,该插件相当于DefinePlugin
针对于区分环境的一个上层封装。
new webpack.EnvironmentPlugin(['NODE_ENV', 'DEBUG'])
经过变量替换后,判断为false的条件语句后续将不会被打包到最终代码,这也是该插件优化性能的核心。
- 特征功能标记
new webpack.DefinePlugin({
'NICE_FEATURE': JSON.stringify(true),
'EXPERIMENTAL_FEATURE': JSON.stringify(false)
})
- 不同环境服务URL区分
new webpack.DefinePlugin({
'SERVICE_URL': JSON.stringify("http://dev.example.com")
})
stats-webpack-plugin
分析构建统计信息有利于我们进一步优化打包,该插件可以灵活的管理统计信息的输出。
SplitChunksPlugin
在Webpack4中使用该插件替代了commonChunksPlugin来提取公共代码,并且提供了一份项目通用的默认配置,我们就从默认配置来分析这个插件的用法。
splitChunks: {
chunks: "async",
minSize: 30000,
minChunks: 1,
maxAsyncRequests: 5,
maxInitialRequests: 3,
automaticNameDelimiter: '~',
name: true,
cacheGroups: {
vendors: {
test: /[\\/]node_modules[\\/]/,
priority: -10
},
default: {
minChunks: 2,
priority: -20,
reuseExistingChunk: true
}
}
}
- chunks: 该选项有3个取值:
async
,initial
,all
。在解释这3个选项之前必须先知道chunks是什么。首先webpack通过入口文件entry
启动模块打包,打包过程首先定义了runtime中webpack要用到的函数以便后续过程使用,此时如果设置了多入口并有合理的公共代码提取配置,会将公共依赖抽离并和runtime代码打包形成一个新的代码块,此代码块就叫做entry chunk
,再然后分析入口文件并打包模块依赖所形成的代码块就叫做initial chunk
,而在initial chunk
中如果设置了异步加载分割点,会从分割点再抽出chunk打包,形成的代码块就叫做async chunk
。 - minSize/minChunks/maxInitialRequests/maxAsyncRequests: 当然,并不是所有公共代码块都需要被提取,这些选项限制了提取出公共代码块的条件。顾名思义,就是公共代码的体积,引用次数和并行请求数。
- name: 该选项用来控制公共代码块命名,当不同公共代码块被赋予相同名称时会被合并。如果被设置为true,webpack会基于缓存组的key自动设置名称。
- cacheGroups: 该选项可以配置提取公共代码的细节设定,和
splitChunks
相同key的配置会默认继承。数据结构为一个对象,键值对则代表着提取公共代码名称和对应的提取配置。如下三个配置是缓存组特有的:
- test: 控制哪些模块被缓存组匹配到并按其规则提取,如果不设置则默认为所有模块。
- priority: 当模块被多个缓存组匹配到时,则按该优先级设置值较高的规则提取。
- reuseExistingChunk: 设置是否允许复用已经存在的代码块。
-- EOF --