Building a Custom Webpack Plugin

October, 6th 2024 2 min read

Webpack is incredibly flexible — not because of its configuration file, but because everything inside Webpack is powered by plugins. Creating your own plugin lets you hook into Webpack’s lifecycle, inspect files, modify assets, generate content, or automate tasks during builds.

This guide walks you through creating a simple but fully functional Webpack plugin, with human-friendly explanations and modern examples.


1. Project Setup

If you don’t have a project yet:

bash
mkdir custom-webpack-plugin
cd custom-webpack-plugin
npm init -y
npm install webpack --save-dev

2. Create a Plugin File

Create a plugins directory:

bash
mkdir plugins
touch plugins/CustomPlugin.js

3. Writing Your First Webpack Plugin

A Webpack plugin is just a class with an apply() method. Webpack calls apply() automatically and passes the compiler object, which exposes the entire build lifecycle.

js
class CustomPlugin {
  constructor(options) {
    this.options = options;
  }

  apply(compiler) {
    compiler.hooks.done.tap('CustomPlugin', stats => {
      console.log('Build is complete!');
      console.log('Message:', this.options.message);
    });

    compiler.hooks.emit.tapAsync(
      'CustomPlugin',
      (compilation, callback) => {
        console.log('Assets are being emitted...');
        callback();
      }
    );
  }
}

module.exports = CustomPlugin;

4. Register the Plugin in Webpack

Create webpack.config.js:

bash
touch webpack.config.js

Configure Webpack:

js
const CustomPlugin = require('./plugins/CustomPlugin');

module.exports = {
  mode: 'development',
  entry: './src/index.js',
  output: {
    filename: 'bundle.js',
    path: __dirname + '/dist',
  },
  plugins: [
    new CustomPlugin({
      message: 'Hello from your custom plugin!'
    }),
  ],
};

5. Test Your Plugin

Create an entry file:

bash
mkdir src
touch src/index.js

Add some JS:

js
console.log('Hello from index.js');

Run Webpack:

bash
npx webpack

You should see:

plaintext
Assets are being emitted...
Build is complete!

Webpack Plugin Lifecycle Hooks

Webpack exposes dozens of hooks via compiler and compilation. Common ones:

HookWhen it Runs
emitBefore assets are written to dist/
compilationDuring module compilation
afterEmitAfter writing assets
doneBuild has fully completed

Hooks come in 3 types:

  • .tap() — synchronous
  • .tapAsync() — async with callback
  • .tapPromise() — async with Promise

Example: Adding a Custom File to the Output

js
class AddCustomFilePlugin {
  apply(compiler) {
    compiler.hooks.emit.tapAsync(
      'AddCustomFilePlugin',
      (compilation, callback) => {
        const content = 'This file was created by a Webpack plugin!';

        compilation.assets['custom-file.txt'] = {
          source: () => content,
          size: () => content.length
        };

        callback();
      }
    );
  }
}

module.exports = AddCustomFilePlugin;

Register it:

js
const AddCustomFilePlugin = require('./plugins/AddCustomFilePlugin');

module.exports = {
  mode: 'development',
  plugins: [new AddCustomFilePlugin()],
};

Running Webpack now generates:

plaintext
dist/custom-file.txt

Conclusion

To create a Webpack plugin, you:

  1. Write a class with an apply method
  2. Hook into Webpack’s lifecycle using compiler hooks
  3. Modify assets, metadata, or build behavior
  4. Register your plugin in webpack.config.js

Once you understand how hooks work, Webpack becomes a fully programmable build engine — capable of automating anything you need.

If you’d like, I can also generate:

  • a more advanced plugin, like asset minification
  • a TypeScript version
  • a Webpack 5 plugin template generator
  • or a plugin that analyzes bundle size