> ## Documentation Index
> Fetch the complete documentation index at: https://docs.webpack.js.org/llms.txt
> Use this file to discover all available pages before exploring further.

# Tapable

> Understanding the Tapable hook system that powers webpack plugins

Tapable is the backbone of webpack's plugin system. It provides a variety of hooks that allow plugins to tap into different stages of the compilation process.

## What is Tapable?

Tapable is a small library that webpack uses to create hooks. These hooks are extension points where plugins can register callbacks to execute at specific times.

## Hook Types

Tapable provides several hook types, each with different execution patterns:

### Sync Hooks

#### `SyncHook`

The simplest hook - calls all taps sequentially.

```javascript theme={null}
const { SyncHook } = require('tapable');

const hook = new SyncHook(['arg1', 'arg2']);

// Tap into the hook
hook.tap('MyPlugin', (arg1, arg2) => {
  console.log('Hook called with:', arg1, arg2);
});

// Call the hook
hook.call('value1', 'value2');
```

**Used in webpack:**

```javascript theme={null}
// lib/Compiler.js:166
initialize: new SyncHook([])

// lib/Compiler.js:188
thisCompilation: new SyncHook(['compilation', 'params'])
```

#### `SyncBailHook`

Like SyncHook, but stops executing if any tap returns a non-undefined value.

```javascript theme={null}
const { SyncBailHook } = require('tapable');

const hook = new SyncBailHook(['value']);

hook.tap('Plugin1', (value) => {
  console.log('Plugin 1');
  if (value === 'stop') return true; // Stop here
});

hook.tap('Plugin2', (value) => {
  console.log('Plugin 2'); // Won't run if Plugin1 returned
});

hook.call('stop'); // Only Plugin1 runs
```

**Used in webpack:**

```javascript theme={null}
// lib/Compiler.js:169
shouldEmit: new SyncBailHook(['compilation'])

// lib/Compiler.js:237
entryOption: new SyncBailHook(['context', 'entry'])
```

#### `SyncWaterfallHook`

Passes the return value of each tap to the next tap.

```javascript theme={null}
const { SyncWaterfallHook } = require('tapable');

const hook = new SyncWaterfallHook(['value']);

hook.tap('Plugin1', (value) => {
  return value + ' -> Plugin1';
});

hook.tap('Plugin2', (value) => {
  return value + ' -> Plugin2';
});

const result = hook.call('Start');
console.log(result); // 'Start -> Plugin1 -> Plugin2'
```

**Used in webpack:**

```javascript theme={null}
// lib/Compilation.js:1012
assetPath: new SyncWaterfallHook(['path', 'options', 'assetInfo'])
```

#### `SyncLoopHook`

Keeps looping through all taps while any tap returns a non-undefined value.

```javascript theme={null}
const { SyncLoopHook } = require('tapable');

let count = 0;
const hook = new SyncLoopHook([]);

hook.tap('Loop', () => {
  count++;
  if (count < 3) {
    console.log('Loop again');
    return true; // Loop
  }
  console.log('Done');
  // Return undefined to stop
});

hook.call(); // Runs 3 times
```

### Async Hooks

#### `AsyncSeriesHook`

Executes async taps in series.

```javascript theme={null}
const { AsyncSeriesHook } = require('tapable');

const hook = new AsyncSeriesHook(['data']);

// Using tapAsync
hook.tapAsync('Plugin1', (data, callback) => {
  setTimeout(() => {
    console.log('Plugin 1 done');
    callback();
  }, 100);
});

// Using tapPromise
hook.tapPromise('Plugin2', async (data) => {
  await new Promise(resolve => setTimeout(resolve, 100));
  console.log('Plugin 2 done');
});

// Call with callback
hook.callAsync('data', (err) => {
  console.log('All done');
});

// Or call with promise
await hook.promise('data');
```

**Used in webpack:**

```javascript theme={null}
// lib/Compiler.js:177
beforeRun: new AsyncSeriesHook(['compiler'])

// lib/Compiler.js:181
emit: new AsyncSeriesHook(['compilation'])

// lib/Compiler.js:197
beforeCompile: new AsyncSeriesHook(['params'])
```

#### `AsyncSeriesBailHook`

Like AsyncSeriesHook, but stops if a tap returns a non-undefined value.

```javascript theme={null}
const { AsyncSeriesBailHook } = require('tapable');

const hook = new AsyncSeriesBailHook(['input']);

hook.tapAsync('Plugin1', (input, callback) => {
  if (input === 'stop') {
    callback(null, 'stopped'); // Bail with result
  } else {
    callback(); // Continue
  }
});

hook.tapAsync('Plugin2', (input, callback) => {
  console.log('Plugin 2'); // Won't run if Plugin1 bailed
  callback();
});

hook.callAsync('stop', (err, result) => {
  console.log(result); // 'stopped'
});
```

#### `AsyncSeriesWaterfallHook`

Like SyncWaterfallHook but async.

```javascript theme={null}
const { AsyncSeriesWaterfallHook } = require('tapable');

const hook = new AsyncSeriesWaterfallHook(['value']);

hook.tapAsync('Plugin1', (value, callback) => {
  callback(null, value + ' -> Plugin1');
});

hook.tapPromise('Plugin2', async (value) => {
  return value + ' -> Plugin2';
});

hook.callAsync('Start', (err, result) => {
  console.log(result); // 'Start -> Plugin1 -> Plugin2'
});
```

#### `AsyncParallelHook`

Executes async taps in parallel.

```javascript theme={null}
const { AsyncParallelHook } = require('tapable');

const hook = new AsyncParallelHook(['data']);

hook.tapAsync('Plugin1', (data, callback) => {
  setTimeout(() => {
    console.log('Plugin 1');
    callback();
  }, 100);
});

hook.tapAsync('Plugin2', (data, callback) => {
  setTimeout(() => {
    console.log('Plugin 2');
    callback();
  }, 50);
});

// Both run in parallel
hook.callAsync('data', () => {
  console.log('All done');
});
```

**Used in webpack:**

```javascript theme={null}
// lib/Compiler.js:201
make: new AsyncParallelHook(['compilation'])
```

#### `AsyncParallelBailHook`

Like AsyncParallelHook, but bails when any tap returns a result.

```javascript theme={null}
const { AsyncParallelBailHook } = require('tapable');

const hook = new AsyncParallelBailHook(['data']);

hook.tapAsync('Plugin1', (data, callback) => {
  setTimeout(() => {
    callback(null, 'result from Plugin1');
  }, 100);
});

hook.tapAsync('Plugin2', (data, callback) => {
  setTimeout(() => {
    callback(); // No result
  }, 50);
});

hook.callAsync('data', (err, result) => {
  console.log(result); // 'result from Plugin1'
});
```

## Hook Maps

HookMap allows you to create multiple hooks with different keys.

```javascript theme={null}
const { HookMap, SyncHook } = require('tapable');

const hookMap = new HookMap(() => new SyncHook(['arg']));

// Access hook for specific key
hookMap.for('javascript/auto').tap('MyPlugin', (arg) => {
  console.log('JavaScript auto:', arg);
});

hookMap.for('css').tap('MyPlugin', (arg) => {
  console.log('CSS:', arg);
});

// Call specific hook
hookMap.for('javascript/auto').call('data');
```

**Used in webpack:**

```javascript theme={null}
// lib/NormalModuleFactory.js - parser hooks for different module types
parser: new HookMap(() => new SyncHook(['parser', 'parserOptions']))
```

## Tap Methods

### `.tap()` - Synchronous

```javascript theme={null}
hook.tap('MyPlugin', (arg) => {
  // Synchronous code
});

// With options
hook.tap({
  name: 'MyPlugin',
  stage: 100 // Control execution order
}, (arg) => {
  // Code
});
```

### `.tapAsync()` - Callback-based

```javascript theme={null}
hook.tapAsync('MyPlugin', (arg, callback) => {
  setTimeout(() => {
    callback(); // Must call callback
  }, 100);
});
```

### `.tapPromise()` - Promise-based

```javascript theme={null}
hook.tapPromise('MyPlugin', async (arg) => {
  await someAsyncOperation();
  return result; // Can return value
});
```

## Tap Options

### `name`

Required. Identifies your plugin.

```javascript theme={null}
hook.tap({ name: 'MyPlugin' }, () => {});
```

### `stage`

Controls execution order. Lower numbers run first.

```javascript theme={null}
hook.tap({ name: 'Plugin1', stage: 100 }, () => {});
hook.tap({ name: 'Plugin2', stage: -100 }, () => {}); // Runs first
```

**Example from webpack:**

```javascript theme={null}
// lib/BannerPlugin.js:100
compilation.hooks.processAssets.tap(
  {
    name: 'BannerPlugin',
    stage: Compilation.PROCESS_ASSETS_STAGE_ADDITIONS
  },
  () => {
    // Add banner to assets
  }
);
```

### `before`

Run before specific plugins.

```javascript theme={null}
hook.tap({
  name: 'MyPlugin',
  before: ['OtherPlugin']
}, () => {});
```

### `after`

Run after specific plugins.

```javascript theme={null}
hook.tap({
  name: 'MyPlugin',
  after: ['OtherPlugin']
}, () => {});
```

## Interceptors

Interceptors allow you to monitor and control hook behavior.

```javascript theme={null}
hook.intercept({
  // Called when a new tap is registered
  register: (tap) => {
    console.log('New tap:', tap.name);
    return tap; // Can modify tap
  },
  
  // Called before the hook is called
  call: (...args) => {
    console.log('Hook called with:', args);
  },
  
  // Called before each tap
  tap: (tap) => {
    console.log('Running tap:', tap.name);
  },
  
  // Called when loop restarts (SyncLoopHook)
  loop: (...args) => {
    console.log('Loop restarted');
  }
});
```

## Context

Share data between taps using context.

```javascript theme={null}
hook.intercept({
  context: true, // Enable context
  tap: (context, tap) => {
    // Access context
  }
});

hook.tap({
  name: 'Plugin1',
  context: true
}, (context, arg) => {
  context.shared = 'data';
});

hook.tap({
  name: 'Plugin2',
  context: true
}, (context, arg) => {
  console.log(context.shared); // 'data'
});
```

## Complete Example: Custom Hook System

```javascript theme={null}
const { SyncHook, AsyncSeriesHook } = require('tapable');

class BuildSystem {
  constructor() {
    this.hooks = {
      beforeBuild: new AsyncSeriesHook(['options']),
      build: new SyncHook(['modules']),
      afterBuild: new AsyncSeriesHook(['result'])
    };
  }

  async run(options) {
    // Before build
    await this.hooks.beforeBuild.promise(options);
    
    // Build
    const modules = ['module1', 'module2'];
    this.hooks.build.call(modules);
    
    // After build
    await this.hooks.afterBuild.promise({ success: true });
  }
}

// Use the system
const builder = new BuildSystem();

// Register plugins
builder.hooks.beforeBuild.tapAsync('PreparePlugin', (options, callback) => {
  console.log('Preparing build');
  setTimeout(callback, 100);
});

builder.hooks.build.tap('BuildPlugin', (modules) => {
  console.log('Building modules:', modules);
});

builder.hooks.afterBuild.tapPromise('CleanupPlugin', async (result) => {
  console.log('Cleaning up');
});

// Run
builder.run({ mode: 'production' });
```

## Best Practices

### 1. Always Name Your Taps

```javascript theme={null}
// Good
hook.tap('MyPlugin', () => {});

// Bad
hook.tap(() => {}); // Error: name is required
```

### 2. Use Appropriate Hook Type

```javascript theme={null}
// For I/O operations
hook.tapAsync('MyPlugin', (data, callback) => {
  fs.readFile(path, callback);
});

// For promises
hook.tapPromise('MyPlugin', async (data) => {
  return await fetch(url);
});
```

### 3. Handle Errors in Async Hooks

```javascript theme={null}
hook.tapAsync('MyPlugin', (data, callback) => {
  doSomething((err, result) => {
    if (err) return callback(err); // Pass error
    callback(); // Success
  });
});
```

### 4. Use Stage for Ordering

```javascript theme={null}
// Run first
hook.tap({ name: 'Setup', stage: -100 }, () => {});

// Run last  
hook.tap({ name: 'Cleanup', stage: 100 }, () => {});
```

### 5. Return Boolean from Bail Hooks

```javascript theme={null}
hook.tap('MyPlugin', (value) => {
  if (shouldStop(value)) {
    return true; // Bail
  }
  // Continue by returning undefined
});
```

## See Also

* [Tapable GitHub](https://github.com/webpack/tapable) - Official repository
* [Compiler Hooks](./compiler-hooks) - Hooks in the compiler
* [Compilation Hooks](./compilation-hooks) - Hooks in compilation
