vite-plugin-babel
Run Babel transforms during Vite dev serve, not just build
Repository Health
Technical Analysis
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 intovite.config’spluginsarray - Babel transforms applied during
optimizeDeps,serve, andbuild— not just build, unlike most Babel/Vite setups - Automatic pickup of existing
.babelrc/babel.config.*files, or an inlinebabelConfigoverride include/excludefile filtering (defaults to.js/.jsx) plusapplyandenforceto 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 devandvite 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).
Used by 2 apps in this directory
Bramble
Password Manager · Security · Authentication
Local-first, end-to-end encrypted password manager that syncs your vault directly between your own devices over a private peer-to-peer mesh — no server, no account, no cloud in the middle.
Openship
Devops · Hosting Control Panel
Openship is an open-source, self-hostable deployment platform that points at a repo and builds, ships, routes, and TLS-terminates the app — driven from a desktop app, web dashboard, or CLI.