# Plugin System

Extensible plugin architecture for custom build transformations, file resolution, and code generation. The plugin system allows deep customization of the build process through lifecycle hooks and custom resolution logic.

## Capabilities

### Plugin Interface

Core structure for creating ESBuild plugins.

```typescript { .api }
interface Plugin {
  /**
   * Name of the plugin for debugging and error reporting
   */
  name: string;
  
  /**
   * Setup function called when the plugin is registered
   * @param build - Build context providing plugin APIs
   */
  setup(build: PluginBuild): void;
}
```

**Basic Plugin Example:**

```typescript
import * as esbuild from "esbuild";

const examplePlugin: esbuild.Plugin = {
  name: "example-plugin",
  setup(build) {
    // Plugin logic goes here
    console.log("Plugin initialized");
  },
};

await esbuild.build({
  entryPoints: ["src/app.js"],
  bundle: true,
  outfile: "dist/app.js",
  plugins: [examplePlugin],
});
```

### Plugin Build Context

The build context provided to plugin setup functions, offering access to build hooks and utilities.

```typescript { .api }
interface PluginBuild {
  /**
   * Initial build options passed to esbuild
   */
  initialOptions: BuildOptions;
  
  /**
   * Register callback for build start
   * @param callback - Function called when build starts
   */
  onStart(callback: () => void | Promise<OnStartResult>): void;
  
  /**
   * Register callback for build end
   * @param callback - Function called when build completes
   */ 
  onEnd(callback: (result: BuildResult) => void | Promise<OnEndResult>): void;
  
  /**
   * Register callback for module resolution
   * @param options - Resolution filter and configuration
   * @param callback - Function to handle resolution
   */
  onResolve(
    options: OnResolveOptions,
    callback: (args: OnResolveArgs) => OnResolveResult | Promise<OnResolveResult>
  ): void;
  
  /**
   * Register callback for file loading
   * @param options - Loading filter and configuration
   * @param callback - Function to handle file loading
   */
  onLoad(
    options: OnLoadOptions,
    callback: (args: OnLoadArgs) => OnLoadResult | Promise<OnLoadResult>
  ): void;
  
  /**
   * Register cleanup callback for plugin disposal
   * @param callback - Function called when plugin is disposed
   */
  onDispose(callback: () => void): void;
  
  /**
   * Resolve a module path using esbuild's resolution logic
   * @param path - Module path to resolve
   * @param options - Resolution options
   * @returns Promise resolving to resolution result
   */
  resolve(
    path: string,
    options?: ResolveOptions
  ): Promise<ResolveResult>;
  
  /**
   * Access to esbuild APIs for advanced plugin functionality
   */
  esbuild: {
    build: typeof build;
    transform: typeof transform;
    formatMessages: typeof formatMessages;
    analyzeMetafile: typeof analyzeMetafile;
  };
}
```

### Build Lifecycle Hooks

#### onStart Hook

Called when a build operation begins.

```typescript { .api }
/**
 * Register callback for build start
 * @param callback - Function called when build starts
 */
onStart(callback: () => void | Promise<OnStartResult>): void;

interface OnStartResult {
  errors?: PartialMessage[];
  warnings?: PartialMessage[];
}
```

**Usage Example:**

```typescript
const buildStartPlugin: esbuild.Plugin = {
  name: "build-start",
  setup(build) {
    build.onStart(() => {
      console.log("Build started at", new Date().toISOString());
    });
  },
};
```

#### onEnd Hook

Called when a build operation completes.

```typescript { .api }
/**
 * Register callback for build end
 * @param callback - Function called when build completes
 */
onEnd(callback: (result: BuildResult) => void | Promise<OnEndResult>): void;

interface OnEndResult {
  errors?: PartialMessage[];
  warnings?: PartialMessage[];
}
```

**Usage Example:**

```typescript
const buildEndPlugin: esbuild.Plugin = {
  name: "build-end",
  setup(build) {
    build.onEnd((result) => {
      console.log(`Build completed with ${result.errors.length} errors`);
      
      if (result.metafile) {
        // Analyze the build
        return esbuild.analyzeMetafile(result.metafile).then(analysis => {
          console.log("Bundle analysis:", analysis);
        });
      }
    });
  },
};
```

### Module Resolution Hooks

#### onResolve Hook

Intercept and customize module resolution logic.

```typescript { .api }
/**
 * Register callback for module resolution
 * @param options - Resolution filter and configuration
 * @param callback - Function to handle resolution
 */
onResolve(
  options: OnResolveOptions,
  callback: (args: OnResolveArgs) => OnResolveResult | Promise<OnResolveResult>
): void;

interface OnResolveOptions {
  filter: RegExp;
  namespace?: string;
}

interface OnResolveArgs {
  path: string;
  importer: string;
  namespace: string;
  resolveDir: string;
  kind: ImportKind;
  pluginData: any;
}

interface OnResolveResult {
  errors?: PartialMessage[];
  warnings?: PartialMessage[];
  path?: string;
  external?: boolean;
  sideEffects?: boolean;
  namespace?: string;
  suffix?: string;
  pluginData?: any;
  watchFiles?: string[];
  watchDirs?: string[];
}

type ImportKind = 
  | "entry-point"
  | "import-statement" 
  | "require-call"
  | "dynamic-import"
  | "require-resolve"
  | "import-rule"
  | "composes-from"
  | "url-token";
```

**Usage Examples:**

```typescript
// Redirect imports to different files
const redirectPlugin: esbuild.Plugin = {
  name: "redirect",
  setup(build) {
    build.onResolve({ filter: /^old-package$/ }, (args) => {
      return { path: "new-package", external: true };
    });
  },
};

// Resolve virtual modules
const virtualPlugin: esbuild.Plugin = {
  name: "virtual",
  setup(build) {
    build.onResolve({ filter: /^virtual:/ }, (args) => {
      return {
        path: args.path,
        namespace: "virtual",
      };
    });
  },
};
```

### File Loading Hooks

#### onLoad Hook

Intercept and customize file loading logic.

```typescript { .api }
/**
 * Register callback for file loading
 * @param options - Loading filter and configuration
 * @param callback - Function to handle file loading
 */
onLoad(
  options: OnLoadOptions,
  callback: (args: OnLoadArgs) => OnLoadResult | Promise<OnLoadResult>
): void;

interface OnLoadOptions {
  filter: RegExp;
  namespace?: string;
}

interface OnLoadArgs {
  path: string;
  namespace: string;
  suffix: string;
  pluginData: any;
}

interface OnLoadResult {
  errors?: PartialMessage[];
  warnings?: PartialMessage[];
  contents?: string | Uint8Array;
  loader?: Loader;
  resolveDir?: string;
  pluginData?: any;
  watchFiles?: string[];
  watchDirs?: string[];
}
```

**Usage Examples:**

```typescript
// Load virtual modules
const virtualLoaderPlugin: esbuild.Plugin = {
  name: "virtual-loader",
  setup(build) {
    build.onLoad({ filter: /.*/, namespace: "virtual" }, (args) => {
      if (args.path === "virtual:config") {
        return {
          contents: `export const config = ${JSON.stringify({
            apiUrl: process.env.API_URL || "http://localhost:3000",
            version: "1.0.0",
          })};`,
          loader: "js",
        };
      }
    });
  },
};

// Transform file contents
const preprocessPlugin: esbuild.Plugin = {
  name: "preprocess",
  setup(build) {
    build.onLoad({ filter: /\.special$/ }, async (args) => {
      const fs = require("fs");
      const contents = await fs.promises.readFile(args.path, "utf8");
      
      // Custom preprocessing
      const processed = contents
        .replace(/%%VERSION%%/g, "1.0.0")
        .replace(/%%BUILD_TIME%%/g, new Date().toISOString());
      
      return {
        contents: processed,
        loader: "js",
      };
    });
  },
};
```

### Advanced Plugin Features

#### Resolve Method

Use esbuild's resolution logic within plugins.

```typescript { .api }
/**
 * Resolve a module path using esbuild's resolution logic
 * @param path - Module path to resolve
 * @param options - Resolution options
 * @returns Promise resolving to resolution result
 */
resolve(path: string, options?: ResolveOptions): Promise<ResolveResult>;

interface ResolveOptions {
  importer?: string;
  namespace?: string;
  resolveDir?: string;
  kind?: ImportKind;
  pluginData?: any;
}

interface ResolveResult {
  errors: Message[];
  warnings: Message[];
  path: string;
  external: boolean;
  sideEffects: boolean;
  namespace: string;
  suffix: string;
  pluginData: any;
}
```

**Usage Example:**

```typescript
const resolverPlugin: esbuild.Plugin = {
  name: "resolver",
  setup(build) {
    build.onLoad({ filter: /\.resolve-test$/ }, async (args) => {
      // Use esbuild's resolver to find a dependency
      const result = await build.resolve("react", {
        resolveDir: path.dirname(args.path),
        kind: "import-statement",
      });
      
      if (result.errors.length > 0) {
        return { errors: result.errors };
      }
      
      return {
        contents: `export const reactPath = ${JSON.stringify(result.path)};`,
        loader: "js",
      };
    });
  },
};
```

#### Plugin Data Flow

Pass data between plugin hooks using pluginData.

```typescript
const dataFlowPlugin: esbuild.Plugin = {
  name: "data-flow",
  setup(build) {
    build.onResolve({ filter: /\.tracked$/ }, (args) => {
      return {
        path: args.path,
        pluginData: {
          originalImporter: args.importer,
          resolvedAt: Date.now(),
        },
      };
    });
    
    build.onLoad({ filter: /\.tracked$/ }, (args) => {
      console.log("Loading file with data:", args.pluginData);
      
      // Access data from onResolve
      const { originalImporter, resolvedAt } = args.pluginData;
      
      return {
        contents: `// Resolved from ${originalImporter} at ${resolvedAt}`,
        loader: "js",
      };
    });
  },
};
```

## Complete Plugin Examples

### Environment Variable Replacement

```typescript
const envPlugin: esbuild.Plugin = {
  name: "env",
  setup(build) {
    const options = build.initialOptions;
    options.define = options.define || {};
    
    // Add environment variables to define
    for (const [key, value] of Object.entries(process.env)) {
      if (key.startsWith("PUBLIC_")) {
        options.define[`process.env.${key}`] = JSON.stringify(value);
      }
    }
  },
};
```

### Asset Processing Plugin

```typescript
const assetPlugin: esbuild.Plugin = {
  name: "assets",
  setup(build) {
    const fs = require("fs");
    const path = require("path");
    
    build.onResolve({ filter: /\.(png|jpg|svg)$/ }, (args) => {
      return {
        path: path.resolve(args.resolveDir, args.path),
        namespace: "asset",
      };
    });
    
    build.onLoad({ filter: /.*/, namespace: "asset" }, async (args) => {
      const contents = await fs.promises.readFile(args.path);
      const base64 = contents.toString("base64");
      const ext = path.extname(args.path).slice(1);
      const mimeType = {
        png: "image/png",
        jpg: "image/jpeg", 
        svg: "image/svg+xml",
      }[ext] || "application/octet-stream";
      
      return {
        contents: `export default "data:${mimeType};base64,${base64}";`,
        loader: "js",
      };
    });
  },
};
```