1、基本写法及使用
这里用到 emit 钩子 及make 钩子,前者是串行后者是并行
/*** 1.webpack加载webpack.config.js中所有配置,此时就会new TestPlugin(),执行插件的constructor2.webpack创建compiler对象3.遍历所有plugins中插件,调用插件的apply方法4.执行剩下编译流程《触发各个hooks事件)*/class TestPlugin {constructor() {console.log('testPlugin-constructor');}apply(compiler) {console.log('testPlugin-apply');// 由文档可知,environment是同步钩子,所以需要使用tap注册compiler.hooks.environment.tap("TestPlugin",() => (console.log("TestPlugin environment")))// 由文档可知,emit是异步串行钩子 AsyncSeriesHook// 串行则顺讯执行compiler.hooks.emit.tap("TestPlugin", (compilation) => {console.log("TestPlugin emit 111");})compiler.hooks.emit.tapAsync("TestPlugin", (compilation, callback) =>{setTimeout(() => {console.log("Testplugin emit 222");callback();}, 2000)})compiler.hooks.emit.tapPromise("TestPlugin", (compilation) => {return new Promise((resolve) => {setTimeout(() => {console.log("TestPlugin emit 333"); resolve();},1000);})})// 由文档可知,make是异步并行钩子 AsyncParallelHookcompiler.hooks.make.tapAsync("TestPlugin", (compilation, callback) => {compilation.hooks.seal.tap("TetsPlugin", ()=>{console.log("TestPlugin seal");})setTimeout(() => {console.log("Testplugin make 111");callback();}, 3000);})compiler.hooks.make.tapAsync("TestPlugin", (compilation, callback) => {setTimeout(() => {console.log("Testplugin make 222");callback();}, 1000);})compiler.hooks.make.tapAsync("TestPlugin", (compilation, callback) => {setTimeout(() => {console.log("Testplugin make 333");callback();}, 2000);})}
}
module.exports = TestPlugin
webpack.config.js中的配置
// 引入插件
const TestPlugin = require('./plugins/test-plugin')// 使用插件
new TestPlugin()
打印结果
2、BannerPlugin
class BannerWebpackPlugin {constructor(options = {}) {this.options = options;}apply(compiler) {// 在资源输出之前触发compiler.hooks.emit.tap("BannerWebpackPlugin", (compilation) => {// debugger;const extensions = ["css", "js"];// 1. 获取即将输出的资源文件:compilation.assets// 2. 过滤只保留js和css资源const assets = Object.keys(compilation.assets).filter((assetPath) => {// 将文件名切割 ['xxxx', 'js'] ['xxxx', 'css']const splitted = assetPath.split(".");// 获取最后一个文件扩展名const extension = splitted[splitted.length - 1];// 判断是否保护return extensions.includes(extension);});const prefix = `/*
* Author: ${this.options.author}
*/
`;// 3. 遍历剩下资源添加上注释// console.log(assets);assets.forEach((asset) => {// 获取原来内容const source = compilation.assets[asset].source();// 拼接上注释const content = prefix + source;// 修改资源compilation.assets[asset] = {// 最终资源输出时,调用source方法,source方法的返回值就是资源的具体内容source() {return content;},// 资源大小size() {return content.length;},};});});}
}module.exports = BannerWebpackPlugin;
3、CleanWebpackPlugin
class CleanWebpackPlugin {apply(compiler) {// 2. 获取打包输出的目录const outputPath = compiler.options.output.path;const fs = compiler.outputFileSystem;// 1. 注册钩子:在打包输出之前 emitcompiler.hooks.emit.tap("CleanWebpackPlugin", (compilation) => {// 3. 通过fs删除打包输出的目录下的所有文件this.removeFiles(fs, outputPath);});}removeFiles(fs, filepath) {// 想要删除打包输出目录下所有资源,需要先将目录下的资源删除,才能删除这个目录// 1. 读取当前目录下所有资源const files = fs.readdirSync(filepath);// console.log(files); // [ 'images', 'index.html', 'js' ]// 2. 遍历一个个删除files.forEach((file) => {// 2.1 遍历所有文件,判断是文件夹还是文件const path = `${filepath}/${file}`;const fileStat = fs.statSync(path);// console.log(fileStat);if (fileStat.isDirectory()) {// 2.2 是文件夹,就得删除下面所有文件,才能删除文件夹this.removeFiles(fs, path);} else {// 2.3 是文件,直接删除fs.unlinkSync(path);}});}
}module.exports = CleanWebpackPlugin;
4、AnalyzeWebpackPlugin
class AnalyzeWebpackPlugin {apply(compiler) {compiler.hooks.emit.tap("AnalyzeWebpackPlugin", (compilation) => {// 1. 遍历所有即将输出文件,得到其大小/*将对象变成一个二维数组:对象:{key1: value1,key2: value2 }二维数组:[[key1, value1],[key2, value2]]*/const assets = Object.entries(compilation.assets);/*md中表格语法:| 资源名称 | 资源大小 || --- | --- || xxx.js | 10kb |*/let content = `| 资源名称 | 资源大小 |
| --- | --- |`;assets.forEach(([filename, file]) => {content += `\n| ${filename} | ${Math.ceil(file.size() / 1024)}kb |`;});// 2. 生成一个md文件compilation.assets["analyze.md"] = {source() {return content;},size() {return content.length;},};});}
}module.exports = AnalyzeWebpackPlugin;
生成md文件
5、InlineChunkWebpackPlugin
让 小的js 文件直接内联到 html中
const HtmlWebpackPlugin = require("safe-require")("html-webpack-plugin");class InlineChunkWebpackPlugin {constructor(tests) {this.tests = tests;}apply(compiler) {compiler.hooks.compilation.tap("InlineChunkWebpackPlugin", (compilation) => {// 1. 获取html-webpack-plugin的hooksconst hooks = HtmlWebpackPlugin.getHooks(compilation);// 2. 注册 html-webpack-plugin的hooks -> alterAssetTagGroupshooks.alterAssetTagGroups.tap("InlineChunkWebpackPlugin", (assets) => {// 3. 从里面将script的runtime文件,变成inline scriptassets.headTags = this.getInlineChunk(assets.headTags, compilation.assets);assets.bodyTags = this.getInlineChunk(assets.bodyTags, compilation.assets);});// 删除runtime文件hooks.afterEmit.tap("InlineChunkWebpackPlugin", () => {// 3. 从里面将script的runtime文件,变成inline scriptObject.keys(compilation.assets).forEach((filepath) => {if (this.tests.some((test) => test.test(filepath))) {delete compilation.assets[filepath];}});});});}getInlineChunk(tags, assets) {/*目前:[{tagName: 'script',voidTag: false,meta: { plugin: 'html-webpack-plugin' },attributes: { defer: true, type: undefined, src: 'js/runtime~main.js.js' }},]修改为:[{tagName: 'script',innerHTML: runtime文件的内容closeTag: true },]*/return tags.map((tag) => {if (tag.tagName !== "script") return tag;// 获取文件资源路径const filepath = tag.attributes.src;if (!filepath) return tag;if (!this.tests.some((test) => test.test(filepath))) return tag;return {tagName: "script",innerHTML: assets[filepath].source(),closeTag: true,};});}
}module.exports = InlineChunkWebpackPlugin;