CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/npm-enhanced-resolve

Highly configurable async require.resolve function with extensive customization options for module resolution

Pending

Quality

Pending

Does it follow best practices?

Impact

Pending

No eval scenarios have been run

Overview
Eval results
Files

plugin-system.mddocs/

Plugin System

Extensive plugin system with 29 built-in plugins for handling aliases, modern package features, and custom resolution logic using tapable hooks.

Capabilities

Core Plugin Classes

Built-in plugins that can be used directly or as examples for custom plugins.

/**
 * Plugin that clones basename for path resolution
 */
class CloneBasenamePlugin {
  constructor(source: string, target: string);
  apply(resolver: Resolver): void;
}

/**
 * Plugin providing detailed logging during resolution
 */
class LogInfoPlugin {
  constructor(source: string);
  apply(resolver: Resolver): void;
}

Usage Examples:

const { CloneBasenamePlugin, LogInfoPlugin } = require("enhanced-resolve");

// Create resolver with plugins
const resolve = require("enhanced-resolve").create({
  extensions: [".js", ".json"],
  plugins: [
    new LogInfoPlugin("resolve"),
    new CloneBasenamePlugin("resolve", "internal-resolve")
  ]
});

// Custom plugin example
class CustomPlugin {
  constructor(options) {
    this.options = options;
  }
  
  apply(resolver) {
    const target = resolver.ensureHook("custom-target");
    resolver.getHook("resolve").tapAsync("CustomPlugin", (request, resolveContext, callback) => {
      // Custom resolution logic
      if (request.request.startsWith("custom:")) {
        const newRequest = {
          ...request,
          request: request.request.slice(7) // Remove "custom:" prefix
        };
        resolver.doResolve(target, newRequest, "custom protocol", resolveContext, callback);
        return;
      }
      callback();
    });
  }
}

Plugin Categories

Path Resolution Plugins

/**
 * Handles module aliases and path mapping
 */
class AliasPlugin {
  constructor(source: string, options: AliasOptions, target: string);
  apply(resolver: Resolver): void;
}

/**
 * Processes alias fields from package.json
 */
class AliasFieldPlugin {
  constructor(source: string, field: string, target: string);
  apply(resolver: Resolver): void;
}

/**
 * Maps file extensions to other extensions
 */
class ExtensionAliasPlugin {
  constructor(source: string, options: ExtensionAliasOptions, target: string);
  apply(resolver: Resolver): void;
}

/**
 * Appends suffixes to resolve requests
 */
class AppendPlugin {
  constructor(source: string, appending: string, target: string);
  apply(resolver: Resolver): void;
}

Module Resolution Plugins

/**
 * Searches for modules in hierarchical directory structure
 */
class ModulesInHierarchicalDirectoriesPlugin {
  constructor(source: string, directories: string[], target: string);
  apply(resolver: Resolver): void;
}

/**
 * Searches for modules in root directories
 */
class ModulesInRootPlugin {
  constructor(source: string, path: string, target: string);
  apply(resolver: Resolver): void;
}

/**
 * Handles self-referencing packages via exports field
 */
class SelfReferencePlugin {
  constructor(source: string, pkg: JsonObject, target: string);
  apply(resolver: Resolver): void;
}

Modern Package Features

/**
 * Processes package.json exports field
 */
class ExportsFieldPlugin {
  constructor(source: string, conditionNames: Set<string>, fieldNamePath: string[], target: string);
  apply(resolver: Resolver): void;
}

/**
 * Processes package.json imports field
 */
class ImportsFieldPlugin {
  constructor(source: string, conditionNames: Set<string>, fieldNamePath: string[], target: string);
  apply(resolver: Resolver): void;
}

File System Plugins

/**
 * Checks if files exist on the file system
 */
class FileExistsPlugin {
  constructor(source: string, target: string);
  apply(resolver: Resolver): void;
}

/**
 * Checks if directories exist on the file system
 */
class DirectoryExistsPlugin {
  constructor(source: string, target: string);
  apply(resolver: Resolver): void;
}

/**
 * Handles symbolic link resolution
 */
class SymlinkPlugin {
  constructor(source: string, target: string);
  apply(resolver: Resolver): void;
}

Custom Plugin Development

Framework for creating custom plugins using tapable hooks.

/**
 * Base plugin interface
 */
interface Plugin {
  apply(resolver: Resolver): void;
}

/**
 * Resolver with hook system
 */
class Resolver {
  hooks: {
    resolve: AsyncSeriesBailHook;
    result: AsyncSeriesHook;
    resolveStep: SyncHook;
    noResolve: SyncHook;
  };
  
  /**
   * Ensure a hook exists
   */
  ensureHook(name: string): AsyncSeriesBailHook;
  
  /**
   * Get a named hook
   */
  getHook(name: string): AsyncSeriesBailHook;
  
  /**
   * Perform resolution with hook
   */
  doResolve(
    hook: AsyncSeriesBailHook,
    request: ResolveRequest,
    message: string,
    resolveContext: ResolveContext,
    callback: (err?: Error, result?: ResolveRequest) => void
  ): void;
}

Custom Plugin Examples:

// Plugin that adds custom protocol support
class ProtocolPlugin {
  constructor(protocol, resolver) {
    this.protocol = protocol;
    this.resolver = resolver;
  }
  
  apply(resolver) {
    const target = resolver.ensureHook("resolve");
    resolver.getHook("resolve").tapAsync("ProtocolPlugin", (request, resolveContext, callback) => {
      if (request.request.startsWith(this.protocol + ":")) {
        const newRequest = {
          ...request,
          request: this.resolver(request.request.slice(this.protocol.length + 1))
        };
        resolver.doResolve(target, newRequest, `${this.protocol} protocol`, resolveContext, callback);
        return;
      }
      callback();
    });
  }
}

// Plugin that adds conditional resolution based on environment
class EnvironmentPlugin {
  constructor(conditions) {
    this.conditions = conditions;
  }
  
  apply(resolver) {
    resolver.getHook("resolve").tapAsync("EnvironmentPlugin", (request, resolveContext, callback) => {
      const env = process.env.NODE_ENV || "development";
      
      if (this.conditions[env]) {
        const condition = this.conditions[env];
        if (condition.alias && condition.alias[request.request]) {
          const newRequest = {
            ...request,
            request: condition.alias[request.request]
          };
          resolver.doResolve(resolver.ensureHook("resolve"), newRequest, 
            `environment ${env}`, resolveContext, callback);
          return;
        }
      }
      
      callback();
    });
  }
}

// Usage
const resolve = require("enhanced-resolve").create({
  plugins: [
    new ProtocolPlugin("virtual", (path) => `/virtual/${path}`),
    new EnvironmentPlugin({
      development: {
        alias: {
          "lodash": "lodash/fp"
        }
      },
      production: {
        alias: {
          "lodash": "lodash-es"
        }
      }
    })
  ]
});

Available Built-in Plugins

Complete list of built-in plugins and their purposes:

// Path Resolution
class AliasPlugin {} // Module aliases
class AliasFieldPlugin {} // Package.json alias fields
class ExtensionAliasPlugin {} // Extension mapping
class AppendPlugin {} // Append suffixes

// Module Resolution  
class ModulesInHierarchicalDirectoriesPlugin {} // node_modules traversal
class ModulesInRootPlugin {} // Root directory modules
class SelfReferencePlugin {} // Self-referencing packages

// Modern Package Features
class ExportsFieldPlugin {} // package.json exports
class ImportsFieldPlugin {} // package.json imports

// File System
class FileExistsPlugin {} // File existence checks
class DirectoryExistsPlugin {} // Directory existence checks
class SymlinkPlugin {} // Symlink handling

// Package.json Processing
class DescriptionFilePlugin {} // Read package.json
class MainFieldPlugin {} // Process main fields
class UseFilePlugin {} // Use specific files

// Request Processing
class ParsePlugin {} // Parse requests
class JoinRequestPlugin {} // Join request parts
class JoinRequestPartPlugin {} // Join specific parts

// Flow Control
class ConditionalPlugin {} // Conditional logic
class NextPlugin {} // Pass to next hook
class TryNextPlugin {} // Try next on failure
class ResultPlugin {} // Handle results

// Advanced Features
class PnpPlugin {} // Yarn Plug'n'Play
class UnsafeCachePlugin {} // Result caching
class RestrictionsPlugin {} // Path restrictions
class RootsPlugin {} // Root path handling
class CloneBasenamePlugin {} // Clone basename
class LogInfoPlugin {} // Detailed logging

Types

type Plugin = 
  | undefined
  | null
  | false
  | ""
  | 0
  | { apply: (resolver: Resolver) => void }
  | ((resolver: Resolver) => void);

interface AsyncSeriesBailHook<T, R> {
  tapAsync(name: string, fn: (...args: [...T, (err?: Error, result?: R) => void]) => void): void;
  tap(name: string, fn: (...args: T) => R | void): void;
  callAsync(...args: [...T, (err?: Error, result?: R) => void]): void;
}

interface AsyncSeriesHook<T> {
  tapAsync(name: string, fn: (...args: [...T, (err?: Error) => void]) => void): void;
  tap(name: string, fn: (...args: T) => void): void;
  callAsync(...args: [...T, (err?: Error) => void]): void;
}

interface SyncHook<T> {
  tap(name: string, fn: (...args: T) => void): void;
  call(...args: T): void;
}

Install with Tessl CLI

npx tessl i tessl/npm-enhanced-resolve

docs

core-resolution.md

file-system.md

index.md

modern-features.md

plugin-system.md

resolver-factory.md

tile.json