Webpack 通过 Plugin 机制让其更加灵活,以适应各种应用场景。 在 Webpack 运行的生命周期中会广播出许多事件,Plugin 可以监听这些事件,在合适的时机通过 Webpack 提供的 API 改变输出结果。
1. 基础的plugin
一个最基础的 Plugin 的代码是这样
javascript
class BasicPlugin{
// 在构造函数中获取用户给该插件传入的配置
constructor(options){
}
// Webpack 会调用 BasicPlugin 实例的 apply 方法给插件实例传入 compiler 对象
apply(compiler){
compiler.plugin('compilation',function(compilation) {
})
}
}
// 导出 Plugin
module.exports = BasicPlugin;
在使用这个 Plugin 时,相关配置代码如下:
ini
const BasicPlugin = require('./BasicPlugin.js');
module.export = {
plugins:[
new BasicPlugin(options),
]
}
Webpack 启动后,在读取配置的过程中会先执行 new BasicPlugin(options)
初始化一个 BasicPlugin 获得其实例。 在初始化 compiler 对象后,再调用 basicPlugin.apply(compiler)
给插件实例传入 compiler 对象。 插件实例在获取到 compiler 对象后,就可以通过 compiler.plugin(事件名称, 回调函数)
监听到 Webpack 广播出来的事件。 并且可以通过 compiler 对象去操作 Webpack。
2. Compiler和Compilation
在webpack的插件开发时,最经常用到的就是Compiler和Compilation,他们是webpack和plugin之间的桥梁
- Compiler包含了所有webpack的配置信息,包含options、loader、plugins等信息。这个对象在Webpack启动时被实例化,它是全局唯一的,可以简单地将它理解为Webpack的实例
- Compilation对象包含了当前的模块资源、编译生成资源、变化的文件等。当Webpack以开发模式运行时,每当检测一个文件发生变化,便有一次新的Compilation被创建。Compilation对象也提供了很多事件回调供插件进行扩展。通过Compilation也能读取到Compiler对象
两者的区别可以理解为:Compiler代表了整个Webpack从启动到关闭的生命周期,Compilation只代表一次新的编译
3. 事件流
Webpack就像一条生产线,要经过一系列处理流程才能将源文件转化成输出结果。这个生产线上每个流程的职责都是单一的,多个流程之间存在依赖关系,只有在完成了当前处理后才能提交给下一个流程处理。
Webpack在运行过程中会广播事件,插件只需要监听它关心的事情,就能加入这条生产线了,这就是插件的运行原理。
我们可以通过Compiler和Compilation对象来广播和监听事件
csharp
// 广播事件
// event-name为事件名称,注意不要和现有的事件重名
// params为附带的参数
compiler.apply('event-name',params)
// 监听名称为event-name的事件,当event-name事件发生时,函数就会被执行
// 同时函数中的params参数为广播事件附带的参数
compiler.plugin('event-name',function(params){})
同理,compilation.apply和compilation.plugin的使用方法和前面讲解一致
4. 开发一个插件
通过前面的知识,我们已经可以开发一个简单的插件了,比如当Webpack成功编译和输出文件后执行发布操作,将输出的文件上传到服务器,同时该插件还能区分Webpack构建是否成功。
要实现该插件,需要借助以下两个事件:
- done:在成功构建并且输出文件后,Webpack即将退出时发生
- failed:在构建出现异常时导致构建失败,Webpack即将退出时发生
以下是该插件的用法
javascript
module.exports = {
plugins: [
//在初始化EndWebpackPlugin时传入两个参数,分别是成功时的回调函数和失败时的回调函数
new EndWebpackPlugin(() => {
//Webpack构建成功,并且文件在输出时会执行到这里,这里就可以做发布文件操作
},(err) => {
//Webpack构建失败,err是导致错误的原因
console.error(err)
})
]
}
以下是实现这个插件
javascript
class EndWebpackPlugin {
constructor(doneCallback,failCallback){
// 保存在构造函数中传入的回调函数,即我们在webpack.config.js实例化EndWebpackPlugin时传进来的两个参数
this.doneCallback = doneCallback
this.failCallback = failCallback
}
apply(compiler){
compiler.plugin('done',(stats) => {
// 在done事件中回调doneCallback
this.doneCallback(stats)
}),
compiler.plugin('failed',(err) => {
// 在failed事件中回调failCallback
this.failCallback(err)
})
}
}
// 导出插件
module.exports = EndWebpackPlugin
我们还差最后一步,就是实现doneCallback,即将我们打包后的文件上传到我们的服务器,我们可以利用axios,通过post请求将打包出来的文件发送到我们服务器
javascript
//完整代码
//webpack.config.js
const axios = require('axios')
module.exports = {
plugins: [
//在初始化EndWebpackPlugin时传入两个参数,分别是成功时的回调函数和失败时的回调函数
new EndWebpackPlugin((compilation,callback) => {
var url = 'http://10.0.0.0:78/upload' //服务器的url
var upload = async function(name,content){
await axios.post(url,{name,content})
}
for(let filename in compilation.assets){
let content = compilation.assets[filename]['source']()
upload(filename,content)
callback()
}
},(err) => {
//Webpack构建失败,err是导致错误的原因
console.error(err)
})
]
}
//uploade.js
class EndWebpackPlugin {
constructor(doneCallback,failCallback){
// 保存在构造函数中传入的回调函数,即我们在webpack.config.js实例化EndWebpackPlugin时传进来的两个参数
this.doneCallback = doneCallback
this.failCallback = failCallback
}
apply(compiler){
compiler.plugin('done',() => {
// 在done事件中回调doneCallback
this.doneCallback()
}),
compiler.plugin('failed',(err) => {
// 在failed事件中回调failCallback
this.failCallback(err)
})
}
}
// 导出插件
module.exports = EndWebpackPlugin