Skip to main content
Webpack plugins are JavaScript objects with an apply method. This method is called by the webpack compiler, giving plugins access to the entire compilation lifecycle.

Basic Plugin Structure

A minimal webpack plugin looks like this:
class MyPlugin {
  apply(compiler) {
    // Plugin logic here
  }
}

module.exports = MyPlugin;

Using Compiler Hooks

The compiler exposes hooks that allow you to tap into different stages of the build process:
class MyPlugin {
  apply(compiler) {
    compiler.hooks.compile.tap('MyPlugin', (params) => {
      console.log('The compiler is starting to compile...');
    });

    compiler.hooks.done.tap('MyPlugin', (stats) => {
      console.log('The compilation has finished!');
    });
  }
}

Available Hooks

Common compiler hooks include:
  • entryOption - Called after entry configuration is processed
  • beforeRun - Called before running the compiler
  • run - Called when the compiler starts running
  • compile - Called before a new compilation is created
  • compilation - Called when a compilation is created
  • emit - Called before assets are emitted to output directory
  • done - Called when compilation finishes

Accessing Compilation

For more detailed control, tap into the compilation hook:
class MyPlugin {
  apply(compiler) {
    compiler.hooks.compilation.tap('MyPlugin', (compilation) => {
      compilation.hooks.optimize.tap('MyPlugin', () => {
        console.log('Assets are being optimized.');
      });
    });
  }
}

Async Hooks

Some hooks support asynchronous operations:
class MyPlugin {
  apply(compiler) {
    compiler.hooks.emit.tapAsync('MyPlugin', (compilation, callback) => {
      setTimeout(() => {
        console.log('Async operation complete');
        callback();
      }, 1000);
    });
  }
}

Using Promises

You can also use promises with tapPromise:
class MyPlugin {
  apply(compiler) {
    compiler.hooks.emit.tapPromise('MyPlugin', async (compilation) => {
      await someAsyncOperation();
      console.log('Promise resolved');
    });
  }
}

Example: Adding Custom Assets

const { RawSource } = require('webpack-sources');

class CreateFilePlugin {
  apply(compiler) {
    compiler.hooks.thisCompilation.tap('CreateFilePlugin', (compilation) => {
      compilation.hooks.processAssets.tap(
        {
          name: 'CreateFilePlugin',
          stage: compilation.PROCESS_ASSETS_STAGE_ADDITIONAL
        },
        () => {
          compilation.emitAsset(
            'custom-file.txt',
            new RawSource('This is custom content')
          );
        }
      );
    });
  }
}

Plugin Options

Plugins typically accept options in their constructor:
class MyPlugin {
  constructor(options = {}) {
    this.options = options;
  }

  apply(compiler) {
    const { message = 'Hello!' } = this.options;
    compiler.hooks.done.tap('MyPlugin', () => {
      console.log(message);
    });
  }
}

// Usage
new MyPlugin({ message: 'Build complete!' });

Best Practices

Always give your plugin a clear, descriptive name when using .tap() - this helps with debugging and understanding the build process.
Be careful with asynchronous operations. Always call the callback or resolve the promise to avoid hanging the build process.