0
# Babel Integration
1
2
@vitejs/plugin-react provides flexible Babel integration allowing you to customize transformations with plugins, presets, and parser options. Babel is **lazy-loaded** and **only used when configured**, ensuring optimal performance when not needed.
3
4
## Performance Context
5
6
**When Babel is Used:**
7
8
| Babel Config | Dev Transformer | Prod Transformer | Speed | Use Case |
9
|--------------|----------------|------------------|-------|----------|
10
| None | esbuild/oxc | esbuild/oxc | Fastest | Standard React app |
11
| parserOpts only | esbuild/oxc | esbuild/oxc | Fast | Parsing experimental syntax |
12
| plugins/presets | Babel | Babel | Slower | Custom transformations needed |
13
14
**Key Principle:** Only use Babel when you need custom transformations that esbuild/oxc cannot handle. Parser-only features (parserOpts) don't trigger Babel transformation.
15
16
## API Reference
17
18
### Babel Configuration Options
19
20
Configure Babel transformations with plugins, presets, and other Babel options. The configuration can be static or dynamic based on the file being transformed.
21
22
```typescript { .api }
23
/**
24
* Babel transformation options for @vitejs/plugin-react
25
* Omits properties that are controlled internally by the plugin
26
*/
27
type BabelOptions = Omit<
28
TransformOptions,
29
'ast' | 'filename' | 'root' | 'sourceFileName' | 'sourceMaps' | 'inputSourceMap'
30
>;
31
32
interface Options {
33
/**
34
* Babel configuration applied in both dev and prod
35
* Can be a static options object or a function that returns options based on file context
36
*/
37
babel?: BabelOptions | ((id: string, options: { ssr?: boolean }) => BabelOptions);
38
}
39
40
// TransformOptions is from @babel/core and includes:
41
// - plugins, presets, overrides, parserOpts, babelrc, configFile, and more
42
// See: https://babeljs.io/docs/options
43
type TransformOptions = import('@babel/core').TransformOptions;
44
```
45
46
**Key BabelOptions properties:**
47
- `plugins?: PluginItem[]` - Babel transformation plugins to apply
48
- `presets?: PluginItem[]` - Babel presets to apply
49
- `overrides?: TransformOptions[]` - Conditional configuration based on file patterns
50
- `parserOpts?: ParserOptions` - Parser configuration (parsing only, no transformation)
51
- `babelrc?: boolean` - Enable .babelrc files (default: searches only if babel options set)
52
- `configFile?: boolean | string` - Enable babel.config.js files
53
- `generatorOpts?: GeneratorOptions` - Code generation options
54
- Full spec: https://babeljs.io/docs/options
55
56
### Normalized Babel Options
57
58
The internal representation of Babel options with guaranteed array types for plugins, presets, and overrides.
59
60
```typescript { .api }
61
/**
62
* Normalized Babel options object used internally and passed to plugin API hooks
63
*/
64
interface ReactBabelOptions extends BabelOptions {
65
/** Array of Babel plugins to apply */
66
plugins: Extract<BabelOptions['plugins'], any[]>;
67
/** Array of Babel presets to apply */
68
presets: Extract<BabelOptions['presets'], any[]>;
69
/** Array of Babel overrides for conditional configuration */
70
overrides: Extract<BabelOptions['overrides'], any[]>;
71
/** Parser options including plugins for syntax support */
72
parserOpts: ParserOptions & {
73
plugins: Extract<ParserOptions['plugins'], any[]>;
74
};
75
}
76
77
type ParserOptions = import('@babel/core').ParserOptions;
78
```
79
80
### Babel Plugin API Hook
81
82
Other Vite plugins can hook into the Babel configuration process to add or modify Babel options dynamically.
83
84
```typescript { .api }
85
/**
86
* API surface for Vite plugins to interact with @vitejs/plugin-react
87
*/
88
interface ViteReactPluginApi {
89
/**
90
* Hook to manipulate the Babel options
91
* Called before each file transformation
92
*/
93
reactBabel?: ReactBabelHook;
94
}
95
96
/**
97
* Callback function type for manipulating Babel configuration
98
*/
99
type ReactBabelHook = (
100
babelConfig: ReactBabelOptions,
101
context: ReactBabelHookContext,
102
config: ResolvedConfig
103
) => void;
104
105
/**
106
* Context object passed to ReactBabelHook callbacks
107
*/
108
type ReactBabelHookContext = {
109
/** Whether this is a server-side rendering transformation */
110
ssr: boolean;
111
/** File identifier being transformed */
112
id: string;
113
};
114
115
type ResolvedConfig = import('vite').ResolvedConfig;
116
```
117
118
**Example:**
119
```typescript
120
import type { Plugin, ResolvedConfig } from 'vite';
121
import type { ReactBabelOptions } from '@vitejs/plugin-react';
122
123
const babelModifierPlugin: Plugin = {
124
name: 'babel-modifier',
125
api: {
126
reactBabel(babelConfig, context, config) {
127
// Add plugins based on environment
128
if (!config.isProduction) {
129
babelConfig.plugins.push('babel-plugin-dev-only');
130
}
131
132
// Add SSR-specific transformations
133
if (context.ssr) {
134
babelConfig.plugins.push([
135
'babel-plugin-transform-imports',
136
{ /* ssr-specific options */ }
137
]);
138
}
139
140
// Add parser plugins for specific files
141
if (context.id.includes('experimental')) {
142
babelConfig.parserOpts.plugins.push('decorators-legacy');
143
}
144
145
// Modify presets
146
babelConfig.presets.push(['@babel/preset-env', {
147
targets: config.build.target
148
}]);
149
}
150
}
151
};
152
153
export default defineConfig({
154
plugins: [babelModifierPlugin, react()],
155
});
156
```
157
158
## Configuration Patterns
159
160
### Static Configuration
161
162
Use static configuration when Babel options are consistent across all files. This is more efficient as options are created once and reused.
163
164
```typescript
165
import react from '@vitejs/plugin-react';
166
import { defineConfig } from 'vite';
167
168
export default defineConfig({
169
plugins: [
170
react({
171
babel: {
172
plugins: ['babel-plugin-macros'],
173
presets: [
174
['@babel/preset-typescript', { isTSX: true, allExtensions: true }]
175
],
176
// Use .babelrc files
177
babelrc: true,
178
// Use babel.config.js files
179
configFile: true,
180
}
181
})
182
]
183
});
184
```
185
186
**Performance:** Config created once and reused across all files (most efficient).
187
188
### Dynamic Configuration
189
190
Use dynamic configuration when you need file-specific or context-specific Babel options.
191
192
```typescript
193
react({
194
babel: (id, { ssr }) => ({
195
plugins: [
196
// Add different plugins based on context
197
...(ssr ? ['babel-plugin-ssr'] : ['babel-plugin-client']),
198
// Conditional plugin for specific files
199
...(id.includes('legacy') ? ['@babel/plugin-transform-arrow-functions'] : []),
200
],
201
})
202
})
203
```
204
205
**Performance:** New config generated for each file transformation (slight overhead but more flexible).
206
207
### Parser Plugins for Proposed Syntax
208
209
Enable parsing (not transformation) of experimental ECMAScript features. This does NOT trigger Babel transformation - esbuild still handles the transformation.
210
211
```typescript
212
react({
213
babel: {
214
parserOpts: {
215
plugins: [
216
'decorators-legacy',
217
'classProperties',
218
'exportDefaultFrom',
219
]
220
}
221
}
222
})
223
```
224
225
**Note:** Parser plugins only enable *parsing* of syntax. Code transformation is handled by esbuild. To transform the code, use Babel transformation plugins.
226
227
**Common parser plugins:**
228
- `decorators-legacy` - Legacy decorator syntax
229
- `decorators` - TC39 decorators proposal
230
- `classProperties` - Class properties
231
- `classPrivateProperties` - Private class properties
232
- `classPrivateMethods` - Private class methods
233
- `exportDefaultFrom` - `export v from 'mod'`
234
- `exportNamespaceFrom` - `export * as ns from 'mod'`
235
- `functionBind` - Function bind operator `::`
236
- `pipelineOperator` - Pipeline operator `|>`
237
238
Full list: https://babeljs.io/docs/en/babel-parser#ecmascript-proposals
239
240
## Common Use Cases
241
242
### Adding Custom Babel Plugins
243
244
```typescript
245
react({
246
babel: {
247
plugins: [
248
'babel-plugin-macros',
249
['babel-plugin-styled-components', { displayName: true }],
250
'@babel/plugin-proposal-decorators',
251
]
252
}
253
})
254
```
255
256
### Using Babel Configuration Files
257
258
Enable reading from `.babelrc` or `babel.config.js`:
259
260
```typescript
261
react({
262
babel: {
263
babelrc: true,
264
configFile: true,
265
}
266
})
267
```
268
269
**Note:** By default, Babel config files are only searched when babel options are explicitly set.
270
271
### React Compiler Integration
272
273
Configure the React Compiler (experimental):
274
275
```typescript
276
// Auto-optimize all components
277
react({
278
babel: {
279
plugins: [
280
[
281
'babel-plugin-react-compiler',
282
{
283
// Target React version ('17', '18', or '19')
284
target: '19',
285
// Compilation mode: 'all' or 'annotation'
286
compilationMode: 'all',
287
}
288
]
289
]
290
}
291
})
292
293
// Annotation mode (only compile functions with "use memo" directive)
294
react({
295
babel: {
296
plugins: [
297
['babel-plugin-react-compiler', { compilationMode: 'annotation' }]
298
]
299
}
300
})
301
```
302
303
Then in your code:
304
305
```typescript
306
function ExpensiveComponent() {
307
"use memo"; // This component will be compiled
308
// ...
309
}
310
```
311
312
### Environment-Specific Configuration
313
314
Different Babel configs for SSR vs client:
315
316
```typescript
317
react({
318
babel: (id, { ssr }) => {
319
const plugins = ['babel-plugin-macros'];
320
321
// SSR-specific plugins
322
if (ssr) {
323
plugins.push('babel-plugin-dynamic-import-node');
324
}
325
326
// Client-specific plugins
327
if (!ssr) {
328
plugins.push('babel-plugin-react-lazy');
329
}
330
331
return { plugins };
332
}
333
})
334
```
335
336
### Conditional File-Based Configuration
337
338
```typescript
339
react({
340
babel: (id, options) => {
341
const config: BabelOptions = {
342
plugins: [],
343
presets: [],
344
};
345
346
// Legacy browser support for specific directories
347
if (id.includes('/legacy/')) {
348
config.presets!.push(['@babel/preset-env', {
349
targets: { ie: 11 }
350
}]);
351
}
352
353
// Modern syntax for modern bundles
354
if (id.includes('/modern/')) {
355
config.presets!.push(['@babel/preset-env', {
356
targets: { esmodules: true }
357
}]);
358
}
359
360
return config;
361
}
362
})
363
```
364
365
### Styled Components Example
366
367
```typescript
368
react({
369
babel: {
370
plugins: [
371
[
372
'babel-plugin-styled-components',
373
{
374
displayName: true,
375
fileName: true,
376
ssr: true,
377
}
378
]
379
]
380
}
381
})
382
```
383
384
### Emotion CSS Prop Support
385
386
```typescript
387
react({
388
jsxImportSource: '@emotion/react',
389
babel: {
390
plugins: ['@emotion/babel-plugin']
391
}
392
})
393
```
394
395
### Babel Macros Support
396
397
```typescript
398
react({
399
babel: {
400
plugins: ['babel-plugin-macros']
401
}
402
})
403
```
404
405
## Performance Optimization
406
407
### When Babel is Used
408
409
- **No plugins/presets configured**: Babel is skipped entirely; esbuild/oxc handles all transformations (fastest)
410
- **Only parserOpts configured**: Babel is used for parsing only; esbuild/oxc handles transformation (fast)
411
- **Plugins/presets configured**: Babel transformation is applied to matching files (slower)
412
- **Dynamic configuration**: New Babel options created for each file transformation (additional overhead)
413
- **Static configuration**: Options created once and reused (more efficient)
414
415
### Optimization Strategies
416
417
1. **Minimize Babel usage**: Only use Babel when necessary; let esbuild/oxc handle standard transformations
418
2. **Prefer parser plugins**: If you only need syntax parsing (not transformation), use parserOpts only
419
3. **Use static configuration**: Avoid function-based babel options when possible
420
4. **Limit file scope**: Use `include`/`exclude` options to reduce files processed by Babel
421
5. **Lazy loading**: Babel is only loaded when needed, so startup time is not affected if Babel is unused
422
423
### Static vs Dynamic Config Performance
424
425
**Static Configuration:**
426
```typescript
427
react({
428
babel: {
429
plugins: ['babel-plugin-macros'] // Created once, reused for all files
430
}
431
})
432
```
433
- Config created once during initialization
434
- Reused across all file transformations
435
- Best performance for consistent transformations
436
437
**Dynamic Configuration:**
438
```typescript
439
react({
440
babel: (id, { ssr }) => ({
441
plugins: ssr ? ['plugin-a'] : ['plugin-b'] // Created for each file
442
})
443
})
444
```
445
- New config generated for each file transformation
446
- More flexible for file-specific or context-specific needs
447
- Slight performance overhead per file
448
449
### Scope Limiting
450
451
Reduce the number of files processed by Babel:
452
453
```typescript
454
react({
455
// Only process files that need custom transformations
456
include: /src\/legacy\/.*\.tsx?$/,
457
babel: {
458
plugins: ['@babel/plugin-transform-arrow-functions']
459
}
460
})
461
```
462
463
## Babel Transformation Pipeline
464
465
Understanding the transformation pipeline helps optimize your configuration:
466
467
1. **File Matching**: File is checked against include/exclude patterns
468
2. **JSX Detection**: Plugin determines if Fast Refresh should be applied
469
3. **Plugin Assembly**: Babel plugins array is constructed:
470
- User-provided plugins (run first)
471
- React Refresh plugin (development only, if applicable)
472
- React JSX plugins (classic runtime development only)
473
4. **Parser Configuration**: Parser plugins for JSX, TypeScript, etc. are added
474
5. **Transformation**: Babel transforms the code with assembled configuration
475
6. **Refresh Wrapping**: Fast Refresh wrapper is added (development only, if applicable)
476
477
### Plugin Execution Order
478
479
Babel plugins run in a specific order:
480
481
1. **User plugins** (as configured in `babel.plugins`)
482
2. **User presets** (as configured in `babel.presets`, in reverse order)
483
3. **React Refresh plugin** (if in development and Fast Refresh applies)
484
4. **React JSX plugins** (if using classic runtime in development)
485
486
**Important:** Plugins run before presets, and plugins run in order while presets run in reverse order.
487
488
## Production Builds
489
490
In production builds:
491
- If no Babel plugins are configured, esbuild handles all transformations (fastest)
492
- If Babel plugins are configured, Babel transformation is applied
493
- Fast Refresh code is never included in production builds
494
- Only transformation plugins affect production; parser plugins do not
495
496
**Optimization tip:** Consider using different Babel configs for dev vs prod:
497
498
```typescript
499
react({
500
babel: (id, { ssr }) => {
501
const isDev = process.env.NODE_ENV === 'development';
502
503
return {
504
plugins: [
505
// Always included
506
'babel-plugin-macros',
507
// Dev-only plugins
508
...(isDev ? ['babel-plugin-dev-tools'] : []),
509
]
510
};
511
}
512
})
513
```
514
515
## Compatibility
516
517
### Babel Version
518
519
The plugin is compatible with Babel 7.x (specifically @babel/core ^7.28.4).
520
521
### TypeScript
522
523
TypeScript syntax is automatically handled by esbuild/oxc without configuration. You don't need `@babel/preset-typescript` unless you have specific TypeScript transformation requirements.
524
525
**When you might need @babel/preset-typescript:**
526
- Custom TypeScript transformation options
527
- Specific legacy TypeScript features not supported by esbuild
528
- Integration with other Babel plugins that expect TypeScript preset
529
530
**Example:**
531
```typescript
532
react({
533
babel: {
534
presets: [
535
['@babel/preset-typescript', {
536
isTSX: true,
537
allExtensions: true,
538
allowDeclareFields: true,
539
}]
540
]
541
}
542
})
543
```
544
545
### React Version
546
547
- **React >= 16.9**: Full support including Fast Refresh
548
- **React < 16.9**: Basic transformations work, but Fast Refresh is unavailable
549
550
### Node.js Version
551
552
Requires Node.js ^20.19.0 || >=22.12.0
553
554
## Troubleshooting
555
556
### Babel Not Being Applied
557
558
**Check:**
559
1. Babel options are actually configured (even `parserOpts` counts)
560
2. File matches `include` pattern (default: /\.[tj]sx?$/)
561
3. File doesn't match `exclude` pattern (default: /\/node_modules\//)
562
4. Babel dependencies are installed (`@babel/core` and any plugins/presets)
563
564
### Performance Issues with Babel
565
566
**If Babel is causing slow builds:**
567
568
1. **Verify Babel is necessary**: Check if you can use parserOpts only
569
2. **Use static config**: Replace function-based babel options with static object
570
3. **Limit file scope**: Use include/exclude to reduce processed files
571
4. **Remove unused plugins**: Each plugin adds transformation overhead
572
5. **Consider alternatives**: Check if esbuild can handle your transformations
573
574
### Plugin Conflicts
575
576
If you experience conflicts between Babel plugins:
577
578
1. **Check plugin order**: Plugins run in the order specified
579
2. **Review presets**: Presets run in reverse order and may contain conflicting plugins
580
3. **Use plugin options**: Some plugins have options to disable specific features
581
4. **Test in isolation**: Remove plugins one by one to identify conflicts
582
583
### Parser Plugin Not Working
584
585
**Common issues:**
586
587
1. **Using wrong plugin name**: Parser plugins have specific names (e.g., 'decorators-legacy' not 'decorator')
588
2. **Transformation expected**: Parser plugins only parse syntax; use transformation plugins for actual code transformation
589
3. **esbuild doesn't support**: Some experimental syntax isn't supported by esbuild even with parser plugins
590
591
**Solution:** Use a transformation plugin instead:
592
```typescript
593
react({
594
babel: {
595
plugins: ['@babel/plugin-proposal-decorators'] // Transformation plugin
596
}
597
})
598
```
599
600
## Reference
601
602
**Babel Documentation:** https://babeljs.io/docs/
603
**Parser Plugins:** https://babeljs.io/docs/en/babel-parser#ecmascript-proposals
604
**Transform Options:** https://babeljs.io/docs/options
605
**Plugin Development:** https://babeljs.io/docs/plugins
606