or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

configuration.mdcore-bundling.mdindex.mdplugin-system.mdutilities.mdwatch-mode.md

plugin-system.mddocs/

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

```