0
# Plugin System
1
2
Comprehensive plugin architecture with lifecycle hooks, configuration modification, and inter-plugin communication. Rsbuild's plugin system provides extensive customization capabilities through a rich set of hooks and utilities.
3
4
## Capabilities
5
6
### Plugin Definition
7
8
Define plugins with lifecycle hooks and configuration modification capabilities.
9
10
```typescript { .api }
11
/**
12
* Plugin interface for extending Rsbuild functionality
13
*/
14
interface RsbuildPlugin {
15
/** Unique plugin identifier */
16
name: string;
17
/** Plugin setup function called during initialization */
18
setup: (api: RsbuildPluginAPI) => MaybePromise<void>;
19
/** Conditional application based on command or context */
20
apply?: 'serve' | 'build' | ((config: any, context: any) => boolean);
21
/** Execution order enforcement */
22
enforce?: 'pre' | 'post';
23
/** Plugins that must run before this plugin */
24
pre?: string[];
25
/** Plugins that must run after this plugin */
26
post?: string[];
27
/** Plugin names to remove from the plugin list */
28
remove?: string[];
29
}
30
31
type MaybePromise<T> = T | Promise<T>;
32
```
33
34
**Usage Examples:**
35
36
```typescript
37
import type { RsbuildPlugin } from "@rsbuild/core";
38
39
// Basic plugin
40
const myPlugin = (): RsbuildPlugin => ({
41
name: "my-plugin",
42
setup(api) {
43
api.modifyRsbuildConfig((config) => {
44
config.source = config.source || {};
45
config.source.alias = {
46
...config.source.alias,
47
"@components": "./src/components",
48
};
49
});
50
},
51
});
52
53
// Conditional plugin (dev only)
54
const devOnlyPlugin = (): RsbuildPlugin => ({
55
name: "dev-only-plugin",
56
apply: "serve",
57
setup(api) {
58
api.onAfterStartDevServer(({ port, urls }) => {
59
console.log(`Dev server running on port ${port}`);
60
});
61
},
62
});
63
64
// Plugin with execution order
65
const criticalPlugin = (): RsbuildPlugin => ({
66
name: "critical-plugin",
67
enforce: "pre",
68
setup(api) {
69
// Runs before other plugins
70
},
71
});
72
73
// Plugin with dependencies
74
const dependentPlugin = (): RsbuildPlugin => ({
75
name: "dependent-plugin",
76
pre: ["rsbuild:css", "rsbuild:html"],
77
setup(api) {
78
// Runs after CSS and HTML plugins
79
},
80
});
81
```
82
83
### Plugin API
84
85
The plugin API provides access to configuration, hooks, and utilities.
86
87
```typescript { .api }
88
/**
89
* API interface provided to plugins during setup
90
*/
91
interface RsbuildPluginAPI {
92
/** Read-only build context */
93
context: RsbuildContext;
94
/** Logging utilities */
95
logger: Logger;
96
97
// Configuration modification hooks
98
modifyRsbuildConfig: ModifyRsbuildConfigHook;
99
modifyEnvironmentConfig: ModifyEnvironmentConfigHook;
100
modifyBundlerChain: ModifyBundlerChainHook;
101
modifyRspackConfig: ModifyRspackConfigHook;
102
modifyWebpackConfig?: ModifyWebpackConfigHook;
103
104
// HTML processing hooks
105
modifyHTML: ModifyHTMLHook;
106
modifyHTMLTags: ModifyHTMLTagsHook;
107
108
// Advanced processing hooks
109
transform: TransformHook;
110
processAssets: ProcessAssetsHook;
111
resolve: ResolveHook;
112
113
// Plugin communication
114
expose: <T = any>(id: string, api: T) => void;
115
useExposed: <T = any>(id: string) => T | undefined;
116
117
// Lifecycle hooks (inherited from RsbuildInstance)
118
onBeforeBuild: (fn: OnBeforeBuildFn) => void;
119
onAfterBuild: (fn: OnAfterBuildFn) => void;
120
onCloseBuild: (fn: OnCloseBuildFn) => void;
121
onBeforeDevCompile: (fn: OnBeforeDevCompileFn) => void;
122
onAfterDevCompile: (fn: OnAfterDevCompileFn) => void;
123
onDevCompileDone: (fn: OnDevCompileDoneFn) => void;
124
onBeforeStartDevServer: (fn: OnBeforeStartDevServerFn) => void;
125
onAfterStartDevServer: (fn: OnAfterStartDevServerFn) => void;
126
onCloseDevServer: (fn: OnCloseDevServerFn) => void;
127
onBeforeCreateCompiler: (fn: OnBeforeCreateCompilerFn) => void;
128
onAfterCreateCompiler: (fn: OnAfterCreateCompilerFn) => void;
129
onExit: (fn: OnExitFn) => void;
130
}
131
```
132
133
## Configuration Modification Hooks
134
135
### Modify Rsbuild Configuration
136
137
Modify the main Rsbuild configuration object.
138
139
```typescript { .api }
140
/**
141
* Hook to modify Rsbuild configuration
142
*/
143
interface ModifyRsbuildConfigHook {
144
(fn: ModifyRsbuildConfigFn): void;
145
}
146
147
type ModifyRsbuildConfigFn = (
148
config: RsbuildConfig,
149
utils: ModifyRsbuildConfigUtils
150
) => RsbuildConfig | void | Promise<RsbuildConfig | void>;
151
152
interface ModifyRsbuildConfigUtils {
153
mergeConfig: typeof mergeRsbuildConfig;
154
}
155
```
156
157
**Usage Examples:**
158
159
```typescript
160
const configPlugin = (): RsbuildPlugin => ({
161
name: "config-plugin",
162
setup(api) {
163
api.modifyRsbuildConfig((config, { mergeConfig }) => {
164
// Direct modification
165
config.output = config.output || {};
166
config.output.assetPrefix = "/static/";
167
168
// Using merge utility
169
return mergeConfig(config, {
170
source: {
171
alias: {
172
"@utils": "./src/utils",
173
},
174
},
175
performance: {
176
chunkSplit: {
177
strategy: "split-by-experience",
178
},
179
},
180
});
181
});
182
},
183
});
184
```
185
186
### Modify Environment Configuration
187
188
Modify environment-specific configuration.
189
190
```typescript { .api }
191
/**
192
* Hook to modify environment-specific configuration
193
*/
194
interface ModifyEnvironmentConfigHook {
195
(fn: ModifyEnvironmentConfigFn): void;
196
}
197
198
type ModifyEnvironmentConfigFn = (
199
config: EnvironmentConfig,
200
utils: ModifyEnvironmentConfigUtils
201
) => EnvironmentConfig | void | Promise<EnvironmentConfig | void>;
202
203
interface ModifyEnvironmentConfigUtils {
204
/** Environment name */
205
name: string;
206
/** Merge configuration utility */
207
mergeConfig: (config: EnvironmentConfig) => EnvironmentConfig;
208
}
209
```
210
211
### Modify Bundler Chain
212
213
Modify bundler configuration using rspack-chain or webpack-chain.
214
215
```typescript { .api }
216
/**
217
* Hook to modify bundler configuration via chain API
218
*/
219
interface ModifyBundlerChainHook {
220
(fn: ModifyBundlerChainFn): void;
221
}
222
223
type ModifyBundlerChainFn = (
224
chain: RspackChain,
225
utils: ModifyBundlerChainUtils
226
) => void | Promise<void>;
227
228
interface ModifyBundlerChainUtils {
229
env: string;
230
target: RsbuildTarget;
231
isDev: boolean;
232
isProd: boolean;
233
CHAIN_ID: ChainIdentifier;
234
bundler: BundlerType;
235
getCompiledPath: (name: string) => string;
236
}
237
238
type BundlerType = 'rspack' | 'webpack';
239
```
240
241
**Usage Examples:**
242
243
```typescript
244
const chainPlugin = (): RsbuildPlugin => ({
245
name: "chain-plugin",
246
setup(api) {
247
api.modifyBundlerChain((chain, { CHAIN_ID }) => {
248
// Add a new loader
249
chain.module
250
.rule("my-rule")
251
.test(/\.special$/)
252
.use("my-loader")
253
.loader("my-loader")
254
.options({
255
customOption: true,
256
});
257
258
// Modify existing plugin
259
chain.plugin(CHAIN_ID.PLUGIN.HTML).tap((args) => {
260
args[0].title = "My App";
261
return args;
262
});
263
264
// Add environment-specific configuration
265
if (utils.isDev) {
266
chain.devtool("eval-cheap-module-source-map");
267
}
268
});
269
},
270
});
271
```
272
273
### Modify Rspack Configuration
274
275
Directly modify the Rspack configuration object.
276
277
```typescript { .api }
278
/**
279
* Hook to modify Rspack configuration directly
280
*/
281
interface ModifyRspackConfigHook {
282
(fn: ModifyRspackConfigFn): void;
283
}
284
285
type ModifyRspackConfigFn = (
286
config: RspackConfig,
287
utils: ModifyRspackConfigUtils
288
) => RspackConfig | void | Promise<RspackConfig | void>;
289
290
interface ModifyRspackConfigUtils {
291
env: string;
292
target: RsbuildTarget;
293
isDev: boolean;
294
isProd: boolean;
295
rspack: typeof import('@rspack/core');
296
}
297
```
298
299
## HTML Processing Hooks
300
301
### Modify HTML Content
302
303
Modify the final HTML content before output.
304
305
```typescript { .api }
306
/**
307
* Hook to modify HTML content
308
*/
309
interface ModifyHTMLHook {
310
(fn: ModifyHTMLFn): void;
311
}
312
313
type ModifyHTMLFn = (
314
html: string,
315
context: ModifyHTMLContext
316
) => string | Promise<string>;
317
318
interface ModifyHTMLContext {
319
assetPrefix: string;
320
filename: string;
321
environment: EnvironmentContext;
322
}
323
```
324
325
### Modify HTML Tags
326
327
Modify HTML tags that are injected into the HTML.
328
329
```typescript { .api }
330
/**
331
* Hook to modify HTML tags
332
*/
333
interface ModifyHTMLTagsHook {
334
(fn: ModifyHTMLTagsFn): void;
335
}
336
337
type ModifyHTMLTagsFn = (
338
tags: HtmlTag[],
339
context: ModifyHTMLTagsContext
340
) => HtmlTag[] | Promise<HtmlTag[]>;
341
342
interface ModifyHTMLTagsContext {
343
assetPrefix: string;
344
filename: string;
345
environment: EnvironmentContext;
346
}
347
348
interface HtmlTag {
349
tag: string;
350
attrs?: Record<string, string | boolean | undefined>;
351
children?: string;
352
hash?: boolean | string;
353
}
354
```
355
356
**Usage Examples:**
357
358
```typescript
359
const htmlPlugin = (): RsbuildPlugin => ({
360
name: "html-plugin",
361
setup(api) {
362
// Modify HTML content
363
api.modifyHTML((html, { filename }) => {
364
if (filename.includes("index")) {
365
return html.replace(
366
"<head>",
367
"<head>\n <meta name=\"custom\" content=\"value\">"
368
);
369
}
370
return html;
371
});
372
373
// Modify HTML tags
374
api.modifyHTMLTags((tags, { environment }) => {
375
// Add custom script tag
376
tags.push({
377
tag: "script",
378
attrs: {
379
src: "/analytics.js",
380
async: true,
381
},
382
});
383
384
// Add environment-specific tags
385
if (environment.name === "production") {
386
tags.push({
387
tag: "meta",
388
attrs: {
389
name: "robots",
390
content: "index,follow",
391
},
392
});
393
}
394
395
return tags;
396
});
397
},
398
});
399
```
400
401
## Advanced Processing Hooks
402
403
### Transform Hook
404
405
Transform module code during the build process.
406
407
```typescript { .api }
408
/**
409
* Hook to transform module code
410
*/
411
interface TransformHook {
412
(descriptor: TransformDescriptor, handler: TransformHandler): void;
413
}
414
415
interface TransformDescriptor {
416
/** File pattern to match */
417
test?: RegExp | ((id: string) => boolean);
418
/** Target environments */
419
targets?: RsbuildTarget[];
420
/** Transform order */
421
order?: 'pre' | 'post';
422
}
423
424
type TransformHandler = (
425
context: TransformContext
426
) => TransformResult | Promise<TransformResult>;
427
428
interface TransformContext {
429
/** Module code */
430
code: string;
431
/** Module resource path with query */
432
resource: string;
433
/** Module resource path without query */
434
resourcePath: string;
435
/** Environment context */
436
environment: EnvironmentContext;
437
/** Add file dependency */
438
addDependency: (file: string) => void;
439
/** Emit file to output */
440
emitFile: (name: string, content: string) => void;
441
}
442
443
interface TransformResult {
444
/** Transformed code */
445
code: string;
446
/** Source map */
447
map?: string | object;
448
}
449
```
450
451
**Usage Examples:**
452
453
```typescript
454
const transformPlugin = (): RsbuildPlugin => ({
455
name: "transform-plugin",
456
setup(api) {
457
// Transform TypeScript files
458
api.transform(
459
{ test: /\.ts$/ },
460
async ({ code, resourcePath }) => {
461
// Add custom header to all TypeScript files
462
const header = `// Generated by custom plugin\n`;
463
return {
464
code: header + code,
465
};
466
}
467
);
468
469
// Transform specific file pattern
470
api.transform(
471
{
472
test: (id) => id.includes("api/"),
473
targets: ["web"],
474
},
475
({ code, addDependency }) => {
476
// Add runtime dependency
477
addDependency("./runtime-helpers.js");
478
479
// Transform API files
480
const transformedCode = code.replace(
481
/process\.env\.API_URL/g,
482
'"https://api.example.com"'
483
);
484
485
return { code: transformedCode };
486
}
487
);
488
},
489
});
490
```
491
492
### Process Assets Hook
493
494
Process build assets before emission.
495
496
```typescript { .api }
497
/**
498
* Hook to process assets before emission
499
*/
500
interface ProcessAssetsHook {
501
(descriptor: ProcessAssetsDescriptor, handler: ProcessAssetsHandler): void;
502
}
503
504
interface ProcessAssetsDescriptor {
505
/** Processing stage */
506
stage?: 'optimize' | 'optimize-count' | 'optimize-compatibility' | 'optimize-size' | 'dev-tooling' | 'optimize-inline' | 'summarize' | 'report';
507
}
508
509
type ProcessAssetsHandler = (
510
assets: Record<string, Source>
511
) => void | Promise<void>;
512
513
interface Source {
514
source(): string | Buffer;
515
size(): number;
516
map(): object | null;
517
}
518
```
519
520
### Resolve Hook
521
522
Intercept and modify module resolution.
523
524
```typescript { .api }
525
/**
526
* Hook to intercept module resolution
527
*/
528
interface ResolveHook {
529
(handler: ResolveHandler): void;
530
}
531
532
type ResolveHandler = (
533
data: ResolveData
534
) => ResolveResult | void | Promise<ResolveResult | void>;
535
536
interface ResolveData {
537
/** Module request */
538
request: string;
539
/** Context directory */
540
context: string;
541
/** Import kind */
542
kind: 'entry' | 'import' | 'require' | 'dynamic-import';
543
}
544
545
interface ResolveResult {
546
/** Resolved path */
547
path?: string;
548
/** External module */
549
external?: boolean;
550
}
551
```
552
553
## Plugin Communication
554
555
### Expose API
556
557
Expose plugin APIs for use by other plugins.
558
559
```typescript { .api }
560
/**
561
* Expose plugin API for use by other plugins
562
* @param id - Unique identifier for the exposed API
563
* @param api - API object to expose
564
*/
565
expose<T = any>(id: string, api: T): void;
566
```
567
568
### Use Exposed API
569
570
Use APIs exposed by other plugins.
571
572
```typescript { .api }
573
/**
574
* Use API exposed by another plugin
575
* @param id - Identifier of the exposed API
576
* @returns The exposed API or undefined if not found
577
*/
578
useExposed<T = any>(id: string): T | undefined;
579
```
580
581
**Usage Examples:**
582
583
```typescript
584
// Plugin that exposes an API
585
const providerPlugin = (): RsbuildPlugin => ({
586
name: "provider-plugin",
587
setup(api) {
588
const sharedAPI = {
589
getConfig: () => ({ theme: "dark" }),
590
transform: (input: string) => input.toUpperCase(),
591
};
592
593
api.expose("provider-api", sharedAPI);
594
},
595
});
596
597
// Plugin that uses the exposed API
598
const consumerPlugin = (): RsbuildPlugin => ({
599
name: "consumer-plugin",
600
enforce: "post", // Run after provider
601
setup(api) {
602
const providerAPI = api.useExposed<{
603
getConfig: () => any;
604
transform: (input: string) => string;
605
}>("provider-api");
606
607
if (providerAPI) {
608
const config = providerAPI.getConfig();
609
console.log("Theme:", config.theme);
610
611
api.modifyRsbuildConfig((config) => {
612
// Use the shared API
613
const transformed = providerAPI.transform("hello");
614
config.source = config.source || {};
615
config.source.define = {
616
...config.source.define,
617
TRANSFORMED_VALUE: `"${transformed}"`,
618
};
619
});
620
}
621
},
622
});
623
```
624
625
## Chain Identifiers
626
627
Predefined identifiers for common configuration elements when using the chain API.
628
629
```typescript { .api }
630
interface ChainIdentifier {
631
PLUGIN: {
632
HTML: string;
633
APP_ICON: string;
634
INLINE_CHUNK: string;
635
BUNDLE_ANALYZER: string;
636
// ... more plugin identifiers
637
};
638
RULE: {
639
JS: string;
640
TS: string;
641
CSS: string;
642
SASS: string;
643
LESS: string;
644
// ... more rule identifiers
645
};
646
USE: {
647
SWC: string;
648
CSS: string;
649
POSTCSS: string;
650
// ... more use identifiers
651
};
652
}
653
```
654
655
## Built-in Plugin Names
656
657
```typescript { .api }
658
const PLUGIN_CSS_NAME = 'rsbuild:css';
659
const PLUGIN_SWC_NAME = 'rsbuild:swc';
660
```
661
662
## Plugin Registration
663
664
```typescript { .api }
665
type RsbuildPlugins = (RsbuildPlugin | RsbuildPluginOptions)[];
666
667
interface RsbuildPluginOptions {
668
plugin: RsbuildPlugin;
669
options?: any;
670
}
671
```
672
673
**Usage Examples:**
674
675
```typescript
676
import { defineConfig } from "@rsbuild/core";
677
import { myPlugin, anotherPlugin } from "./plugins";
678
679
export default defineConfig({
680
plugins: [
681
myPlugin(),
682
anotherPlugin({ option: "value" }),
683
684
// Conditional plugin
685
process.env.NODE_ENV === "development" && devPlugin(),
686
687
// Plugin with options
688
{
689
plugin: complexPlugin(),
690
options: {
691
advanced: true,
692
},
693
},
694
].filter(Boolean), // Remove falsy values
695
});
696
```