CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/npm-rollup

Next-generation ES module bundler that compiles small pieces of code into larger, more complex applications or libraries

Pending
Overview
Eval results
Files

plugin-system.mddocs/

Plugin System

Comprehensive hook-based plugin architecture for extending bundling behavior, with lifecycle hooks for input processing, transformation, and output generation. Rollup's plugin system provides fine-grained control over every aspect of the bundling process.

Capabilities

Plugin Interface

Core plugin interface for creating Rollup plugins with lifecycle hooks and metadata.

/**
 * Plugin interface for extending Rollup functionality
 */
interface Plugin<A = any> extends OutputPlugin, Partial<PluginHooks> {
  /** Plugin identifier (required) */
  name: string;
  /** Plugin version */
  version?: string;
  /** Inter-plugin communication API */
  api?: A;
}

interface OutputPlugin extends Partial<Record<OutputPluginHooks, PluginHooks[OutputPluginHooks]>>, 
  Partial<Record<AddonHooks, ObjectHook<AddonHook>>> {
  /** Cache key for plugin output */
  cacheKey?: string;
  /** Plugin name (required) */
  name: string;
  /** Plugin version */
  version?: string;
}

type OutputPluginHooks = 
  | 'augmentChunkHash'
  | 'generateBundle'
  | 'outputOptions'
  | 'renderChunk'
  | 'renderDynamicImport'
  | 'renderError'
  | 'renderStart'
  | 'resolveFileUrl'
  | 'resolveImportMeta'
  | 'writeBundle';

type AddonHooks = 'banner' | 'footer' | 'intro' | 'outro';

Usage Examples:

// Basic plugin
const myPlugin = () => ({
  name: 'my-plugin',
  version: '1.0.0',
  buildStart(options) {
    console.log('Build starting...');
  },
  load(id) {
    if (id.endsWith('?inline')) {
      return `export default ${JSON.stringify(
        fs.readFileSync(id.slice(0, -7), 'utf8')
      )}`;
    }
  }
});

// Plugin with API
const utilityPlugin = () => ({
  name: 'utility-plugin',
  api: {
    getUtilityPath: () => '/path/to/utility',
    processData: (data) => processUtilityData(data)
  },
  buildStart() {
    this.api.initialize();
  }
});

// Using plugin APIs
const consumerPlugin = () => ({
  name: 'consumer-plugin',
  buildStart() {
    const utilPlugin = this.resolve('utility-plugin');
    if (utilPlugin?.api) {
      const path = utilPlugin.api.getUtilityPath();
      console.log('Utility path:', path);
    }
  }
});

Plugin Context

Context object provided to plugin hooks with utilities for bundle manipulation and communication.

/**
 * Context object provided to plugin hooks
 */
interface PluginContext extends MinimalPluginContext {
  /** Add file to watch list */
  addWatchFile: (id: string) => void;
  /** Plugin-specific cache */
  cache: PluginCache;
  /** Emit additional files (assets/chunks) */
  emitFile: EmitFile;
  /** Get emitted file name by reference ID */
  getFileName: (fileReferenceId: string) => string;
  /** Get iterator of all module IDs */
  getModuleIds: () => IterableIterator<string>;
  /** Get module information */
  getModuleInfo: GetModuleInfo;
  /** Get list of watched files */
  getWatchFiles: () => string[];
  /** Load module with options */
  load: (options: LoadOptions & Partial<PartialNull<ModuleOptions>>) => Promise<ModuleInfo>;
  /** Parse code to AST */
  parse: ParseAst;
  /** Resolve module path */
  resolve: (
    source: string,
    importer?: string,
    options?: {
      attributes?: Record<string, string>;
      custom?: CustomPluginOptions;
      isEntry?: boolean;
      skipSelf?: boolean;
    }
  ) => Promise<ResolvedId | null>;
  /** Set asset source content */
  setAssetSource: (assetReferenceId: string, source: string | Uint8Array) => void;
  /** File system interface */
  fs: RollupFsModule;
}

interface TransformPluginContext extends PluginContext {
  /** Debug logging with position support */
  debug: LoggingFunctionWithPosition;
  /** Error with position support */
  error: (error: RollupError | string, pos?: number | { column: number; line: number }) => never;
  /** Get combined source map for transformations */
  getCombinedSourcemap: () => SourceMap;
  /** Info logging with position support */
  info: LoggingFunctionWithPosition;
  /** Warning with position support */
  warn: LoggingFunctionWithPosition;
}

interface LoadOptions {
  id: string;
  resolveDependencies?: boolean;
}

type LoggingFunctionWithPosition = (
  log: RollupLog | string | (() => RollupLog | string),
  pos?: number | { column: number; line: number }
) => void;

interface MinimalPluginContext {
  /** Debug logging */
  debug: LoggingFunction;
  /** Throw build error */
  error: (error: RollupError | string) => never;
  /** Info logging */
  info: LoggingFunction;
  /** Plugin metadata */
  meta: PluginContextMeta;
  /** Warning logging */
  warn: LoggingFunction;
}

interface PluginContextMeta {
  /** Current Rollup version */
  rollupVersion: string;
  /** Whether running in watch mode */
  watchMode: boolean;
}

Input Plugin Hooks

Lifecycle hooks for processing input modules during the build phase.

/**
 * Input plugin hooks for build processing
 */
interface InputPluginHooks {
  /** Build initialization */
  buildStart: (this: PluginContext, options: NormalizedInputOptions) => void;
  /** Build completion */
  buildEnd: (this: PluginContext, error?: Error) => void;
  /** Bundle closure */
  closeBundle: (this: PluginContext, error?: Error) => void;
  /** Watcher closure */
  closeWatcher: (this: PluginContext) => void;
  /** Module loading */
  load: LoadHook;
  /** Module parsing completion */
  moduleParsed: ModuleParsedHook;
  /** Log filtering */
  onLog: (this: MinimalPluginContext, level: LogLevel, log: RollupLog) => boolean | null;
  /** Options processing */
  options: (this: MinimalPluginContext, options: InputOptions) => InputOptions | null;
  /** Module resolution */
  resolveId: ResolveIdHook;
  /** Dynamic import resolution */
  resolveDynamicImport: ResolveDynamicImportHook;
  /** Cache validation */
  shouldTransformCachedModule: ShouldTransformCachedModuleHook;
  /** Code transformation */
  transform: TransformHook;
  /** File change handling */
  watchChange: WatchChangeHook;
}

type LoadHook = (this: PluginContext, id: string) => LoadResult;
type ResolveIdHook = (
  this: PluginContext,
  source: string,  
  importer: string | undefined,
  options: { attributes: Record<string, string>; custom?: CustomPluginOptions; isEntry: boolean }
) => ResolveIdResult;
type TransformHook = (this: TransformPluginContext, code: string, id: string) => TransformResult;

type LoadResult = SourceDescription | string | null;
type ResolveIdResult = string | null | false | PartialResolvedId;
type TransformResult = string | null | Partial<SourceDescription>;

Usage Examples:

// Module resolution plugin
const resolverPlugin = () => ({
  name: 'resolver-plugin',
  resolveId(source, importer) {
    if (source.startsWith('virtual:')) {
      return source; // Handle virtual modules
    }
    if (source.startsWith('~')) {
      return path.resolve('src', source.slice(1));
    }
    return null; // Let other plugins handle
  },
  load(id) {
    if (id.startsWith('virtual:')) {
      return `export default "Virtual module: ${id}";`;
    }
    return null;
  }
});

// Transform plugin
const transformPlugin = () => ({
  name: 'transform-plugin',
  transform(code, id) {
    if (id.endsWith('.special')) {
      return {
        code: processSpecialFile(code),
        map: generateSourceMap(code, id)
      };
    }
    return null;
  }
});

// Build lifecycle plugin
const lifecyclePlugin = () => ({
  name: 'lifecycle-plugin',
  buildStart(options) {
    console.log('Build started with options:', options.input);
    this.addWatchFile('config.json'); // Watch additional files
  },
  moduleParsed(moduleInfo) {
    console.log(`Parsed module: ${moduleInfo.id}`);
  },
  buildEnd(error) {
    if (error) {
      console.error('Build failed:', error.message);
    } else {
      console.log('Build completed successfully');
    }
  }
});

Output Plugin Hooks

Hooks for processing generated output during the render and write phases.

/**
 * Output plugin hooks for render processing
 */
interface OutputPluginHooks {
  /** Add hash contribution */
  augmentChunkHash: (this: PluginContext, chunk: RenderedChunk) => string | void;
  /** Bundle generation */
  generateBundle: (
    this: PluginContext,
    options: NormalizedOutputOptions,
    bundle: OutputBundle,
    isWrite: boolean
  ) => void;
  /** Output options processing */
  outputOptions: (this: PluginContext, options: OutputOptions) => OutputOptions | null;
  /** Chunk rendering */
  renderChunk: RenderChunkHook;
  /** Dynamic import rendering */
  renderDynamicImport: (this: PluginContext, options: RenderDynamicImportOptions) => RenderDynamicImportResult;
  /** Render error handling */
  renderError: (this: PluginContext, error?: Error) => void;
  /** Render initialization */
  renderStart: (
    this: PluginContext,
    outputOptions: NormalizedOutputOptions,
    inputOptions: NormalizedInputOptions
  ) => void;
  /** File URL resolution */
  resolveFileUrl: ResolveFileUrlHook;
  /** Import meta resolution */
  resolveImportMeta: ResolveImportMetaHook;
  /** Bundle writing completion */
  writeBundle: (
    this: PluginContext,
    options: NormalizedOutputOptions,
    bundle: OutputBundle
  ) => void;
}

type RenderChunkHook = (
  this: PluginContext,
  code: string,
  chunk: RenderedChunk,
  options: NormalizedOutputOptions,
  meta: { chunks: Record<string, RenderedChunk> }
) => { code: string; map?: SourceMapInput } | string | null;

Usage Examples:

// Output processing plugin
const outputPlugin = () => ({
  name: 'output-plugin',
  renderStart(outputOptions, inputOptions) {
    console.log(`Rendering for format: ${outputOptions.format}`);
  },
  renderChunk(code, chunk) {
    if (chunk.isEntry) {
      // Add header to entry chunks
      return {
        code: `/* Entry: ${chunk.name} */\n${code}`,
        map: null
      };
    }
    return null;
  },
  generateBundle(options, bundle) {
    // Add manifest file
    this.emitFile({
      type: 'asset',
      fileName: 'manifest.json',
      source: JSON.stringify({
        files: Object.keys(bundle),
        timestamp: Date.now()
      })
    });
  },
  writeBundle(options, bundle) {
    console.log(`Written ${Object.keys(bundle).length} files to ${options.dir || options.file}`);
  }
});

// Code transformation plugin
const codeProcessorPlugin = () => ({
  name: 'code-processor',
  renderChunk(code, chunk, options) {
    if (options.format === 'umd') {
      // Add UMD-specific modifications
      return addUmdPolyfills(code);
    }
    return null;
  },
  augmentChunkHash(chunk) {
    // Include custom data in hash
    return JSON.stringify(chunk.exports);
  }
});

Plugin Utilities

Utility functions and types for advanced plugin development.

/**
 * File emission utilities
 */
type EmitFile = (emittedFile: EmittedFile) => string;

type EmittedFile = EmittedAsset | EmittedChunk | EmittedPrebuiltChunk;

interface EmittedAsset {
  type: 'asset';
  name?: string;
  fileName?: string;
  source?: string | Uint8Array;
  needsCodeReference?: boolean;
}

interface EmittedChunk {
  type: 'chunk';
  id: string;
  name?: string;
  fileName?: string;
  implicitlyLoadedAfterOneOf?: string[];
  importer?: string;
  preserveSignature?: PreserveEntrySignaturesOption;
}

/**
 * Plugin cache interface
 */
interface PluginCache {
  delete(id: string): boolean;
  get<T = any>(id: string): T;
  has(id: string): boolean;
  set<T = any>(id: string, value: T): void;
}

/**
 * Module information interface
 */
interface ModuleInfo extends ModuleOptions {
  id: string;
  code: string | null;
  ast: ProgramNode | null;
  isEntry: boolean;
  isExternal: boolean;
  isIncluded: boolean | null;
  importers: readonly string[];
  dynamicImporters: readonly string[];
  importedIds: readonly string[];
  importedIdResolutions: readonly ResolvedId[];
  dynamicallyImportedIds: readonly string[];
  dynamicallyImportedIdResolutions: readonly ResolvedId[];
  implicitlyLoadedAfterOneOf: readonly string[];
  implicitlyLoadedBefore: readonly string[];
  exports: string[] | null;
  exportedBindings: Record<string, string[]> | null;
  hasDefaultExport: boolean | null;
}

Advanced Plugin Patterns

Virtual Module Plugin

const virtualModulesPlugin = (modules) => {
  const virtualModules = new Map(Object.entries(modules));
  
  return {
    name: 'virtual-modules',
    resolveId(id) {
      if (virtualModules.has(id)) {
        return id;
      }
      return null;
    },
    load(id) {
      if (virtualModules.has(id)) {
        return virtualModules.get(id);
      }
      return null;
    }
  };
};

// Usage
rollup({
  input: 'src/main.js',
  plugins: [
    virtualModulesPlugin({
      'virtual:config': 'export default { version: "1.0.0" };',
      'virtual:env': `export default ${JSON.stringify(process.env)};`
    })
  ]
});

Asset Processing Plugin

const assetPlugin = () => ({
  name: 'asset-plugin',
  load(id) {
    if (id.endsWith('?asset')) {
      const filePath = id.slice(0, -6);
      const source = fs.readFileSync(filePath);
      
      const assetId = this.emitFile({
        type: 'asset',
        name: path.basename(filePath),
        source
      });
      
      return `export default import.meta.ROLLUP_FILE_URL_${assetId};`;
    }
    return null;
  },
  generateBundle() {
    // Post-process emitted assets
    for (const [fileName, asset] of Object.entries(this.bundle)) {
      if (asset.type === 'asset' && fileName.endsWith('.svg')) {
        asset.source = optimizeSvg(asset.source);
      }
    }
  }
});

Development Plugin

const devPlugin = () => ({
  name: 'dev-plugin',
  buildStart() {
    if (this.meta.watchMode) {
      console.log('🔄 Development mode active');
    }
  },
  watchChange(id, { event }) {
    console.log(`📁 ${event.toUpperCase()}: ${path.relative(process.cwd(), id)}`);
  },
  buildEnd(error) {
    if (error) {
      console.error('❌ Build failed:', error.message);
    } else {
      console.log('✅ Build successful');
    }
  }
});

Install with Tessl CLI

npx tessl i tessl/npm-rollup

docs

configuration.md

core-bundling.md

index.md

plugin-system.md

utilities.md

watch-mode.md

tile.json