Skip to main content

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.

Module factories are responsible for creating module instances in webpack. The two main factories are NormalModuleFactory and ContextModuleFactory.

NormalModuleFactory

The NormalModuleFactory creates normal modules (most JavaScript/TypeScript files, JSON, etc.).

Accessing NormalModuleFactory

class MyPlugin {
  apply(compiler) {
    compiler.hooks.compilation.tap(
      'MyPlugin',
      (compilation, { normalModuleFactory }) => {
        // Access normalModuleFactory hooks
      }
    );
  }
}

NormalModuleFactory Hooks

resolve

AsyncSeriesBailHook<[ResolveData]>
Called before resolving a module request.
normalModuleFactory.hooks.resolve.tap('MyPlugin', (resolveData) => {
  console.log('Resolving:', resolveData.request);
  console.log('Context:', resolveData.context);
});

factorize

AsyncSeriesBailHook<[ResolveData]>
Called before the module is created.
normalModuleFactory.hooks.factorize.tapAsync(
  'MyPlugin',
  (resolveData, callback) => {
    // Custom module creation logic
    callback();
  }
);

beforeResolve

AsyncSeriesBailHook<[ResolveData]>
Called before module resolution begins. Return false to skip resolving.
normalModuleFactory.hooks.beforeResolve.tap('MyPlugin', (resolveData) => {
  if (resolveData.request.includes('ignore-me')) {
    return false; // Skip this module
  }
});

afterResolve

AsyncSeriesBailHook<[ResolveData]>
Called after module is resolved.
normalModuleFactory.hooks.afterResolve.tap('MyPlugin', (resolveData) => {
  console.log('Resolved to:', resolveData.createData.resource);
});

createModule

AsyncSeriesBailHook<[CreateData, ResolveData]>
Called when creating a module. Can return a custom module.
normalModuleFactory.hooks.createModule.tap(
  'MyPlugin',
  (createData, resolveData) => {
    if (shouldUseCustomModule(createData)) {
      return new MyCustomModule(createData);
    }
  }
);

module

SyncWaterfallHook<[Module, CreateData, ResolveData]>
Called after a module is created. Can modify or replace the module.
normalModuleFactory.hooks.module.tap(
  'MyPlugin',
  (module, createData, resolveData) => {
    // Modify module
    module.customData = 'my custom data';
    return module;
  }
);

createParser

HookMap<SyncBailHook<[ParserOptions], Parser>>
Create a custom parser for a module type.
normalModuleFactory.hooks.createParser
  .for('javascript/auto')
  .tap('MyPlugin', (parserOptions) => {
    return new MyCustomParser(parserOptions);
  });

parser

HookMap<SyncHook<[Parser, ParserOptions]>>
Access and customize the parser for a module type. This is where most parser customization happens.
normalModuleFactory.hooks.parser
  .for('javascript/auto')
  .tap('MyPlugin', (parser, parserOptions) => {
    // Hook into parser events
    parser.hooks.program.tap('MyPlugin', (ast) => {
      console.log('Parsing JavaScript');
    });
  });
Example: DefinePlugin (lib/DefinePlugin.js:780):
normalModuleFactory.hooks.parser
  .for('javascript/auto')
  .tap('DefinePlugin', (parser) => {
    // Define global constants
    parser.hooks.expression.for('MY_VAR').tap('DefinePlugin', (expr) => {
      return toConstantDependency(parser, JSON.stringify('value'))(expr);
    });
  });

createGenerator

HookMap<SyncBailHook<[GeneratorOptions], Generator>>
Create a custom generator for a module type.
normalModuleFactory.hooks.createGenerator
  .for('javascript/auto')
  .tap('MyPlugin', (generatorOptions) => {
    return new MyCustomGenerator(generatorOptions);
  });

generator

HookMap<SyncHook<[Generator, GeneratorOptions]>>
Access and customize the generator for a module type.
normalModuleFactory.hooks.generator
  .for('javascript/auto')
  .tap('MyPlugin', (generator, generatorOptions) => {
    // Customize code generation
  });

ContextModuleFactory

The ContextModuleFactory handles dynamic requires like require('./templates/' + name + '.js').

Accessing ContextModuleFactory

compiler.hooks.compilation.tap(
  'MyPlugin',
  (compilation, { contextModuleFactory }) => {
    // Access contextModuleFactory hooks
  }
);

ContextModuleFactory Hooks

beforeResolve

AsyncSeriesBailHook<[ResolveData]>
Called before context resolution.
contextModuleFactory.hooks.beforeResolve.tap('MyPlugin', (resolveData) => {
  console.log('Context request:', resolveData.request);
});

afterResolve

AsyncSeriesBailHook<[ResolveData]>
Called after context resolution.
contextModuleFactory.hooks.afterResolve.tap('MyPlugin', (resolveData) => {
  console.log('Context resolved:', resolveData.resource);
});

contextModuleFiles

SyncWaterfallHook<[string[]]>
Called with the list of files found in the context.
contextModuleFactory.hooks.contextModuleFiles.tap(
  'MyPlugin',
  (files) => {
    console.log('Context files:', files);
    // Can filter or modify files
    return files.filter(f => !f.endsWith('.test.js'));
  }
);

Parser Hooks (JavaScript)

When you access a parser through normalModuleFactory.hooks.parser, you get access to numerous hooks for customizing code parsing.

Common Parser Hooks

program

SyncBailHook<[Program, Comment[]]>
Called when parsing starts.
parser.hooks.program.tap('MyPlugin', (ast, comments) => {
  console.log('Parsing program');
});

statement

SyncBailHook<[Statement]>
Called for each statement.
parser.hooks.statement.tap('MyPlugin', (statement) => {
  if (statement.type === 'ImportDeclaration') {
    console.log('Found import');
  }
});

expression

HookMap<SyncBailHook<[Expression]>>
Called when an expression is encountered.
parser.hooks.expression.for('MY_GLOBAL').tap('MyPlugin', (expr) => {
  // Replace MY_GLOBAL with a constant
  const dep = new ConstDependency('"my value"', expr.range);
  parser.state.module.addDependency(dep);
  return true;
});
Example: ProvidePlugin (lib/ProvidePlugin.js:75):
parser.hooks.expression.for('$').tap('ProvidePlugin', (expr) => {
  const dep = new ProvidedDependency(
    'jquery',
    'jquery',
    [],
    expr.range
  );
  parser.state.module.addDependency(dep);
  return true;
});

call

HookMap<SyncBailHook<[CallExpression]>>
Called when a function call is encountered.
parser.hooks.call.for('require').tap('MyPlugin', (expr) => {
  console.log('Found require call');
});

import

SyncBailHook<[Statement, ImportSource]>
Called for import statements.
parser.hooks.import.tap('MyPlugin', (statement, source) => {
  console.log('Importing:', source);
});

Complete Example: Custom Module Type

class CustomModulePlugin {
  apply(compiler) {
    compiler.hooks.compilation.tap(
      'CustomModulePlugin',
      (compilation, { normalModuleFactory }) => {
        // Register custom module type
        normalModuleFactory.hooks.createParser
          .for('custom/type')
          .tap('CustomModulePlugin', (parserOptions) => {
            return new CustomParser(parserOptions);
          });

        normalModuleFactory.hooks.createGenerator
          .for('custom/type')
          .tap('CustomModulePlugin', (generatorOptions) => {
            return new CustomGenerator(generatorOptions);
          });

        // Customize parsing for JavaScript
        normalModuleFactory.hooks.parser
          .for('javascript/auto')
          .tap('CustomModulePlugin', (parser) => {
            // Hook into require calls
            parser.hooks.call.for('customRequire').tap(
              'CustomModulePlugin',
              (expr) => {
                if (expr.arguments.length !== 1) return;
                
                const param = parser.evaluateExpression(expr.arguments[0]);
                if (!param.isString()) return;
                
                // Add custom dependency
                const dep = new CustomDependency(
                  param.string,
                  expr.range
                );
                parser.state.module.addDependency(dep);
                return true;
              }
            );
          });
      }
    );
  }
}

Complete Example: Provide Global Variables

class ProvideGlobalsPlugin {
  constructor(definitions) {
    this.definitions = definitions;
  }

  apply(compiler) {
    compiler.hooks.compilation.tap(
      'ProvideGlobalsPlugin',
      (compilation, { normalModuleFactory }) => {
        const handler = (parser) => {
          for (const [name, modulePath] of Object.entries(this.definitions)) {
            // Hook into identifier expressions
            parser.hooks.expression.for(name).tap(
              'ProvideGlobalsPlugin',
              (expr) => {
                const dep = new ProvidedDependency(
                  modulePath,
                  name,
                  [],
                  expr.range
                );
                dep.loc = expr.loc;
                parser.state.module.addDependency(dep);
                return true;
              }
            );
          }
        };

        normalModuleFactory.hooks.parser
          .for('javascript/auto')
          .tap('ProvideGlobalsPlugin', handler);
        normalModuleFactory.hooks.parser
          .for('javascript/dynamic')
          .tap('ProvideGlobalsPlugin', handler);
        normalModuleFactory.hooks.parser
          .for('javascript/esm')
          .tap('ProvideGlobalsPlugin', handler);
      }
    );
  }
}

// Usage
new ProvideGlobalsPlugin({
  $: 'jquery',
  _: 'lodash'
});

Module Types

Common module types you can hook into:
  • javascript/auto - Auto-detect ESM or CommonJS
  • javascript/dynamic - CommonJS
  • javascript/esm - ES Modules
  • json - JSON files
  • asset - Asset modules
  • asset/source - Source assets
  • asset/resource - Resource assets
  • asset/inline - Inline assets
  • webassembly/async - Async WebAssembly
  • webassembly/sync - Sync WebAssembly
  • css - CSS files
  • css/module - CSS Modules

ResolveData Structure

The ResolveData object contains:
interface ResolveData {
  contextInfo: {
    issuer: string;         // Path of the importing module
    compiler: string;       // Compiler name
  };
  context: string;          // Directory context
  request: string;          // The import request
  dependencies: Dependency[];
  createData: {
    resource: string;       // Resolved file path
    loaders: LoaderItem[];  // Loaders to apply
    type: string;           // Module type
    parser: Parser;         // Parser instance
    generator: Generator;   // Generator instance
  };
}

See Also