0
# Plugin System
1
2
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.
3
4
## Capabilities
5
6
### Plugin Interface
7
8
Core plugin interface for creating Rollup plugins with lifecycle hooks and metadata.
9
10
```typescript { .api }
11
/**
12
* Plugin interface for extending Rollup functionality
13
*/
14
interface Plugin<A = any> extends OutputPlugin, Partial<PluginHooks> {
15
/** Plugin identifier (required) */
16
name: string;
17
/** Plugin version */
18
version?: string;
19
/** Inter-plugin communication API */
20
api?: A;
21
}
22
23
interface OutputPlugin extends Partial<Record<OutputPluginHooks, PluginHooks[OutputPluginHooks]>>,
24
Partial<Record<AddonHooks, ObjectHook<AddonHook>>> {
25
/** Cache key for plugin output */
26
cacheKey?: string;
27
/** Plugin name (required) */
28
name: string;
29
/** Plugin version */
30
version?: string;
31
}
32
33
type OutputPluginHooks =
34
| 'augmentChunkHash'
35
| 'generateBundle'
36
| 'outputOptions'
37
| 'renderChunk'
38
| 'renderDynamicImport'
39
| 'renderError'
40
| 'renderStart'
41
| 'resolveFileUrl'
42
| 'resolveImportMeta'
43
| 'writeBundle';
44
45
type AddonHooks = 'banner' | 'footer' | 'intro' | 'outro';
46
```
47
48
**Usage Examples:**
49
50
```typescript
51
// Basic plugin
52
const myPlugin = () => ({
53
name: 'my-plugin',
54
version: '1.0.0',
55
buildStart(options) {
56
console.log('Build starting...');
57
},
58
load(id) {
59
if (id.endsWith('?inline')) {
60
return `export default ${JSON.stringify(
61
fs.readFileSync(id.slice(0, -7), 'utf8')
62
)}`;
63
}
64
}
65
});
66
67
// Plugin with API
68
const utilityPlugin = () => ({
69
name: 'utility-plugin',
70
api: {
71
getUtilityPath: () => '/path/to/utility',
72
processData: (data) => processUtilityData(data)
73
},
74
buildStart() {
75
this.api.initialize();
76
}
77
});
78
79
// Using plugin APIs
80
const consumerPlugin = () => ({
81
name: 'consumer-plugin',
82
buildStart() {
83
const utilPlugin = this.resolve('utility-plugin');
84
if (utilPlugin?.api) {
85
const path = utilPlugin.api.getUtilityPath();
86
console.log('Utility path:', path);
87
}
88
}
89
});
90
```
91
92
### Plugin Context
93
94
Context object provided to plugin hooks with utilities for bundle manipulation and communication.
95
96
```typescript { .api }
97
/**
98
* Context object provided to plugin hooks
99
*/
100
interface PluginContext extends MinimalPluginContext {
101
/** Add file to watch list */
102
addWatchFile: (id: string) => void;
103
/** Plugin-specific cache */
104
cache: PluginCache;
105
/** Emit additional files (assets/chunks) */
106
emitFile: EmitFile;
107
/** Get emitted file name by reference ID */
108
getFileName: (fileReferenceId: string) => string;
109
/** Get iterator of all module IDs */
110
getModuleIds: () => IterableIterator<string>;
111
/** Get module information */
112
getModuleInfo: GetModuleInfo;
113
/** Get list of watched files */
114
getWatchFiles: () => string[];
115
/** Load module with options */
116
load: (options: LoadOptions & Partial<PartialNull<ModuleOptions>>) => Promise<ModuleInfo>;
117
/** Parse code to AST */
118
parse: ParseAst;
119
/** Resolve module path */
120
resolve: (
121
source: string,
122
importer?: string,
123
options?: {
124
attributes?: Record<string, string>;
125
custom?: CustomPluginOptions;
126
isEntry?: boolean;
127
skipSelf?: boolean;
128
}
129
) => Promise<ResolvedId | null>;
130
/** Set asset source content */
131
setAssetSource: (assetReferenceId: string, source: string | Uint8Array) => void;
132
/** File system interface */
133
fs: RollupFsModule;
134
}
135
136
interface TransformPluginContext extends PluginContext {
137
/** Debug logging with position support */
138
debug: LoggingFunctionWithPosition;
139
/** Error with position support */
140
error: (error: RollupError | string, pos?: number | { column: number; line: number }) => never;
141
/** Get combined source map for transformations */
142
getCombinedSourcemap: () => SourceMap;
143
/** Info logging with position support */
144
info: LoggingFunctionWithPosition;
145
/** Warning with position support */
146
warn: LoggingFunctionWithPosition;
147
}
148
149
interface LoadOptions {
150
id: string;
151
resolveDependencies?: boolean;
152
}
153
154
type LoggingFunctionWithPosition = (
155
log: RollupLog | string | (() => RollupLog | string),
156
pos?: number | { column: number; line: number }
157
) => void;
158
159
interface MinimalPluginContext {
160
/** Debug logging */
161
debug: LoggingFunction;
162
/** Throw build error */
163
error: (error: RollupError | string) => never;
164
/** Info logging */
165
info: LoggingFunction;
166
/** Plugin metadata */
167
meta: PluginContextMeta;
168
/** Warning logging */
169
warn: LoggingFunction;
170
}
171
172
interface PluginContextMeta {
173
/** Current Rollup version */
174
rollupVersion: string;
175
/** Whether running in watch mode */
176
watchMode: boolean;
177
}
178
```
179
180
### Input Plugin Hooks
181
182
Lifecycle hooks for processing input modules during the build phase.
183
184
```typescript { .api }
185
/**
186
* Input plugin hooks for build processing
187
*/
188
interface InputPluginHooks {
189
/** Build initialization */
190
buildStart: (this: PluginContext, options: NormalizedInputOptions) => void;
191
/** Build completion */
192
buildEnd: (this: PluginContext, error?: Error) => void;
193
/** Bundle closure */
194
closeBundle: (this: PluginContext, error?: Error) => void;
195
/** Watcher closure */
196
closeWatcher: (this: PluginContext) => void;
197
/** Module loading */
198
load: LoadHook;
199
/** Module parsing completion */
200
moduleParsed: ModuleParsedHook;
201
/** Log filtering */
202
onLog: (this: MinimalPluginContext, level: LogLevel, log: RollupLog) => boolean | null;
203
/** Options processing */
204
options: (this: MinimalPluginContext, options: InputOptions) => InputOptions | null;
205
/** Module resolution */
206
resolveId: ResolveIdHook;
207
/** Dynamic import resolution */
208
resolveDynamicImport: ResolveDynamicImportHook;
209
/** Cache validation */
210
shouldTransformCachedModule: ShouldTransformCachedModuleHook;
211
/** Code transformation */
212
transform: TransformHook;
213
/** File change handling */
214
watchChange: WatchChangeHook;
215
}
216
217
type LoadHook = (this: PluginContext, id: string) => LoadResult;
218
type ResolveIdHook = (
219
this: PluginContext,
220
source: string,
221
importer: string | undefined,
222
options: { attributes: Record<string, string>; custom?: CustomPluginOptions; isEntry: boolean }
223
) => ResolveIdResult;
224
type TransformHook = (this: TransformPluginContext, code: string, id: string) => TransformResult;
225
226
type LoadResult = SourceDescription | string | null;
227
type ResolveIdResult = string | null | false | PartialResolvedId;
228
type TransformResult = string | null | Partial<SourceDescription>;
229
```
230
231
**Usage Examples:**
232
233
```typescript
234
// Module resolution plugin
235
const resolverPlugin = () => ({
236
name: 'resolver-plugin',
237
resolveId(source, importer) {
238
if (source.startsWith('virtual:')) {
239
return source; // Handle virtual modules
240
}
241
if (source.startsWith('~')) {
242
return path.resolve('src', source.slice(1));
243
}
244
return null; // Let other plugins handle
245
},
246
load(id) {
247
if (id.startsWith('virtual:')) {
248
return `export default "Virtual module: ${id}";`;
249
}
250
return null;
251
}
252
});
253
254
// Transform plugin
255
const transformPlugin = () => ({
256
name: 'transform-plugin',
257
transform(code, id) {
258
if (id.endsWith('.special')) {
259
return {
260
code: processSpecialFile(code),
261
map: generateSourceMap(code, id)
262
};
263
}
264
return null;
265
}
266
});
267
268
// Build lifecycle plugin
269
const lifecyclePlugin = () => ({
270
name: 'lifecycle-plugin',
271
buildStart(options) {
272
console.log('Build started with options:', options.input);
273
this.addWatchFile('config.json'); // Watch additional files
274
},
275
moduleParsed(moduleInfo) {
276
console.log(`Parsed module: ${moduleInfo.id}`);
277
},
278
buildEnd(error) {
279
if (error) {
280
console.error('Build failed:', error.message);
281
} else {
282
console.log('Build completed successfully');
283
}
284
}
285
});
286
```
287
288
### Output Plugin Hooks
289
290
Hooks for processing generated output during the render and write phases.
291
292
```typescript { .api }
293
/**
294
* Output plugin hooks for render processing
295
*/
296
interface OutputPluginHooks {
297
/** Add hash contribution */
298
augmentChunkHash: (this: PluginContext, chunk: RenderedChunk) => string | void;
299
/** Bundle generation */
300
generateBundle: (
301
this: PluginContext,
302
options: NormalizedOutputOptions,
303
bundle: OutputBundle,
304
isWrite: boolean
305
) => void;
306
/** Output options processing */
307
outputOptions: (this: PluginContext, options: OutputOptions) => OutputOptions | null;
308
/** Chunk rendering */
309
renderChunk: RenderChunkHook;
310
/** Dynamic import rendering */
311
renderDynamicImport: (this: PluginContext, options: RenderDynamicImportOptions) => RenderDynamicImportResult;
312
/** Render error handling */
313
renderError: (this: PluginContext, error?: Error) => void;
314
/** Render initialization */
315
renderStart: (
316
this: PluginContext,
317
outputOptions: NormalizedOutputOptions,
318
inputOptions: NormalizedInputOptions
319
) => void;
320
/** File URL resolution */
321
resolveFileUrl: ResolveFileUrlHook;
322
/** Import meta resolution */
323
resolveImportMeta: ResolveImportMetaHook;
324
/** Bundle writing completion */
325
writeBundle: (
326
this: PluginContext,
327
options: NormalizedOutputOptions,
328
bundle: OutputBundle
329
) => void;
330
}
331
332
type RenderChunkHook = (
333
this: PluginContext,
334
code: string,
335
chunk: RenderedChunk,
336
options: NormalizedOutputOptions,
337
meta: { chunks: Record<string, RenderedChunk> }
338
) => { code: string; map?: SourceMapInput } | string | null;
339
```
340
341
**Usage Examples:**
342
343
```typescript
344
// Output processing plugin
345
const outputPlugin = () => ({
346
name: 'output-plugin',
347
renderStart(outputOptions, inputOptions) {
348
console.log(`Rendering for format: ${outputOptions.format}`);
349
},
350
renderChunk(code, chunk) {
351
if (chunk.isEntry) {
352
// Add header to entry chunks
353
return {
354
code: `/* Entry: ${chunk.name} */\n${code}`,
355
map: null
356
};
357
}
358
return null;
359
},
360
generateBundle(options, bundle) {
361
// Add manifest file
362
this.emitFile({
363
type: 'asset',
364
fileName: 'manifest.json',
365
source: JSON.stringify({
366
files: Object.keys(bundle),
367
timestamp: Date.now()
368
})
369
});
370
},
371
writeBundle(options, bundle) {
372
console.log(`Written ${Object.keys(bundle).length} files to ${options.dir || options.file}`);
373
}
374
});
375
376
// Code transformation plugin
377
const codeProcessorPlugin = () => ({
378
name: 'code-processor',
379
renderChunk(code, chunk, options) {
380
if (options.format === 'umd') {
381
// Add UMD-specific modifications
382
return addUmdPolyfills(code);
383
}
384
return null;
385
},
386
augmentChunkHash(chunk) {
387
// Include custom data in hash
388
return JSON.stringify(chunk.exports);
389
}
390
});
391
```
392
393
### Plugin Utilities
394
395
Utility functions and types for advanced plugin development.
396
397
```typescript { .api }
398
/**
399
* File emission utilities
400
*/
401
type EmitFile = (emittedFile: EmittedFile) => string;
402
403
type EmittedFile = EmittedAsset | EmittedChunk | EmittedPrebuiltChunk;
404
405
interface EmittedAsset {
406
type: 'asset';
407
name?: string;
408
fileName?: string;
409
source?: string | Uint8Array;
410
needsCodeReference?: boolean;
411
}
412
413
interface EmittedChunk {
414
type: 'chunk';
415
id: string;
416
name?: string;
417
fileName?: string;
418
implicitlyLoadedAfterOneOf?: string[];
419
importer?: string;
420
preserveSignature?: PreserveEntrySignaturesOption;
421
}
422
423
/**
424
* Plugin cache interface
425
*/
426
interface PluginCache {
427
delete(id: string): boolean;
428
get<T = any>(id: string): T;
429
has(id: string): boolean;
430
set<T = any>(id: string, value: T): void;
431
}
432
433
/**
434
* Module information interface
435
*/
436
interface ModuleInfo extends ModuleOptions {
437
id: string;
438
code: string | null;
439
ast: ProgramNode | null;
440
isEntry: boolean;
441
isExternal: boolean;
442
isIncluded: boolean | null;
443
importers: readonly string[];
444
dynamicImporters: readonly string[];
445
importedIds: readonly string[];
446
importedIdResolutions: readonly ResolvedId[];
447
dynamicallyImportedIds: readonly string[];
448
dynamicallyImportedIdResolutions: readonly ResolvedId[];
449
implicitlyLoadedAfterOneOf: readonly string[];
450
implicitlyLoadedBefore: readonly string[];
451
exports: string[] | null;
452
exportedBindings: Record<string, string[]> | null;
453
hasDefaultExport: boolean | null;
454
}
455
```
456
457
## Advanced Plugin Patterns
458
459
### Virtual Module Plugin
460
461
```typescript
462
const virtualModulesPlugin = (modules) => {
463
const virtualModules = new Map(Object.entries(modules));
464
465
return {
466
name: 'virtual-modules',
467
resolveId(id) {
468
if (virtualModules.has(id)) {
469
return id;
470
}
471
return null;
472
},
473
load(id) {
474
if (virtualModules.has(id)) {
475
return virtualModules.get(id);
476
}
477
return null;
478
}
479
};
480
};
481
482
// Usage
483
rollup({
484
input: 'src/main.js',
485
plugins: [
486
virtualModulesPlugin({
487
'virtual:config': 'export default { version: "1.0.0" };',
488
'virtual:env': `export default ${JSON.stringify(process.env)};`
489
})
490
]
491
});
492
```
493
494
### Asset Processing Plugin
495
496
```typescript
497
const assetPlugin = () => ({
498
name: 'asset-plugin',
499
load(id) {
500
if (id.endsWith('?asset')) {
501
const filePath = id.slice(0, -6);
502
const source = fs.readFileSync(filePath);
503
504
const assetId = this.emitFile({
505
type: 'asset',
506
name: path.basename(filePath),
507
source
508
});
509
510
return `export default import.meta.ROLLUP_FILE_URL_${assetId};`;
511
}
512
return null;
513
},
514
generateBundle() {
515
// Post-process emitted assets
516
for (const [fileName, asset] of Object.entries(this.bundle)) {
517
if (asset.type === 'asset' && fileName.endsWith('.svg')) {
518
asset.source = optimizeSvg(asset.source);
519
}
520
}
521
}
522
});
523
```
524
525
### Development Plugin
526
527
```typescript
528
const devPlugin = () => ({
529
name: 'dev-plugin',
530
buildStart() {
531
if (this.meta.watchMode) {
532
console.log('π Development mode active');
533
}
534
},
535
watchChange(id, { event }) {
536
console.log(`π ${event.toUpperCase()}: ${path.relative(process.cwd(), id)}`);
537
},
538
buildEnd(error) {
539
if (error) {
540
console.error('β Build failed:', error.message);
541
} else {
542
console.log('β Build successful');
543
}
544
}
545
});
546
```