The default Vite plugin for React projects enabling Fast Refresh and flexible Babel integration
@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.
When Babel is Used:
| Babel Config | Dev Transformer | Prod Transformer | Speed | Use Case |
|---|---|---|---|---|
| None | esbuild/oxc | esbuild/oxc | Fastest | Standard React app |
| parserOpts only | esbuild/oxc | esbuild/oxc | Fast | Parsing experimental syntax |
| plugins/presets | Babel | Babel | Slower | Custom transformations needed |
Key Principle: Only use Babel when you need custom transformations that esbuild/oxc cannot handle. Parser-only features (parserOpts) don't trigger Babel transformation.
Configure Babel transformations with plugins, presets, and other Babel options. The configuration can be static or dynamic based on the file being transformed.
/**
* Babel transformation options for @vitejs/plugin-react
* Omits properties that are controlled internally by the plugin
*/
type BabelOptions = Omit<
TransformOptions,
'ast' | 'filename' | 'root' | 'sourceFileName' | 'sourceMaps' | 'inputSourceMap'
>;
interface Options {
/**
* Babel configuration applied in both dev and prod
* Can be a static options object or a function that returns options based on file context
*/
babel?: BabelOptions | ((id: string, options: { ssr?: boolean }) => BabelOptions);
}
// TransformOptions is from @babel/core and includes:
// - plugins, presets, overrides, parserOpts, babelrc, configFile, and more
// See: https://babeljs.io/docs/options
type TransformOptions = import('@babel/core').TransformOptions;Key BabelOptions properties:
plugins?: PluginItem[] - Babel transformation plugins to applypresets?: PluginItem[] - Babel presets to applyoverrides?: TransformOptions[] - Conditional configuration based on file patternsparserOpts?: ParserOptions - Parser configuration (parsing only, no transformation)babelrc?: boolean - Enable .babelrc files (default: searches only if babel options set)configFile?: boolean | string - Enable babel.config.js filesgeneratorOpts?: GeneratorOptions - Code generation optionsThe internal representation of Babel options with guaranteed array types for plugins, presets, and overrides.
/**
* Normalized Babel options object used internally and passed to plugin API hooks
*/
interface ReactBabelOptions extends BabelOptions {
/** Array of Babel plugins to apply */
plugins: Extract<BabelOptions['plugins'], any[]>;
/** Array of Babel presets to apply */
presets: Extract<BabelOptions['presets'], any[]>;
/** Array of Babel overrides for conditional configuration */
overrides: Extract<BabelOptions['overrides'], any[]>;
/** Parser options including plugins for syntax support */
parserOpts: ParserOptions & {
plugins: Extract<ParserOptions['plugins'], any[]>;
};
}
type ParserOptions = import('@babel/core').ParserOptions;Other Vite plugins can hook into the Babel configuration process to add or modify Babel options dynamically.
/**
* API surface for Vite plugins to interact with @vitejs/plugin-react
*/
interface ViteReactPluginApi {
/**
* Hook to manipulate the Babel options
* Called before each file transformation
*/
reactBabel?: ReactBabelHook;
}
/**
* Callback function type for manipulating Babel configuration
*/
type ReactBabelHook = (
babelConfig: ReactBabelOptions,
context: ReactBabelHookContext,
config: ResolvedConfig
) => void;
/**
* Context object passed to ReactBabelHook callbacks
*/
type ReactBabelHookContext = {
/** Whether this is a server-side rendering transformation */
ssr: boolean;
/** File identifier being transformed */
id: string;
};
type ResolvedConfig = import('vite').ResolvedConfig;Example:
import type { Plugin, ResolvedConfig } from 'vite';
import type { ReactBabelOptions } from '@vitejs/plugin-react';
const babelModifierPlugin: Plugin = {
name: 'babel-modifier',
api: {
reactBabel(babelConfig, context, config) {
// Add plugins based on environment
if (!config.isProduction) {
babelConfig.plugins.push('babel-plugin-dev-only');
}
// Add SSR-specific transformations
if (context.ssr) {
babelConfig.plugins.push([
'babel-plugin-transform-imports',
{ /* ssr-specific options */ }
]);
}
// Add parser plugins for specific files
if (context.id.includes('experimental')) {
babelConfig.parserOpts.plugins.push('decorators-legacy');
}
// Modify presets
babelConfig.presets.push(['@babel/preset-env', {
targets: config.build.target
}]);
}
}
};
export default defineConfig({
plugins: [babelModifierPlugin, react()],
});Use static configuration when Babel options are consistent across all files. This is more efficient as options are created once and reused.
import react from '@vitejs/plugin-react';
import { defineConfig } from 'vite';
export default defineConfig({
plugins: [
react({
babel: {
plugins: ['babel-plugin-macros'],
presets: [
['@babel/preset-typescript', { isTSX: true, allExtensions: true }]
],
// Use .babelrc files
babelrc: true,
// Use babel.config.js files
configFile: true,
}
})
]
});Performance: Config created once and reused across all files (most efficient).
Use dynamic configuration when you need file-specific or context-specific Babel options.
react({
babel: (id, { ssr }) => ({
plugins: [
// Add different plugins based on context
...(ssr ? ['babel-plugin-ssr'] : ['babel-plugin-client']),
// Conditional plugin for specific files
...(id.includes('legacy') ? ['@babel/plugin-transform-arrow-functions'] : []),
],
})
})Performance: New config generated for each file transformation (slight overhead but more flexible).
Enable parsing (not transformation) of experimental ECMAScript features. This does NOT trigger Babel transformation - esbuild still handles the transformation.
react({
babel: {
parserOpts: {
plugins: [
'decorators-legacy',
'classProperties',
'exportDefaultFrom',
]
}
}
})Note: Parser plugins only enable parsing of syntax. Code transformation is handled by esbuild. To transform the code, use Babel transformation plugins.
Common parser plugins:
decorators-legacy - Legacy decorator syntaxdecorators - TC39 decorators proposalclassProperties - Class propertiesclassPrivateProperties - Private class propertiesclassPrivateMethods - Private class methodsexportDefaultFrom - export v from 'mod'exportNamespaceFrom - export * as ns from 'mod'functionBind - Function bind operator ::pipelineOperator - Pipeline operator |>Full list: https://babeljs.io/docs/en/babel-parser#ecmascript-proposals
react({
babel: {
plugins: [
'babel-plugin-macros',
['babel-plugin-styled-components', { displayName: true }],
'@babel/plugin-proposal-decorators',
]
}
})Enable reading from .babelrc or babel.config.js:
react({
babel: {
babelrc: true,
configFile: true,
}
})Note: By default, Babel config files are only searched when babel options are explicitly set.
Configure the React Compiler (experimental):
// Auto-optimize all components
react({
babel: {
plugins: [
[
'babel-plugin-react-compiler',
{
// Target React version ('17', '18', or '19')
target: '19',
// Compilation mode: 'all' or 'annotation'
compilationMode: 'all',
}
]
]
}
})
// Annotation mode (only compile functions with "use memo" directive)
react({
babel: {
plugins: [
['babel-plugin-react-compiler', { compilationMode: 'annotation' }]
]
}
})Then in your code:
function ExpensiveComponent() {
"use memo"; // This component will be compiled
// ...
}Different Babel configs for SSR vs client:
react({
babel: (id, { ssr }) => {
const plugins = ['babel-plugin-macros'];
// SSR-specific plugins
if (ssr) {
plugins.push('babel-plugin-dynamic-import-node');
}
// Client-specific plugins
if (!ssr) {
plugins.push('babel-plugin-react-lazy');
}
return { plugins };
}
})react({
babel: (id, options) => {
const config: BabelOptions = {
plugins: [],
presets: [],
};
// Legacy browser support for specific directories
if (id.includes('/legacy/')) {
config.presets!.push(['@babel/preset-env', {
targets: { ie: 11 }
}]);
}
// Modern syntax for modern bundles
if (id.includes('/modern/')) {
config.presets!.push(['@babel/preset-env', {
targets: { esmodules: true }
}]);
}
return config;
}
})react({
babel: {
plugins: [
[
'babel-plugin-styled-components',
{
displayName: true,
fileName: true,
ssr: true,
}
]
]
}
})react({
jsxImportSource: '@emotion/react',
babel: {
plugins: ['@emotion/babel-plugin']
}
})react({
babel: {
plugins: ['babel-plugin-macros']
}
})include/exclude options to reduce files processed by BabelStatic Configuration:
react({
babel: {
plugins: ['babel-plugin-macros'] // Created once, reused for all files
}
})Dynamic Configuration:
react({
babel: (id, { ssr }) => ({
plugins: ssr ? ['plugin-a'] : ['plugin-b'] // Created for each file
})
})Reduce the number of files processed by Babel:
react({
// Only process files that need custom transformations
include: /src\/legacy\/.*\.tsx?$/,
babel: {
plugins: ['@babel/plugin-transform-arrow-functions']
}
})Understanding the transformation pipeline helps optimize your configuration:
Babel plugins run in a specific order:
babel.plugins)babel.presets, in reverse order)Important: Plugins run before presets, and plugins run in order while presets run in reverse order.
In production builds:
Optimization tip: Consider using different Babel configs for dev vs prod:
react({
babel: (id, { ssr }) => {
const isDev = process.env.NODE_ENV === 'development';
return {
plugins: [
// Always included
'babel-plugin-macros',
// Dev-only plugins
...(isDev ? ['babel-plugin-dev-tools'] : []),
]
};
}
})The plugin is compatible with Babel 7.x (specifically @babel/core ^7.28.4).
TypeScript syntax is automatically handled by esbuild/oxc without configuration. You don't need @babel/preset-typescript unless you have specific TypeScript transformation requirements.
When you might need @babel/preset-typescript:
Example:
react({
babel: {
presets: [
['@babel/preset-typescript', {
isTSX: true,
allExtensions: true,
allowDeclareFields: true,
}]
]
}
})Requires Node.js ^20.19.0 || >=22.12.0
Check:
parserOpts counts)include pattern (default: /.[tj]sx?$/)exclude pattern (default: //node_modules//)@babel/core and any plugins/presets)If Babel is causing slow builds:
If you experience conflicts between Babel plugins:
Common issues:
Solution: Use a transformation plugin instead:
react({
babel: {
plugins: ['@babel/plugin-proposal-decorators'] // Transformation plugin
}
})Babel Documentation: https://babeljs.io/docs/ Parser Plugins: https://babeljs.io/docs/en/babel-parser#ecmascript-proposals Transform Options: https://babeljs.io/docs/options Plugin Development: https://babeljs.io/docs/plugins
Install with Tessl CLI
npx tessl i tessl/npm-vitejs--plugin-react