这里用到 emit 钩子 及make 钩子,前者是串行后者是并行
/**
* 1.webpack加载webpack.config.js中所有配置,此时就会new TestPlugin(),执行插件的constructor
2.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是异步并行钩子 AsyncParallelHook
compiler.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()
打印结果
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;
class CleanWebpackPlugin {
apply(compiler) {
// 2. 获取打包输出的目录
const outputPath = compiler.options.output.path;
const fs = compiler.outputFileSystem;
// 1. 注册钩子:在打包输出之前 emit
compiler.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;
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文件
让 小的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的hooks
const hooks = HtmlWebpackPlugin.getHooks(compilation);
// 2. 注册 html-webpack-plugin的hooks -> alterAssetTagGroups
hooks.alterAssetTagGroups.tap("InlineChunkWebpackPlugin", (assets) => {
// 3. 从里面将script的runtime文件,变成inline script
assets.headTags = this.getInlineChunk(assets.headTags, compilation.assets);
assets.bodyTags = this.getInlineChunk(assets.bodyTags, compilation.assets);
});
// 删除runtime文件
hooks.afterEmit.tap("InlineChunkWebpackPlugin", () => {
// 3. 从里面将script的runtime文件,变成inline script
Object.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;