Upgrading a major version of your build tool is rarely just about editing package.json and running npm update. For simple applications, it might work on the first try. But once your codebase grows to include custom configuration, legacy plugins, decorator syntax, or specific CommonJS dependencies, a major upgrade can become a frustrating puzzle of build errors and runtime issues.
Vite 8 is a major shift in how the bundler operates underneath. While our previous look at Vite 8's architecture focused on the move to a unified Rust-powered toolchain, this guide is a hands-on playbook for the developers responsible for doing the actual upgrade.
Here is how to take an existing Vite project, migrate it to Vite 8, resolve configuration changes, and ensure your build remains stable.
Pre-Migration Audit
Before touching your dependency file, prepare your local environment. This prevents you from chasing ghost bugs caused by cached files or mismatched runtimes.
- Verify Node.js Version: Vite 8 requires Node.js 20.19+ or 22.12+. Check your runtime with
node -v. If your local machine or CI/CD pipelines run on older LTS versions, you must upgrade your environment first. - Commit or Stash Current Work: Never run a major upgrade on a dirty Git tree. Make sure your workspace is clean so you can easily diff the changes or discard them if a blocker appears.
- Save a Build Benchmark: Run your production build on the old Vite version and record the build time, bundle size, and chunk count. You will need these numbers later to verify that the migration actually improved your performance.
The Core Engine Shift
Vite 8 moves away from its dual-engine model. In Vite 7 and earlier, the system relied on two separate bundlers: esbuild for pre-bundling dependencies in development, and Rollup for bundling your application in production.
Vite 8 replaces both with Rolldown, a Rust-based port of Rollup. It also swaps out esbuild's parser and transformer with Oxc.
This change simplifies configuration because the development and production pathways now use the same underlying bundler. However, because Rolldown and Oxc interpret configuration options and syntax rules differently than esbuild and Rollup, you will need to adjust your setup.
Configuration Mappings
Vite 8 deprecates several configuration properties in vite.config.ts. The system includes a compatibility layer that automatically maps older properties to their new names, allowing existing configurations to continue working without immediate rewrites, but you should update them manually to avoid deprecation warnings.
Rollup Options Renamed to Rolldown
Because Rollup is no longer under the hood, all configuration options pointing to it have been renamed to target Rolldown. However, the compatibility layer allows existing Rollup configuration keys to continue working seamlessly in Vite 8. You do not need to rewrite your entire config just to complete the basic upgrade.
- Rename
build.rollupOptionstobuild.rolldownOptions - Rename
worker.rollupOptionstoworker.rolldownOptions
Legacy configurations like build.commonjsOptions and build.dynamicImportVarsOptions.warnOnError are now completely obsolete and act as no-ops. You can safely delete them.
Deprecating esbuildOptions
Since esbuild is no longer the default dependency optimizer, the optimizeDeps.esbuildOptions block is deprecated. Use optimizeDeps.rolldownOptions instead.
The compatibility layer translates these values, but you can use the following mapping reference to update your config file:
| Old Config Option (esbuildOptions) | New Config Option (rolldownOptions) |
|---|---|
minify | output.minify |
treeShaking | treeshake |
define | transform.define |
loader | moduleTypes |
preserveSymlinks | !resolve.symlinks (negated boolean) |
resolveExtensions | resolve.extensions |
mainFields | resolve.mainFields |
conditions | resolve.conditionNames |
keepNames | output.keepNames |
platform | platform |
If you need to inspect the exact configuration generated by the compatibility layer, you can use Vite's configResolved hook inside a custom inline plugin to print the result:
// vite.config.ts
import { defineConfig } from 'vite'
export default defineConfig({
plugins: [
{
name: 'log-resolved-config',
configResolved(config) {
console.log('Rolldown Options:', config.optimizeDeps.rolldownOptions)
}
}
]
})Migrating the esbuild Block to Oxc
JavaScript transformations are now managed by Oxc. The esbuild configuration property is deprecated in favor of oxc. Update your configuration parameters according to this table:
Old Option (esbuild.*) | New Option (oxc.*) | Notes / Value |
|---|---|---|
esbuild.jsxInject | oxc.jsxInject | Inject helper code |
esbuild.include | oxc.include | Files to transform |
esbuild.exclude | oxc.exclude | Files to skip |
esbuild.jsx | oxc.jsx | Options like 'preserve' |
esbuild.jsxImportSource | oxc.jsx.importSource | custom JSX package |
esbuild.jsxFactory | oxc.jsx.pragma | custom factory function |
esbuild.jsxFragment | oxc.jsx.pragmaFrag | custom fragment function |
esbuild.jsxDev | oxc.jsx.development | development flags |
esbuild.jsxSideEffects | oxc.jsx.pure | pure annotations |
esbuild.define | oxc.define | global constants |
If your config files used esbuild.jsx: 'automatic' or esbuild.jsx: 'transform', change them to object notation under oxc:
// For automatic JSX
oxc: {
jsx: { runtime: 'automatic' }
}
// For classic JSX transform
oxc: {
jsx: { runtime: 'classic' }
}The option esbuild.supported is not supported by Oxc. If your build relies on specific syntax targets, you will need to handle syntax support using alternative Oxc target configurations.
Handling the Native Decorators Gap
Oxc does not yet support lowering native ECMAScript decorators (the 2023-11 spec). If your codebase uses native decorators (common in some Angular, MobX, or server-side TypeScript projects), you cannot rely on Oxc for transformation out of the box.
You can work around this by using SWC or Babel to transform files containing decorators before they reach Oxc.
Workaround 1: Using Babel
Install the Babel plugin and decorator proposals:
pnpm add -D @rolldown/plugin-babel @babel/plugin-proposal-decoratorsConfigure vite.config.ts to run Babel only on files containing decorator syntax:
import { defineConfig } from 'vite'
import babel from '@rolldown/plugin-babel'
function decoratorPreset(options: Record<string, unknown>) {
return {
preset: () => ({
plugins: [['@babel/plugin-proposal-decorators', options]],
}),
rolldown: {
// Run the Babel transform only if the file uses a decorator
filter: {
code: '@',
},
},
}
}
export default defineConfig({
plugins: [
babel({
presets: [decoratorPreset({ version: '2023-11' })]
})
],
})Workaround 2: Using SWC
If you prefer SWC for faster execution, install SWC and the rollup helper:
pnpm add -D @rollup/plugin-swc @swc/coreIntegrate SWC into your Vite configuration with a code filter:
import { defineConfig, withFilter } from 'vite'
import swc from '@rollup/plugin-swc'
export default defineConfig({
plugins: [
withFilter(
swc({
swc: {
jsc: {
parser: {
syntax: 'typescript',
decorators: true,
decoratorsBeforeExport: true
},
transform: {
decoratorVersion: '2023-11'
},
},
},
}),
// Only transform files with decorator code
{ transform: { code: '@' } }
),
],
})Minification Changes
Vite 8 shifts its default minification engines for both JavaScript and CSS.
JavaScript Minification with Oxc
Vite now defaults to the Oxc Minifier instead of esbuild. Because Oxc is written in Rust, it yields faster build times, but its compression logic differs slightly from esbuild.
To configure minifier behaviors like dropping consoles, use build.rolldownOptions instead of the old esbuild.drop settings:
// vite.config.ts
export default defineConfig({
build: {
rolldownOptions: {
output: {
minify: {
compress: {
// Drop console logs in production
drop_console: true
}
}
}
}
}
})Oxc does not support property mangling options (mangleProps, reserveProps, mangleQuoted, or mangleCache). If your application depends on renaming object keys for safety or compression, you must keep esbuild.
To fall back to esbuild for minification, you must install esbuild as a devDependency (since Vite no longer embeds it) and set your config:
// vite.config.ts
export default defineConfig({
build: {
minify: 'esbuild'
}
})CSS Minification with Lightning CSS
Lightning CSS is now the default CSS minifier in Vite 8. It handles modern CSS syntax, nesting, and vendor prefixes faster than previous tools.
If Lightning CSS breaks your stylesheets or you need to match a legacy environment, you can switch back to esbuild:
// vite.config.ts
export default defineConfig({
build: {
cssMinify: 'esbuild'
}
})(Note: Switching back requires adding esbuild to your project's devDependencies).
CommonJS Interop and Resolver Adjustments
Vite 8 introduces stricter guidelines for module resolution. This is where older projects are most likely to experience build errors.
Strict CJS/ESM Interop
Vite 8 uses Rolldown's rules to resolve default imports from CommonJS modules. If a CJS module does not explicitly set __esModule: true, or if the importing file is considered an ES module (by ending in .mjs/.mts or having "type": "module" in package.json), Vite resolves the default import to module.exports instead of trying to look for a nested default property.
If a third-party package fails to import after upgrading, you may see errors like Cannot read properties of undefined or is not a function.
As a temporary fallback while library maintainers update their packages, you can enable legacy interop in your config:
// vite.config.ts
export default defineConfig({
legacy: {
inconsistentCjsInterop: true
}
})Format Sniffing Removal
In Vite 7, if a dependency package file declared both a browser and module field in its package.json, Vite would read the file content to guess whether to load the ESM file or the browser file.
Vite 8 removes this sniffing heuristic. It strictly honors the field ordering defined in your resolve.mainFields option. If a package resolution breaks because of this change, map the correct entry point manually using resolve.alias:
// vite.config.ts
export default defineConfig({
resolve: {
alias: {
'broken-library': 'broken-library/dist/esm/index.js'
}
}
})Step-by-Step Migration Checklist
Follow these steps to upgrade your repository:
1. Prepare and Update Dependencies
Before running the upgrade, make sure you commit your existing package lockfile so you can track precise dependency changes.
Update Vite along with Lightning CSS for modern CSS builds:
# Using pnpm
pnpm add -D vite@latest lightningcssIf your build relies on legacy plugins that call the transformWithEsbuild utility function, you must add esbuild as an explicit development dependency:
pnpm add -D esbuildYou do not need to delete your package lockfile or node_modules directory as a standard step. Only clear node_modules or local build caches (like .vite) if you encounter stale dependency states or caching problems during troubleshooting.
2. Update the Configuration File
Open vite.config.ts and apply the configuration updates:
- Replace any instances of
rollupOptionswithrolldownOptions. - Replace
esbuildOptionswithrolldownOptionsoptions using the mapping table. - Replace the
esbuildobject settings withoxc. - Remove obsolete parameters like
commonjsOptions.
3. Run the Dev Server
Start your application in development mode:
pnpm exec viteOpen your web browser console and check for errors or deprecation warnings. If you notice dependency resolution errors, check your node_modules structure or enable legacy.inconsistentCjsInterop.
4. Build and Verify
Run the production build:
pnpm exec vite buildVerify that the build exits successfully. Compare the generated files against the build baseline you saved before upgrading.
Post-Upgrade Verification
Before pushing your changes to main, run three validation checks:
- Compare Bundle Size: Rolldown's chunk generation and Oxc's minifier may compile bundles differently than Rollup and esbuild. Confirm that your output chunk sizes did not grow unexpectedly.
- Test in Older Browsers: Vite 8 targets Baseline Widely Available browser versions released around mid-2023 (Chrome 111, Firefox 114, Safari 16.4). If your application supports older browser engines, run your test suite against those targets or configure
@vitejs/plugin-legacy. - Inspect Source Maps: Ensure debugging paths remain valid. Run your build with source maps enabled and verify that stack traces resolve correctly to your source files in your monitoring tools.



