Skip to main content

Plugins

Plugins are the backbone of webpack. Webpack itself is built on the same plugin system that you use in your configuration. They allow you to perform a wider range of tasks than loaders, from bundle optimization and minification to defining environment variables.

Overview

While loaders transform individual modules, plugins can tap into webpack’s compilation process to perform more complex tasks. Plugins have access to the full webpack compilation lifecycle through hooks.

Using Plugins

Plugins are configured in the plugins array:
const HtmlWebpackPlugin = require('html-webpack-plugin');
const webpack = require('webpack');

module.exports = {
  plugins: [
    new HtmlWebpackPlugin({ template: './src/index.html' }),
    new webpack.ProgressPlugin()
  ]
};
Plugins are JavaScript classes that must be instantiated with new.

Plugin Interface

A webpack plugin is a JavaScript object with an apply method:
class MyPlugin {
  apply(compiler) {
    compiler.hooks.compile.tap('MyPlugin', (params) => {
      console.log('The compilation is starting!');
    });
    
    compiler.hooks.emit.tapAsync('MyPlugin', (compilation, callback) => {
      console.log('The compilation is emitting files...');
      callback();
    });
  }
}

module.exports = MyPlugin;

Basic Structure

class BasicPlugin {
  // Constructor can accept options
  constructor(options = {}) {
    this.options = options;
  }

  // Apply method is called by webpack
  apply(compiler) {
    // Access compiler hooks
    compiler.hooks.done.tap('BasicPlugin', (stats) => {
      console.log('Build complete!');
    });
  }
}

Compiler Hooks

The Compiler object represents the fully configured webpack environment and exposes hooks for the entire build lifecycle:
// Available Compiler hooks (from Compiler.js)
compiler.hooks = {
  initialize: new SyncHook([]),
  shouldEmit: new SyncBailHook(['compilation']),
  done: new AsyncSeriesHook(['stats']),
  afterDone: new SyncHook(['stats']),
  beforeRun: new AsyncSeriesHook(['compiler']),
  run: new AsyncSeriesHook(['compiler']),
  emit: new AsyncSeriesHook(['compilation']),
  assetEmitted: new AsyncSeriesHook(['file', 'info']),
  afterEmit: new AsyncSeriesHook(['compilation']),
  
  thisCompilation: new SyncHook(['compilation', 'params']),
  compilation: new SyncHook(['compilation', 'params']),
  make: new AsyncParallelHook(['compilation']),
  afterCompile: new AsyncSeriesHook(['compilation']),
  
  watchRun: new AsyncSeriesHook(['compiler']),
  failed: new SyncHook(['error']),
  invalid: new SyncHook(['filename', 'changeTime']),
  watchClose: new SyncHook([]),
  shutdown: new AsyncSeriesHook([])
};

Compilation Hooks

The Compilation object represents a single build of versioned assets and provides hooks for finer-grained control:
// Key Compilation hooks (from Compilation.js)
compilation.hooks = {
  buildModule: new SyncHook(['module']),
  succeedModule: new SyncHook(['module']),
  finishModules: new AsyncSeriesHook(['modules']),
  
  seal: new SyncHook([]),
  beforeChunks: new SyncHook([]),
  afterChunks: new SyncHook(['chunks']),
  
  optimizeModules: new SyncBailHook(['modules']),
  optimizeChunks: new SyncBailHook(['chunks', 'chunkGroups']),
  optimizeTree: new AsyncSeriesHook(['chunks', 'modules']),
  
  processAssets: new AsyncSeriesHook(['assets']),
  afterProcessAssets: new SyncHook(['assets']),
  
  beforeHash: new SyncHook([]),
  afterHash: new SyncHook([]),
  
  beforeModuleAssets: new SyncHook([]),
  afterSeal: new AsyncSeriesHook([])
};

Hook Types

Webpack uses the Tapable library for hooks:

Synchronous Hooks

compiler.hooks.compile.tap('MyPlugin', (params) => {
  // Synchronous operation
});

Asynchronous Hooks (Callback)

compiler.hooks.emit.tapAsync('MyPlugin', (compilation, callback) => {
  // Async operation
  setTimeout(() => {
    console.log('Done!');
    callback();
  }, 1000);
});

Asynchronous Hooks (Promise)

compiler.hooks.emit.tapPromise('MyPlugin', (compilation) => {
  return new Promise((resolve) => {
    setTimeout(() => {
      console.log('Done!');
      resolve();
    }, 1000);
  });
});

Common Plugin Patterns

Modifying Assets

class ModifyAssetsPlugin {
  apply(compiler) {
    compiler.hooks.compilation.tap('ModifyAssetsPlugin', (compilation) => {
      compilation.hooks.processAssets.tapAsync(
        {
          name: 'ModifyAssetsPlugin',
          stage: compilation.PROCESS_ASSETS_STAGE_OPTIMIZE
        },
        (assets, callback) => {
          // Modify assets
          Object.keys(assets).forEach((filename) => {
            if (filename.endsWith('.js')) {
              const asset = assets[filename];
              const source = asset.source();
              // Transform source
            }
          });
          callback();
        }
      );
    });
  }
}

Adding Assets

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

class EmitFilePlugin {
  apply(compiler) {
    compiler.hooks.emit.tapAsync('EmitFilePlugin', (compilation, callback) => {
      // Add a new asset to the compilation
      compilation.emitAsset(
        'my-file.txt',
        new RawSource('This is my file content')
      );
      callback();
    });
  }
}

Watching Files

class WatchFilesPlugin {
  apply(compiler) {
    compiler.hooks.afterCompile.tap('WatchFilesPlugin', (compilation) => {
      // Add files to watch
      compilation.fileDependencies.add('/path/to/file');
      compilation.contextDependencies.add('/path/to/directory');
    });
  }
}

Built-in Plugins

Webpack includes many built-in plugins:

DefinePlugin

const webpack = require('webpack');

module.exports = {
  plugins: [
    new webpack.DefinePlugin({
      'process.env.NODE_ENV': JSON.stringify('production'),
      'API_URL': JSON.stringify('https://api.example.com')
    })
  ]
};

ProvidePlugin

const webpack = require('webpack');

module.exports = {
  plugins: [
    new webpack.ProvidePlugin({
      $: 'jquery',
      jQuery: 'jquery',
      process: 'process/browser'
    })
  ]
};

BannerPlugin

const webpack = require('webpack');

module.exports = {
  plugins: [
    new webpack.BannerPlugin({
      banner: '/*! My Copyright Notice */'
    })
  ]
};
const HtmlWebpackPlugin = require('html-webpack-plugin');

module.exports = {
  plugins: [
    new HtmlWebpackPlugin({
      template: './src/index.html',
      filename: 'index.html',
      inject: 'body',
      minify: true
    })
  ]
};

Process Assets Stages

When modifying assets, use the appropriate stage:
compilation.hooks.processAssets.tap(
  {
    name: 'MyPlugin',
    stage: Compilation.PROCESS_ASSETS_STAGE_OPTIMIZE_SIZE
  },
  (assets) => {
    // Process assets
  }
);
Available stages (in order):
  • PROCESS_ASSETS_STAGE_ADDITIONAL
  • PROCESS_ASSETS_STAGE_PRE_PROCESS
  • PROCESS_ASSETS_STAGE_DERIVED
  • PROCESS_ASSETS_STAGE_ADDITIONS
  • PROCESS_ASSETS_STAGE_OPTIMIZE
  • PROCESS_ASSETS_STAGE_OPTIMIZE_COUNT
  • PROCESS_ASSETS_STAGE_OPTIMIZE_COMPATIBILITY
  • PROCESS_ASSETS_STAGE_OPTIMIZE_SIZE
  • PROCESS_ASSETS_STAGE_DEV_TOOLING
  • PROCESS_ASSETS_STAGE_OPTIMIZE_INLINE
  • PROCESS_ASSETS_STAGE_SUMMARIZE
  • PROCESS_ASSETS_STAGE_OPTIMIZE_HASH
  • PROCESS_ASSETS_STAGE_OPTIMIZE_TRANSFER
  • PROCESS_ASSETS_STAGE_ANALYSE
  • PROCESS_ASSETS_STAGE_REPORT

Real-World Plugin Example

class FileListPlugin {
  constructor(options = {}) {
    this.options = options;
  }

  apply(compiler) {
    compiler.hooks.emit.tapAsync('FileListPlugin', (compilation, callback) => {
      // Create a list of all files
      const fileList = Object.keys(compilation.assets)
        .map(filename => `- ${filename}`)
        .join('\n');

      // Insert as a new file asset
      compilation.emitAsset(
        'filelist.md',
        new RawSource(`# Build Files\n\n${fileList}`)
      );

      callback();
    });
  }
}

module.exports = FileListPlugin;
This plugin generates a markdown file listing all emitted assets.

Best Practices

  1. Use specific hooks - Choose the most appropriate hook for your task
  2. Handle errors - Always call callbacks with errors when appropriate
  3. Don’t mutate - Avoid mutating webpack internals directly
  4. Clean up - Remove listeners and clean up resources
  5. Document options - Provide clear documentation for plugin options
  6. Test thoroughly - Test plugins with different configurations

Debugging Plugins

Enable webpack’s debug mode:
module.exports = {
  infrastructureLogging: {
    level: 'verbose',
    debug: /MyPlugin/
  }
};
Or use Node.js debugging:
node --inspect-brk node_modules/webpack/bin/webpack.js