vite-plugin-babel

Run Babel transforms during Vite dev serve, not just build

Library
npm
v1.7.3
68stars
MIT License

Repository Health

Pre-computed score based on development activity, maintenance, community, maturity, and trend momentum.How we score it →
34/100Needs Attention
Development Activity16
Maintenance0
Community44
Maturity56
Momentum20

Technical Analysis

AI-assessed by reading the actual repository — architecture, code quality, innovation, and documentation.How we score it →
65/100Good
Architecture68
Code Quality52
Innovation62
Learning Curve78

vite-plugin-babel plugs Babel into every stage of the Vite pipeline — optimizeDeps, dev serve, and build — instead of only build like most Vite/Babel integrations. Vite’s default esbuild transform is fast but doesn’t support some experimental syntax (decorators, class instance fields in plain JS) that Babel handles natively, so this plugin fills that gap without giving up esbuild’s speed elsewhere.

It exposes a single plugin factory that reads your existing Babel config file (or an inline babelConfig object) and applies it through esbuild’s plugin hooks during dev, and directly during build. Scope is controlled with include/exclude filters (or the deprecated filter option), and apply/enforce mirror Vite’s own plugin-ordering API.

What You Get

  • A single default-exported plugin factory (babel()) you drop straight into vite.config’s plugins array
  • Babel transforms applied during optimizeDeps, serve, and build — not just build, unlike most Babel/Vite setups
  • Automatic pickup of existing .babelrc / babel.config.* files, or an inline babelConfig override
  • include / exclude file filtering (defaults to .js/.jsx) plus apply and enforce to mirror Vite’s native plugin-ordering API
  • Vite 8+ support via a rolldown-native transform path, alongside the esbuild-plugin path used on Vite 2–7

Common Use Cases

  • Enabling stage-2/experimental proposals (e.g. the legacy decorators proposal) in plain JS files that esbuild’s parser rejects
  • Sharing one Babel config across a codebase’s build and dev-server transforms so behavior never diverges between vite dev and vite build
  • Migrating a Create React App or Webpack+Babel project to Vite without dropping Babel-only plugins the team still depends on
  • Running framework-specific Babel plugins (e.g. class-properties transforms) that only affect a narrow, filtered subset of source files

Under The Hood

Architecture — The plugin’s entire public surface is one factory function in index.ts that returns a Vite Plugin object with config, configResolved, and transform hooks. The transform hook is shared verbatim between two code paths: on Vite 2–7 it’s wrapped into an esbuild plugin (esbuildBabel.ts, adapted from esbuild-plugin-babel to fix an ESM package.json interop bug) and injected into optimizeDeps.esbuildOptions.plugins; on Vite 8+ (detected via version.split('.')[0] at load time) it’s injected instead as a rolldown transform hook under optimizeDeps.rolldownOptions.plugins, since Vite 8 replaces esbuild-based dependency optimization with rolldown. useBabelConfig() closes over a lazily-initialized, memoized PartialConfig from babel.loadPartialConfig, populated with the resolved project root once configResolved fires — so Babel’s own config-file resolution (.babelrc, babel.config.*) works relative to the real project root rather than the plugin’s cwd. Tech Stack — Written in TypeScript against the @babel/core and vite peer dependencies (peer range spans Vite 2 through 8), with esbuild’s Loader/Plugin types imported only for typing the esbuild code path. Build output is produced by Rollup (rollup.config.ts) using rollup-plugin-esbuild for the JS/CJS/ESM bundles and rollup-plugin-dts for hand-rolled .d.cts/.d.mts declaration files, published as dual CJS/ESM via package.json#exports. There is no runtime framework — the whole implementation is ~250 lines across three source files. Code Quality — There are no test files anywhere in the repository (no *.test.*, *.spec.*, or tests/ directory), so correctness across the Vite 2–8 compatibility matrix and the esbuild/rolldown branch relies entirely on manual verification and community bug reports. Naming and typing are otherwise consistent: options are fully typed via BabelPluginOptions, the deprecated filter option carries an explicit @deprecated JSDoc tag plus a runtime console.warn migration hint, and the small filter.ts helper cleanly separates the legacy filter semantics from the newer include/exclude filtering. API Design — The public API is a single default export (babel(options?)) matching the conventional Vite plugin-factory shape, so integration is a one-line addition to a plugins array with no required options. Option naming (include, exclude, apply, enforce) deliberately mirrors Vite’s own plugin API rather than inventing new vocabulary, which lowers the learning curve for anyone who has written a Vite plugin before; the README documents every option in a table with types and defaults, and includes a worked troubleshooting example for the one sharp edge (loader selection for JSX-in-.jsx-file transforms).

Join founders buildingwith open source

Opinionated takes, migration guides, cost-saving tips, and insights from the open source ecosystem.

Subscribe on Substack
Join 750+ subscribers

Search