Skip to main content
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