Writing a custom webpack plugin
All webpack users have used one or more plugins in their webpack config, like HtmlWebpackPlugin, CopyWebpackPlugin, DefinePlugin etc for different use cases in the build process.
Though you will most probably find a plugin in the npm registry that match your needs but still if you want you can create your own custom plugin and publish it for others to use :)
Creating Webpack plugin
Let's go over a custom webpack plugin example to understand implementation details. We will create a plugin to monitor the change in build asset sizes from the previous build. Let’s call it BuildInfoPlugin.
Create a class or constructor function called BuildInfoPlugin.
class BuildInfoPlugin {
}
or function BuildInfoPlugin() {
}
Add the apply method to the class or function prototype
class BuildInfoPlugin {
constructor() {} // complier object received as argument in apply call gives hooks to plug into different life cycle stages
apply(compiler) {}}orBuildInfoPlugin.prototype.apply = function(compiler) {}
compiler.hook provides a number of lifecycle methods that you can tap into to do your plugin-specific work. For a full list refer to this.
To compare build sizes before and after we need to read sizes of assets in the build folder before the compile starts and after webpack has compiled and emitted results to the build folder. We can refer to the list of hooks available and see which ones match our requirements. I found beforeCompile and afterEmit hooks match the requirements here. Let’s add these to apply method.
apply(compiler) {
compiler.hooks.compile.tap('ReadBundleSizeBefore', () => {
// implement this to read all files sizes at start
});
compiler.hooks.afterEmit.tap('ReadBundleSizeAfter', () => {
// implement this to read all files sizes after compilation
});
}
Using node’s filesystem we can add logic to read files in the build folder, save them to instance variables and give a callback at the end.
See the full implementation below.
class BuildInfoPlugin {
constructor(pathToAssets, callback) {
this.prevBuildInfo = {};
this.currBuildInfo = {};
this.pathToAssets = pathToAssets;
this.callback = callback;
}
apply(compiler) {
compiler.hooks.beforeCompile.tap('ReadBundleSizeBefore', () => {
try {
const filenames = fs.readdirSync(this.pathToAssets);
filenames.forEach(f => {
const stats = fs.statSync(`${this.pathToAssets}/${f}`);
this.prevBuildInfo[f] = stats.size;
fs.unlinkSync(`${this.pathToAssets}/${f}`);
});
} catch (e) {
console.log('ReadBundleSizeBefore - > error', e);
}
});
compiler.hooks.afterEmit.tap('ReadBundleSizeAfter', () => {
try {
const filenames = fs.readdirSync(this.pathToAssets);
filenames.forEach(f => {
const stats = fs.statSync(`${this.pathToAssets}/${f}`);
this.currBuildInfo[f] = stats.size;
});
this.callback && this.callback(this.prevBuildInfo,
this.currBuildInfo);
} catch (e) {
console.log('ReadBundleSizeAfter - > error', e);
}
});
}
}
From webpack config, we can call our plugin like this
new BuildInfoPlugin('./build', (prevBuildInfo, currBuildInfo) => {
// we have previous and current assest sizes as arguments
// up to you how you want to console it :D})
and That’s all!