Skip to main content

What is a Plugin?

Plugins are the backbone of webpack. Webpack itself is built on the same plugin system that you use in your webpack configuration. They allow you to hook into the entire compilation lifecycle and customize webpack’s behavior.

Basic Plugin Structure

A webpack plugin is a JavaScript object that has an apply method. This apply method is called by the webpack compiler, giving the plugin access to the entire compilation lifecycle.
class MyPlugin {
  apply(compiler) {
    compiler.hooks.compilation.tap('MyPlugin', (compilation) => {
      // Access compilation hooks
      compilation.hooks.optimize.tap('MyPlugin', () => {
        console.log('Optimizing...');
      });
    });
  }
}

module.exports = MyPlugin;

Plugin Anatomy

1. The apply Method

Every plugin must have an apply method that receives the compiler instance:
apply(compiler) {
  // Plugin logic here
}

2. Tapping into Hooks

Webpack uses Tapable to expose hooks. You can tap into these hooks using:
  • tap - Synchronous hook
  • tapAsync - Asynchronous hook with callback
  • tapPromise - Asynchronous hook with promises
// Synchronous
compiler.hooks.run.tap('MyPlugin', (compiler) => {
  console.log('Webpack is running');
});

// Asynchronous with callback
compiler.hooks.emit.tapAsync('MyPlugin', (compilation, callback) => {
  setTimeout(() => {
    console.log('Done with async work');
    callback();
  }, 1000);
});

// Asynchronous with Promise
compiler.hooks.afterEmit.tapPromise('MyPlugin', (compilation) => {
  return new Promise((resolve) => {
    setTimeout(() => {
      console.log('Done with async work');
      resolve();
    }, 1000);
  });
});

Real-World Example: BannerPlugin

Here’s a simplified version of webpack’s BannerPlugin:
const { ConcatSource } = require('webpack-sources');

class BannerPlugin {
  constructor(options) {
    this.banner = options.banner;
  }

  apply(compiler) {
    compiler.hooks.compilation.tap('BannerPlugin', (compilation) => {
      compilation.hooks.processAssets.tap(
        {
          name: 'BannerPlugin',
          stage: compilation.PROCESS_ASSETS_STAGE_ADDITIONS
        },
        () => {
          for (const chunk of compilation.chunks) {
            for (const file of chunk.files) {
              compilation.updateAsset(file, (old) => {
                return new ConcatSource(this.banner, '\n', old);
              });
            }
          }
        }
      );
    });
  }
}

Plugin Types

Compiler Plugins

Hook into the compiler lifecycle for tasks that happen once per build:
compiler.hooks.beforeRun.tap('MyPlugin', (compiler) => {
  // Runs before webpack starts
});

Compilation Plugins

Hook into each compilation for tasks that happen during module processing:
compiler.hooks.compilation.tap('MyPlugin', (compilation) => {
  compilation.hooks.buildModule.tap('MyPlugin', (module) => {
    // Runs before each module is built
  });
});

Module Factory Plugins

Customize how modules are created and resolved:
compiler.hooks.compilation.tap(
  'MyPlugin',
  (compilation, { normalModuleFactory }) => {
    normalModuleFactory.hooks.parser
      .for('javascript/auto')
      .tap('MyPlugin', (parser) => {
        // Customize the JavaScript parser
      });
  }
);

Key Concepts

Compiler vs Compilation

  • Compiler: Represents the entire webpack configuration. Created once per webpack process.
  • Compilation: Represents a single build. In watch mode, a new compilation is created on each file change.

Hook Stages

Many hooks support a stage option to control execution order:
compilation.hooks.processAssets.tap(
  {
    name: 'MyPlugin',
    stage: Compilation.PROCESS_ASSETS_STAGE_OPTIMIZE
  },
  (assets) => {
    // Process assets during optimization stage
  }
);
Common stages:
  • PROCESS_ASSETS_STAGE_ADDITIONAL - Add additional assets
  • PROCESS_ASSETS_STAGE_PRE_PROCESS - Basic preprocessing
  • PROCESS_ASSETS_STAGE_OPTIMIZE - Optimize assets
  • PROCESS_ASSETS_STAGE_SUMMARIZE - Summarize assets

Next Steps

Best Practices

  1. Name your taps: Always provide a clear plugin name when tapping hooks
  2. Handle errors: Properly handle errors in async hooks
  3. Clean up: Remove side effects in watch mode
  4. Document: Add comments explaining what your plugin does
  5. Test: Write tests for different webpack configurations