CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/npm-vitejs--plugin-react

The default Vite plugin for React projects enabling Fast Refresh and flexible Babel integration

Overview
Eval results
Files

babel-integration.mddocs/

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.

Performance Context

When Babel is Used:

Babel ConfigDev TransformerProd TransformerSpeedUse Case
Noneesbuild/oxcesbuild/oxcFastestStandard React app
parserOpts onlyesbuild/oxcesbuild/oxcFastParsing experimental syntax
plugins/presetsBabelBabelSlowerCustom 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.

API Reference

Babel Configuration Options

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 apply
  • presets?: PluginItem[] - Babel presets to apply
  • overrides?: TransformOptions[] - Conditional configuration based on file patterns
  • parserOpts?: 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 files
  • generatorOpts?: GeneratorOptions - Code generation options
  • Full spec: https://babeljs.io/docs/options

Normalized Babel Options

The 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;

Babel Plugin API Hook

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()],
});

Configuration Patterns

Static Configuration

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).

Dynamic Configuration

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).

Parser Plugins for Proposed Syntax

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 syntax
  • decorators - TC39 decorators proposal
  • classProperties - Class properties
  • classPrivateProperties - Private class properties
  • classPrivateMethods - Private class methods
  • exportDefaultFrom - 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

Common Use Cases

Adding Custom Babel Plugins

react({
  babel: {
    plugins: [
      'babel-plugin-macros',
      ['babel-plugin-styled-components', { displayName: true }],
      '@babel/plugin-proposal-decorators',
    ]
  }
})

Using Babel Configuration Files

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.

React Compiler Integration

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
  // ...
}

Environment-Specific Configuration

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 };
  }
})

Conditional File-Based Configuration

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;
  }
})

Styled Components Example

react({
  babel: {
    plugins: [
      [
        'babel-plugin-styled-components',
        {
          displayName: true,
          fileName: true,
          ssr: true,
        }
      ]
    ]
  }
})

Emotion CSS Prop Support

react({
  jsxImportSource: '@emotion/react',
  babel: {
    plugins: ['@emotion/babel-plugin']
  }
})

Babel Macros Support

react({
  babel: {
    plugins: ['babel-plugin-macros']
  }
})

Performance Optimization

When Babel is Used

  • No plugins/presets configured: Babel is skipped entirely; esbuild/oxc handles all transformations (fastest)
  • Only parserOpts configured: Babel is used for parsing only; esbuild/oxc handles transformation (fast)
  • Plugins/presets configured: Babel transformation is applied to matching files (slower)
  • Dynamic configuration: New Babel options created for each file transformation (additional overhead)
  • Static configuration: Options created once and reused (more efficient)

Optimization Strategies

  1. Minimize Babel usage: Only use Babel when necessary; let esbuild/oxc handle standard transformations
  2. Prefer parser plugins: If you only need syntax parsing (not transformation), use parserOpts only
  3. Use static configuration: Avoid function-based babel options when possible
  4. Limit file scope: Use include/exclude options to reduce files processed by Babel
  5. Lazy loading: Babel is only loaded when needed, so startup time is not affected if Babel is unused

Static vs Dynamic Config Performance

Static Configuration:

react({
  babel: {
    plugins: ['babel-plugin-macros']  // Created once, reused for all files
  }
})
  • Config created once during initialization
  • Reused across all file transformations
  • Best performance for consistent transformations

Dynamic Configuration:

react({
  babel: (id, { ssr }) => ({
    plugins: ssr ? ['plugin-a'] : ['plugin-b']  // Created for each file
  })
})
  • New config generated for each file transformation
  • More flexible for file-specific or context-specific needs
  • Slight performance overhead per file

Scope Limiting

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']
  }
})

Babel Transformation Pipeline

Understanding the transformation pipeline helps optimize your configuration:

  1. File Matching: File is checked against include/exclude patterns
  2. JSX Detection: Plugin determines if Fast Refresh should be applied
  3. Plugin Assembly: Babel plugins array is constructed:
    • User-provided plugins (run first)
    • React Refresh plugin (development only, if applicable)
    • React JSX plugins (classic runtime development only)
  4. Parser Configuration: Parser plugins for JSX, TypeScript, etc. are added
  5. Transformation: Babel transforms the code with assembled configuration
  6. Refresh Wrapping: Fast Refresh wrapper is added (development only, if applicable)

Plugin Execution Order

Babel plugins run in a specific order:

  1. User plugins (as configured in babel.plugins)
  2. User presets (as configured in babel.presets, in reverse order)
  3. React Refresh plugin (if in development and Fast Refresh applies)
  4. React JSX plugins (if using classic runtime in development)

Important: Plugins run before presets, and plugins run in order while presets run in reverse order.

Production Builds

In production builds:

  • If no Babel plugins are configured, esbuild handles all transformations (fastest)
  • If Babel plugins are configured, Babel transformation is applied
  • Fast Refresh code is never included in production builds
  • Only transformation plugins affect production; parser plugins do not

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'] : []),
      ]
    };
  }
})

Compatibility

Babel Version

The plugin is compatible with Babel 7.x (specifically @babel/core ^7.28.4).

TypeScript

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:

  • Custom TypeScript transformation options
  • Specific legacy TypeScript features not supported by esbuild
  • Integration with other Babel plugins that expect TypeScript preset

Example:

react({
  babel: {
    presets: [
      ['@babel/preset-typescript', {
        isTSX: true,
        allExtensions: true,
        allowDeclareFields: true,
      }]
    ]
  }
})

React Version

  • React >= 16.9: Full support including Fast Refresh
  • React < 16.9: Basic transformations work, but Fast Refresh is unavailable

Node.js Version

Requires Node.js ^20.19.0 || >=22.12.0

Troubleshooting

Babel Not Being Applied

Check:

  1. Babel options are actually configured (even parserOpts counts)
  2. File matches include pattern (default: /.[tj]sx?$/)
  3. File doesn't match exclude pattern (default: //node_modules//)
  4. Babel dependencies are installed (@babel/core and any plugins/presets)

Performance Issues with Babel

If Babel is causing slow builds:

  1. Verify Babel is necessary: Check if you can use parserOpts only
  2. Use static config: Replace function-based babel options with static object
  3. Limit file scope: Use include/exclude to reduce processed files
  4. Remove unused plugins: Each plugin adds transformation overhead
  5. Consider alternatives: Check if esbuild can handle your transformations

Plugin Conflicts

If you experience conflicts between Babel plugins:

  1. Check plugin order: Plugins run in the order specified
  2. Review presets: Presets run in reverse order and may contain conflicting plugins
  3. Use plugin options: Some plugins have options to disable specific features
  4. Test in isolation: Remove plugins one by one to identify conflicts

Parser Plugin Not Working

Common issues:

  1. Using wrong plugin name: Parser plugins have specific names (e.g., 'decorators-legacy' not 'decorator')
  2. Transformation expected: Parser plugins only parse syntax; use transformation plugins for actual code transformation
  3. esbuild doesn't support: Some experimental syntax isn't supported by esbuild even with parser plugins

Solution: Use a transformation plugin instead:

react({
  babel: {
    plugins: ['@babel/plugin-proposal-decorators']  // Transformation plugin
  }
})

Reference

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

docs

babel-integration.md

index.md

tile.json