插件提供给第三方开发人员可以完全深入处理 webpack 引擎处理的数据。
定义一个插件的要求
- 定义一个函数或类
- 包含一个 apply 方法的属性
- 在 event hooks 上绑定 tap 方法
- 处理 webpack 的各种数据
- 最后调用 callback
插件基本的结构
// my-plugin.js
class MyPlugin {
/**
* 当 webpack 编译器在安装该插件时 apply 方法就会调用
*/
apply(compiler) {
compiler.hooks.done.tap(
'MyPlugin',
(stats) => {
console.log('test', stats.compilation.modules[0])
}
)
}
}
module.exports = MyPlugin
// webpack.config.js
var MyPlugin = require('./my-plugin')
module.exports = {
// ...
plugins: [
new MyPlugin()
]
}
Tap TapAsync TapPromise
给 hooks 绑定回调的方法
xxx.hooks.xxx.tap('{some name}', () => {})
每个 hook 都需要通过 tap 方法绑定它的回调,hook 有同步和异步之分,同步的使用 tap 方法绑定,异步的使用 tapAsync 或 tapPromise 绑定。
可从下面文档中查找每个 hook 的类型,常用类型有 SyncHook、SyncBailHook、AsyncSeriesHook
// tap
compiler.hooks.environment.tap('a', () => {
console.log('environment')
})
// tapAsync 必须调用作为最后一个参数的 callback,方法,否则就会在中断继续执行
compiler.hooks.run.tapAsync('b', (compiler, callback) => {
console.log('run b')
// callback()
})
// tapPromise 必须返回一个 promise
compiler.hooks.run.tapPromise('c', (compiler) => {
return new Promise(resolve => {
console.log('run c')
resolve()
})
}
如果给一个同步 hook 使用 tapAsync 方法会抛出如下错误
tapAsync is not supported on a SyncHook
compiler.hooks.environment.tapAsync('a', () => {
console.log('environment')
})
如果 tapPromise 不返回一个 promise 也会抛出如下错误
Tap function (tapPromise) did not return promise (returned undefined)
compiler.hooks.run.tapPromise('b', (compiler) => {
console.log('run b')
})
Compiler 和 Compilation
- Compiler 编译器
每个 Webpack 的实例都会创建一个 Compiler 对象,Compiler 把加载、打包、写入这些操作委托给注册的插件,它的 hooks 属性能将插件注册到 Compiler 生命周期中的任何钩子事件上。
- Compilation 编译
每个 Compiler 都会创建一个 Compilation 对象,同时只支持有一个并行的 Compilation 对象。每个 Compilation 对象代表编译的过程,它可以访问所有的模块及其依赖,在编译过程中模块会被加载、密封、优化、分块、散列和恢复,包含错误警告(如果有)、时间、模块和分块信息等内容
// webpack.js
const webpack = (options, callback) => {
// ...
const compiler = new Compiler(options.context)
// ...
if (callback) {
compiler.run(() => {
callback()
});
}
return compiler
}
// Compiler.js
class Compiler {
// ...
run() {
// ...
this.hooks.beforeRun.callAsync(this, err => {
if (err) return finalCallback(err);
this.hooks.run.callAsync(this, err => {
if (err) return finalCallback(err);
this.readRecords(err => {
if (err) return finalCallback(err);
this.compile(onCompiled);
});
});
});
// ...
}
compiler(callback) {
// ...
const compilation = new Compilation(this, params));
this.hooks.make.callAsync...
// ...
}
}
Compilation 在 Compiler 某些钩子的回调的参数中可以获取到,eg:compilation、emit、done...
compiler.hooks.compilation.tap('MyPlugin', (compilation) => {
// ...
compilation.xxx
});
- Compiler hooks 生命周期
生命周期主要包含 5 个部分,run -> compile -> make -> emit -> done
一个Demo
// For Webpack4
class CountAssetPlugin {
apply(compiler) {
compiler.hooks.emit.tap('CountAssetPlugin', (compilation) => {
let fileCount = 0
let totalFileSize = 0
Object.entries(compilation.assets).forEach(([pathname, source]) => {
fileCount++
totalFileSize += source.size()
})
console.log(`file ${fileCount} size ${totalFileSize}`)
})
}
}
module.exports = CountAssetPlugin
// For Webpack4
const { ConcatSource } = require("webpack-sources");
class MyBannerPlugin {
apply(compiler) {
compiler.hooks.compilation.tap('MyPlugin', (compilation) => {
compilation.hooks.afterOptimizeChunkAssets.tap('MyPlugin', (chunks) => {
// console.log(chunks.map(chunk => `${chunk.id}`))
chunks.forEach(chunk => {
chunk.files.forEach(file => {
compilation.updateAsset(
file,
old => {
return new ConcatSource(
'/\* Powered by MT \*/',
'\n',
old
)}
)
});
});
})
compilation.hooks.afterOptimizeAssets.tap('MyPlugin', (assets) => {
console.log(Object.keys(assets))
})
})
}
}
module.exports = MyBannerPlugin