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.

Parser hooks allow you to customize how webpack parses and analyzes your code. These hooks are primarily used for JavaScript/TypeScript files but can also apply to other module types.

Accessing Parser Hooks

Parser hooks are accessed through the NormalModuleFactory:
class MyPlugin {
  apply(compiler) {
    compiler.hooks.compilation.tap(
      'MyPlugin',
      (compilation, { normalModuleFactory }) => {
        normalModuleFactory.hooks.parser
          .for('javascript/auto')
          .tap('MyPlugin', (parser) => {
            // Access parser hooks here
          });
      }
    );
  }
}

Program-Level Hooks

program

SyncBailHook<[Program, Comment[]]>
Called when parsing begins with the AST root node.
parser.hooks.program.tap('MyPlugin', (ast, comments) => {
  console.log('Parsing started');
  console.log('Number of comments:', comments.length);
});

finish

SyncHook<[Program, Comment[]]>
Called when parsing finishes.
parser.hooks.finish.tap('MyPlugin', (ast, comments) => {
  console.log('Parsing complete');
});

Statement Hooks

statement

SyncBailHook<[Statement]>
Called for every statement in the code.
parser.hooks.statement.tap('MyPlugin', (statement) => {
  if (statement.type === 'IfStatement') {
    console.log('Found if statement');
  }
});

statementIf

SyncBailHook<[IfStatement]>
Called for if statements.
parser.hooks.statementIf.tap('MyPlugin', (statement) => {
  console.log('If statement condition:', statement.test);
});

label

HookMap<SyncBailHook<[LabeledStatement]>>
Called for labeled statements.
parser.hooks.label.for('myLabel').tap('MyPlugin', (statement) => {
  console.log('Found labeled statement');
});

Expression Hooks

expression

HookMap<SyncBailHook<[Expression]>>
Called when an expression matching the key is encountered. This is one of the most powerful hooks.
// Replace process.env.NODE_ENV with a constant
parser.hooks.expression
  .for('process.env.NODE_ENV')
  .tap('MyPlugin', (expr) => {
    const dep = new ConstDependency(
      JSON.stringify('production'),
      expr.range
    );
    dep.loc = expr.loc;
    parser.state.module.addDependency(dep);
    return true; // Handled
  });
Example: DefinePlugin (lib/DefinePlugin.js:628):
parser.hooks.expression.for(key).tap('DefinePlugin', (expr) => {
  const strCode = toCode(
    code,
    parser,
    compilation.valueCacheVersions,
    key,
    runtimeTemplate,
    logger,
    !parser.isAsiPosition(expr.range[0])
  );
  return toConstantDependency(parser, strCode)(expr);
});

expressionAnyMember

HookMap<SyncBailHook<[Expression]>>
Called for member expressions with any property.
parser.hooks.expressionAnyMember
  .for('process.env')
  .tap('MyPlugin', (expr) => {
    // Handle process.env.ANYTHING
  });

expressionConditionalOperator

SyncBailHook<[ConditionalExpression]>
Called for ternary operators.
parser.hooks.expressionConditionalOperator.tap(
  'MyPlugin',
  (expr) => {
    console.log('Found ternary:', expr);
  }
);

expressionLogicalOperator

SyncBailHook<[LogicalExpression]>
Called for logical operators (&&, ||, ??).
parser.hooks.expressionLogicalOperator.tap('MyPlugin', (expr) => {
  if (expr.operator === '&&') {
    console.log('Found AND operator');
  }
});

Call Expression Hooks

call

HookMap<SyncBailHook<[CallExpression]>>
Called when a function call is encountered.
parser.hooks.call.for('require').tap('MyPlugin', (expr) => {
  if (expr.arguments.length === 1) {
    const arg = parser.evaluateExpression(expr.arguments[0]);
    if (arg.isString()) {
      console.log('Requiring:', arg.string);
    }
  }
});
Example: ProvidePlugin (lib/ProvidePlugin.js:90):
parser.hooks.call.for(name).tap('ProvidePlugin', (expr) => {
  const dep = new ProvidedDependency(
    request[0],
    nameIdentifier,
    request.slice(1),
    expr.callee.range
  );
  dep.loc = expr.callee.loc;
  parser.state.module.addDependency(dep);
  parser.walkExpressions(expr.arguments);
  return true;
});

callAnyMember

HookMap<SyncBailHook<[CallExpression]>>
Called for method calls on objects.
parser.hooks.callAnyMember
  .for('console')
  .tap('MyPlugin', (expr) => {
    // Matches console.log(), console.error(), etc.
  });

new

HookMap<SyncBailHook<[NewExpression]>>
Called for new expressions.
parser.hooks.new.for('Worker').tap('MyPlugin', (expr) => {
  console.log('Creating new Worker');
});

Member Expression Hooks

member

HookMap<SyncBailHook<[MemberExpression]>>
Called for member access expressions.
parser.hooks.member
  .for('module.exports')
  .tap('MyPlugin', (expr) => {
    console.log('Accessing module.exports');
  });

memberChain

HookMap<SyncBailHook<[Expression, string[]]>>
Called for chained member expressions.
parser.hooks.memberChain
  .for('process')
  .tap('MyPlugin', (expr, members) => {
    // members might be ['env', 'NODE_ENV']
    console.log('Process chain:', members);
  });

Import/Export Hooks

import

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

export

SyncBailHook<[Statement]>
Called for export statements.
parser.hooks.export.tap('MyPlugin', (statement) => {
  console.log('Export statement found');
});

exportImport

SyncBailHook<[Statement, ImportSource]>
Called for export ... from statements.
parser.hooks.exportImport.tap('MyPlugin', (statement, source) => {
  console.log('Re-exporting from:', source);
});

exportExpression

SyncBailHook<[Statement, Declaration]>
Called for export expressions.
parser.hooks.exportExpression.tap('MyPlugin', (statement, declaration) => {
  console.log('Exporting expression');
});

exportSpecifier

SyncBailHook<[Statement, string, string, number]>
Called for each export specifier.
parser.hooks.exportSpecifier.tap(
  'MyPlugin',
  (statement, localName, exportedName, index) => {
    console.log(`Exporting ${localName} as ${exportedName}`);
  }
);

importSpecifier

SyncBailHook<[Statement, ImportSource, string, string]>
Called for each import specifier.
parser.hooks.importSpecifier.tap(
  'MyPlugin',
  (statement, source, importedName, localName) => {
    console.log(`Importing ${importedName} as ${localName} from ${source}`);
  }
);

Evaluation Hooks

evaluate

HookMap<SyncBailHook<[Expression]>>
Evaluate expressions at build time.
parser.hooks.evaluate
  .for('Identifier')
  .tap('MyPlugin', (expr) => {
    if (expr.name === 'MY_CONSTANT') {
      return new BasicEvaluatedExpression()
        .setString('my value')
        .setRange(expr.range);
    }
  });

evaluateTypeof

HookMap<SyncBailHook<[Expression]>>
Evaluate typeof expressions.
parser.hooks.evaluateTypeof
  .for('MY_VAR')
  .tap('MyPlugin', (expr) => {
    return new BasicEvaluatedExpression()
      .setString('object')
      .setRange(expr.range);
  });

evaluateIdentifier

HookMap<SyncBailHook<[Expression]>>
Evaluate identifiers.
parser.hooks.evaluateIdentifier
  .for('MY_VAR')
  .tap('MyPlugin', (expr) => {
    return new BasicEvaluatedExpression()
      .setBoolean(true)
      .setRange(expr.range);
  });

evaluateDefinedIdentifier

HookMap<SyncBailHook<[Expression]>>
Evaluate defined identifiers.
parser.hooks.evaluateDefinedIdentifier
  .for('MY_VAR')
  .tap('MyPlugin', (expr) => {
    return new BasicEvaluatedExpression()
      .setBoolean(true)
      .setRange(expr.range);
  });

Type Detection Hooks

typeof

HookMap<SyncBailHook<[Expression]>>
Customize typeof operator results.
parser.hooks.typeof
  .for('MY_VAR')
  .tap('MyPlugin', (expr) => {
    return toConstantDependency(parser, JSON.stringify('string'))(expr);
  });

Variable Hooks

varDeclaration

HookMap<SyncBailHook<[Declaration]>>
Called for variable declarations.
parser.hooks.varDeclaration
  .for('myVar')
  .tap('MyPlugin', (declaration) => {
    console.log('Variable declared:', declaration);
  });

pattern

HookMap<SyncBailHook<[Identifier]>>
Called for pattern matching in destructuring.
parser.hooks.pattern
  .for('myVar')
  .tap('MyPlugin', (identifier) => {
    console.log('Pattern match:', identifier);
  });

canRename

HookMap<SyncBailHook<[Expression]>>
Determines if an identifier can be renamed.
parser.hooks.canRename
  .for('MY_VAR')
  .tap('MyPlugin', () => {
    return true; // Allow renaming
  });

rename

HookMap<SyncBailHook<[Expression]>>
Rename an identifier.
parser.hooks.rename
  .for('oldName')
  .tap('MyPlugin', (expr) => {
    return 'newName';
  });

Assign Hooks

assign

HookMap<SyncBailHook<[AssignmentExpression]>>
Called for assignment expressions.
parser.hooks.assign
  .for('module.exports')
  .tap('MyPlugin', (expr) => {
    console.log('Assigning to module.exports');
  });

assignMemberChain

HookMap<SyncBailHook<[AssignmentExpression, string[]]>>
Called for chained member assignments.
parser.hooks.assignMemberChain
  .for('module')
  .tap('MyPlugin', (expr, members) => {
    // members might be ['exports', 'default']
  });

Import Meta Hooks

importMeta

SyncBailHook<[MemberExpression]>
Called for import.meta expressions.
parser.hooks.importMeta.tap('MyPlugin', (expr) => {
  console.log('import.meta accessed');
});

Complete Example: Environment Variables Plugin

const ConstDependency = require('webpack/lib/dependencies/ConstDependency');

class EnvironmentPlugin {
  constructor(env) {
    this.env = env;
  }

  apply(compiler) {
    compiler.hooks.compilation.tap(
      'EnvironmentPlugin',
      (compilation, { normalModuleFactory }) => {
        const handler = (parser) => {
          // Handle process.env.VAR_NAME
          parser.hooks.expressionAnyMember
            .for('process.env')
            .tap('EnvironmentPlugin', (expr) => {
              if (expr.property.type === 'Identifier') {
                const name = expr.property.name;
                const value = this.env[name];
                
                if (value !== undefined) {
                  const dep = new ConstDependency(
                    JSON.stringify(value),
                    expr.range
                  );
                  dep.loc = expr.loc;
                  parser.state.module.addDependency(dep);
                  return true;
                }
              }
            });

          // Handle typeof process.env.VAR_NAME
          parser.hooks.typeof
            .for('process.env')
            .tap('EnvironmentPlugin', (expr) => {
              // Return 'object' for process.env
              const dep = new ConstDependency(
                JSON.stringify('object'),
                expr.range
              );
              dep.loc = expr.loc;
              parser.state.module.addDependency(dep);
              return true;
            });
        };

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

// Usage
new EnvironmentPlugin({
  NODE_ENV: 'production',
  API_URL: 'https://api.example.com'
});

Parser State

The parser maintains state during parsing:
parser.state.module         // Current module being parsed
parser.state.compilation    // Current compilation
parser.scope               // Current scope information

Helper Functions

evaluateExpression

Evaluate an expression at build time:
const result = parser.evaluateExpression(expr);
if (result.isString()) {
  console.log('String value:', result.string);
}

walkExpressions

Walk through an array of expressions:
parser.walkExpressions(node.arguments);

getNameForExpression

Get the name for an expression:
const nameInfo = parser.getNameForExpression(expr);
if (nameInfo) {
  console.log('Name:', nameInfo.name);
}

See Also