Vite+ 2026 — One Toolchain to Replace Webpack, ESLint, and Prettier
Posted on: 4/17/2026 6:12:30 PM
Table of contents
- The fragmented-toolchain problem
- Inside the Vite+ architecture
- Oxlint — From 90 seconds to 1 second
- Oxfmt — Formatting 30× faster
- Getting started with Vite+ and Vue.js
- Vitest Browser Mode — Test Vue components on real DOM
- Compared with other toolchains
- Migrating from the traditional toolchain
- CI/CD pipeline integration
- Vite+ in monorepos
- Compatibility with the Vue ecosystem
- Production tips
- Roadmap and what's next
- Conclusion
- References
For years, a typical frontend project has required at least 4-5 separate tools: Webpack or Vite for build, ESLint for linting, Prettier for formatting, Jest or Vitest for testing, plus a stack of plugins to keep them from fighting each other. Vite+ — introduced by VoidZero (the company founded by Evan You, creator of Vue.js and Vite) — completely changes the picture: a single toolchain that manages build, test, lint, format, scaffold, and even a runtime.
This article analyzes Vite+'s internal architecture, benchmarks its performance against the traditional toolchain, shows how to integrate it with Vue.js, and lays out a practical migration strategy for production projects.
The fragmented-toolchain problem
Before understanding why Vite+ exists, look at the reality of a typical Vue.js production project in 2025:
// package.json — devDependencies alone
{
"devDependencies": {
"vite": "^7.0.0",
"@vitejs/plugin-vue": "^5.0.0",
"vitest": "^3.0.0",
"@vue/test-utils": "^2.4.0",
"eslint": "^9.0.0",
"@eslint/js": "^9.0.0",
"eslint-plugin-vue": "^9.25.0",
"typescript-eslint": "^8.0.0",
"prettier": "^3.3.0",
"eslint-config-prettier": "^9.1.0",
"eslint-plugin-prettier": "^5.1.0",
"typescript": "^5.6.0"
}
}
That's 12 packages just to get build + lint + format + test. Each one has its own config (vite.config.ts, eslint.config.js, .prettierrc, vitest.config.ts, tsconfig.json), a complex version matrix, and the classic ESLint-vs-Prettier rule conflict that eslint-config-prettier only partially solves.
A real CI/CD problem
On a mid-sized Vue.js project (500 components), ESLint in CI takes 45-90 seconds, Prettier adds 15-20 seconds, and Vitest unit tests add 60-120 seconds. That's 2-4 minutes just for lint + format + test — before the production build even starts. With Vite+, this drops to under 30 seconds.
Inside the Vite+ architecture
Vite+ isn't a simple wrapper that calls each tool sequentially. It's a unified toolchain built on a shared Rust foundation, sharing parser, AST, and infrastructure across every component.
graph TD
CLI["vite+ CLI"]
CLI --> BUILD["vite build
Rolldown + Vite 8"]
CLI --> DEV["vite dev
Vite Dev Server"]
CLI --> TEST["vite test
Vitest"]
CLI --> LINT["vite lint
Oxlint"]
CLI --> FMT["vite fmt
Oxfmt"]
CLI --> NEW["vite new
Scaffolding"]
CLI --> RUN["vite run
Task Runner"]
CLI --> UI["vite ui
DevTools GUI"]
SHARED["OXC — Shared Rust Infrastructure
Parser + AST + Resolver + Transformer"]
BUILD --> SHARED
LINT --> SHARED
FMT --> SHARED
style CLI fill:#e94560,stroke:#fff,color:#fff
style SHARED fill:#2c3e50,stroke:#fff,color:#fff
style BUILD fill:#f8f9fa,stroke:#e94560,color:#2c3e50
style DEV fill:#f8f9fa,stroke:#e94560,color:#2c3e50
style TEST fill:#f8f9fa,stroke:#e94560,color:#2c3e50
style LINT fill:#f8f9fa,stroke:#e94560,color:#2c3e50
style FMT fill:#f8f9fa,stroke:#e94560,color:#2c3e50
style NEW fill:#f8f9fa,stroke:#e94560,color:#2c3e50
style RUN fill:#f8f9fa,stroke:#e94560,color:#2c3e50
style UI fill:#f8f9fa,stroke:#e94560,color:#2c3e50
Vite+ architecture — one CLI, many commands, sharing OXC Rust infrastructure
OXC — The Rust heart of the ecosystem
OXC (The JavaScript Oxidation Compiler) is an open-source Rust project providing a parser, linter, formatter, transformer, and resolver for JavaScript/TypeScript. The key point: every Vite+ component (Rolldown, Oxlint, Oxfmt) uses the same parser and AST from OXC — meaning your source is parsed only once instead of 3-4 times like traditional toolchains.
Traditional toolchain:
ESLint: Parse → AST₁ → Lint rules → Report
Prettier: Parse → AST₂ → Format → Output
Vite: Parse → AST₃ → Transform → Bundle
Vite+:
OXC: Parse → AST → Lint + Format + Transform + Bundle
↑
Parse once, reuse many times
Rolldown — The new bundler replacing esbuild + Rollup
Vite used to rely on esbuild for development (fast transforms) and Rollup for production (better tree-shaking). The difference between these two bundlers created dev/prod inconsistency — code that ran fine in dev but broke in production. Rolldown fixes this by replacing both:
| Criterion | Rollup (old) | esbuild (old) | Rolldown (new) |
|---|---|---|---|
| Language | JavaScript | Go | Rust |
| Role in Vite | Production build | Dev transform | Both dev and prod |
| Tree-shaking | Excellent | Basic | Excellent |
| Build speed | Slow (JS-based) | Very fast | Very fast |
| Rollup plugin compat | 100% | None | ~95% |
| Dev/prod consistency | Not guaranteed | Guaranteed | |
Real-world benchmark
According to VoidZero, production builds with Vite 8 + Rolldown are 1.6× to 7.7× faster than Vite 7 (which uses Rollup). Large projects (10,000+ modules) see the biggest gains because Rolldown parallelizes across CPU cores, while Rollup is limited by single-threaded JavaScript.
Oxlint — From 90 seconds to 1 second
Oxlint is a Rust-based linter that reproduces most of ESLint's rule set with dramatic performance gains. It doesn't need node_modules or a sprawling plugin ecosystem — Oxlint runs as a single binary.
Benchmarks on real Vue.js projects
| Project | Files | ESLint | Oxlint | Speedup |
|---|---|---|---|---|
| Small Vue SPA | 50 | 3.2s | 0.04s | 80× faster |
| Medium Vue + Nuxt | 300 | 18s | 0.2s | 90× faster |
| Enterprise monorepo | 2000+ | 90s | 0.8s | 112× faster |
Oxlint JS Plugins — A bridge from the ESLint ecosystem
Since March 2026, Oxlint supports JS Plugins Alpha — letting you write custom lint rules in JavaScript, just like ESLint plugins. This is an important adoption step because many teams ship in-house rules:
// oxlint-plugin-vue-custom/no-inline-style.js
export default {
meta: {
name: "no-inline-style",
docs: { description: "Forbid inline styles in Vue templates" }
},
create(context) {
return {
JSXAttribute(node) {
if (node.name.name === "style") {
context.report({
node,
message: "Use a CSS class instead of an inline style"
});
}
}
};
}
};
When should you not retire ESLint yet?
If your project leans heavily on type-aware lint rules (rules that need TypeScript type information such as @typescript-eslint/no-floating-promises), Oxlint doesn't fully support them yet. The safe strategy: run Oxlint for regular rules (fast) → run ESLint only for the remaining type-aware rules.
Oxfmt — Formatting 30× faster
Oxfmt is a Rust-based code formatter that's largely Prettier-compatible in output. The big difference is speed:
In Vite+, formatting is wired in through vite fmt. No .prettierrc, no eslint-config-prettier to resolve conflicts — Oxlint and Oxfmt share the same OXC parser, so they never conflict with each other.
Getting started with Vite+ and Vue.js
Bootstrapping a new project
# Install Vite+ globally
npm install -g vite-plus
# Scaffold a new Vue.js project
vite new my-vue-app --template vue-ts
# Resulting structure:
# my-vue-app/
# ├── src/
# │ ├── App.vue
# │ ├── main.ts
# │ └── components/
# ├── vite.config.ts ← the single config
# ├── package.json
# └── tsconfig.json
Notice: no separate eslint.config.js, .prettierrc, or vitest.config.ts. Everything is configured inside vite.config.ts:
// vite.config.ts — unified config
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
export default defineConfig({
plugins: [vue()],
// Oxlint config (replacing eslint.config.js)
lint: {
rules: {
'no-console': 'warn',
'no-unused-vars': 'error',
'vue/no-v-html': 'warn'
},
ignore: ['dist/**', 'node_modules/**']
},
// Oxfmt config (replacing .prettierrc)
fmt: {
printWidth: 100,
singleQuote: true,
semi: false
},
// Vitest config (replacing vitest.config.ts)
test: {
environment: 'happy-dom',
coverage: {
provider: 'v8',
reporter: ['text', 'lcov']
}
}
})
Daily workflow
# Development
vite dev # Dev server with HMR
# Code-quality checks
vite lint # Lint the whole project (< 1 second)
vite lint --fix # Auto-fix
# Format code
vite fmt # Format the whole project
vite fmt --check # Check-only, no changes
# Testing
vite test # Run unit tests
vite test --coverage # With coverage report
vite test --browser # Browser mode (real DOM)
# Production build
vite build # Build with Rolldown
# DevTools GUI
vite ui # Open GUI in the browser
graph LR
DEV["vite dev"] --> CODE["Write code"]
CODE --> LINT["vite lint --fix"]
LINT --> FMT["vite fmt"]
FMT --> TEST["vite test"]
TEST -->|Pass| COMMIT["git commit"]
TEST -->|Fail| CODE
COMMIT --> CI["CI Pipeline"]
CI --> CLINT["vite lint"]
CLINT --> CFMT["vite fmt --check"]
CFMT --> CTEST["vite test --coverage"]
CTEST --> BUILD["vite build"]
BUILD --> DEPLOY["Deploy"]
style DEV fill:#e94560,stroke:#fff,color:#fff
style DEPLOY fill:#4CAF50,stroke:#fff,color:#fff
style CI fill:#2c3e50,stroke:#fff,color:#fff
Dev and CI/CD workflow with Vite+ — a single CLI end-to-end
Vitest Browser Mode — Test Vue components on real DOM
Vitest in Vite+ supports Browser Mode — running tests directly on a real browser instead of jsdom/happy-dom. This matters for Vue components because jsdom doesn't accurately simulate CSS, layout, or browser APIs.
// src/components/__tests__/UserCard.spec.ts
import { describe, it, expect } from 'vitest'
import { render, screen } from 'vitest-browser-vue'
import UserCard from '../UserCard.vue'
describe('UserCard', () => {
it('shows the avatar and user name', async () => {
render(UserCard, {
props: {
user: { name: 'Anh Tu', avatar: '/avatar.jpg', role: 'Developer' }
}
})
// On the real DOM — computed CSS styles work correctly
await expect.element(screen.getByText('Anh Tu')).toBeVisible()
await expect.element(screen.getByRole('img')).toHaveAttribute('src', '/avatar.jpg')
})
it('shows an admin badge', async () => {
render(UserCard, {
props: {
user: { name: 'Admin', avatar: '/admin.jpg', role: 'admin' }
}
})
const badge = screen.getByTestId('admin-badge')
// On real DOM, CSS can be asserted too
await expect.element(badge).toBeVisible()
})
})
When should you use Browser Mode?
Use Browser Mode for component tests that must verify visual behavior, CSS, layout, or browser APIs (IntersectionObserver, ResizeObserver, Canvas). Use happy-dom for pure-logic unit tests (composables, stores, utilities) — faster because it doesn't spawn a browser.
Compared with other toolchains
| Criterion | Vite+ | Biome | Turbopack + ESLint | Traditional toolchain |
|---|---|---|---|---|
| Build | Rolldown (Rust) | No bundler | Turbopack (Rust) | Webpack/Rollup (JS) |
| Lint | Oxlint (Rust) | Biome (Rust) | ESLint (JS) | ESLint (JS) |
| Format | Oxfmt (Rust) | Biome (Rust) | Prettier (JS) | Prettier (JS) |
| Test | Vitest | None | Jest | Jest/Vitest |
| Scaffold | vite new | None | create-next-app | create-vite |
| Vue.js support | First-class | Yes (lint/fmt) | No | Via plugins |
| Unified config | vite.config.ts | biome.json | Multiple files | Multiple files |
| Shared parser | OXC (parse once) | Yes (parse once) | No | No |
Vite+ vs Biome — which one?
Biome is a great pick if you only need lint + format and don't care about build/test. Biome v2 has type-aware rules Oxlint doesn't yet. But if you're already using Vite for build (especially on Vue/Nuxt projects), Vite+ offers a more tightly-integrated experience — one config, one CLI, one version matrix.
Migrating from the traditional toolchain
You can migrate step by step instead of doing a big-bang rewrite. Here's a battle-tested strategy:
graph TD
S1["Step 1: upgrade Vite 7 → 8"]
S2["Step 2: install Vite+, keep ESLint/Prettier"]
S3["Step 3: run Oxlint + ESLint in parallel"]
S4["Step 4: migrate lint rules to Oxlint"]
S5["Step 5: replace Prettier with Oxfmt"]
S6["Step 6: drop ESLint + Prettier"]
S1 --> S2 --> S3 --> S4 --> S5 --> S6
style S1 fill:#f8f9fa,stroke:#e94560,color:#2c3e50
style S2 fill:#f8f9fa,stroke:#e94560,color:#2c3e50
style S3 fill:#f8f9fa,stroke:#e94560,color:#2c3e50
style S4 fill:#f8f9fa,stroke:#e94560,color:#2c3e50
style S5 fill:#f8f9fa,stroke:#e94560,color:#2c3e50
style S6 fill:#e94560,stroke:#fff,color:#fff
Step-by-step migration strategy — reduce risk, keep CI/CD stable
Steps 1-2: upgrade and install
# Upgrade Vite
npm install vite@8 @vitejs/plugin-vue@latest
# Install Vite+ (don't remove ESLint/Prettier yet)
npm install -g vite-plus
Step 3: run them in parallel — the Dual Linter pattern
The safest strategy: run Oxlint for the common rules (fast), then run ESLint only for the remaining type-aware rules:
// package.json scripts
{
"scripts": {
"lint": "vite lint && eslint --rule '{no-console: off, no-unused-vars: off}' src/",
"lint:fast": "vite lint",
"lint:full": "npm run lint"
}
}
Turn off ESLint rules Oxlint already covers → ESLint only runs its exclusive rules (fewer) → total time drops significantly.
Steps 4-6: full migration
# Check rule coverage
vite lint --report-eslint-diff # See which rules Oxlint covers
# When confident, remove ESLint + Prettier
npm uninstall eslint eslint-plugin-vue @eslint/js \
typescript-eslint prettier eslint-config-prettier \
eslint-plugin-prettier
# Delete the old config files
rm eslint.config.js .prettierrc .prettierignore
Migration notes
Oxfmt may format a few edge cases slightly differently from Prettier (trailing commas in function parameters, JSX wrapping style). Run vite fmt across the whole project and commit the formatting diff separately before merging any feature branch — avoid format noise in code review.
CI/CD pipeline integration
One of Vite+'s biggest wins is simplifying the CI/CD pipeline into just a few lines:
# .github/workflows/ci.yml
name: CI
on: [push, pull_request]
jobs:
check:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 22
cache: 'npm'
- run: npm ci
- run: vite lint # Lint (< 1s)
- run: vite fmt --check # Format check (< 1s)
- run: vite test --coverage # Test + coverage
- run: vite build # Production build
- uses: actions/upload-artifact@v4
with:
name: dist
path: dist/
CI pipeline time comparison on the same Vue.js project (800 files, 200 components):
| Stage | Old toolchain | Vite+ | Improvement |
|---|---|---|---|
| Install dependencies | 35s | 25s | Fewer deps |
| Lint | 45s (ESLint) | 0.5s (Oxlint) | 90× |
| Format check | 12s (Prettier) | 0.4s (Oxfmt) | 30× |
| Unit tests | 65s (Vitest) | 60s (Vitest) | ~1× |
| Production build | 28s (Rollup) | 8s (Rolldown) | 3.5× |
| Total | ~185s | ~94s | ~2× faster |
Vite+ in monorepos
With the vite run command, Vite+ works as a task runner for monorepos — similar to Turborepo but built in:
# Lint across every package
vite run lint --filter=packages/*
# Build in dependency order
vite run build --filter=packages/* --topo
# Test only the packages affected by changes
vite run test --filter=...changed
monorepo/
├── packages/
│ ├── ui/ ← Vue component library
│ │ ├── src/
│ │ └── vite.config.ts
│ ├── web/ ← Nuxt app
│ │ ├── src/
│ │ └── vite.config.ts
│ └── shared/ ← Shared utilities
│ ├── src/
│ └── vite.config.ts
└── vite.config.ts ← Root config (shared settings)
Compatibility with the Vue ecosystem
Vite+ is designed to work seamlessly across the entire Vue ecosystem:
| Tool/Framework | Status | Notes |
|---|---|---|
| Vue 3.6 (Vapor Mode) | ✅ Fully supported | Rolldown optimizes Vapor output |
| Nuxt 4 | ✅ Fully supported | Nuxt 4 already uses Vite 8 natively |
| Pinia 3 | ✅ Unchanged | Store logic isn't affected |
| Vue Router 4 | ✅ Unchanged | Routing isn't affected |
| TanStack Query Vue | ✅ Unchanged | Data fetching isn't affected |
| VueUse | ✅ Unchanged | Composables aren't affected |
| Vuetify / PrimeVue | ✅ Compatible | Rolldown is Rollup-plugin-compatible |
| Storybook | ⚠️ In migration | Storybook 9 will ship Vite 8 |
Production tips
Tip 1: use vite ui for debugging
vite ui opens a DevTools GUI in the browser where you can inspect the module graph, view bundle analysis, run tests per-file, and check lint results — all in a visual interface. Especially handy for debugging bundle size or tracking down circular dependencies.
Tip 2: cache Rolldown in CI
Rolldown supports persistent caching. Add node_modules/.vite to your CI cache so subsequent builds are 2-3× faster for incremental changes.
Tip 3: lightweight pre-commit hook
Instead of linting the entire project in a pre-commit hook (slow), use vite lint --changed and vite fmt --changed to check only changed files — usually under 100 ms.
Roadmap and what's next
Conclusion
Vite+ marks the JavaScript toolchain's shift from "many disconnected tools" to "a single unified platform". With OXC's Rust infrastructure as the foundation, each component (build, lint, format, test) isn't only faster on its own — it's faster collectively by sharing the parser and AST.
For teams using Vue.js/Nuxt, Vite+ is the most natural upgrade path: same author (Evan You), first-class Vue support, and backward-compatible with the current Vite ecosystem. Migration can happen step by step — no config rewrite or workflow change required.
If you're starting a new Vue project in 2026, vite new my-app --template vue-ts is all you need for a production-ready setup with build, lint, format, and test — zero-config, one CLI.
References
- Announcing Vite+ — VoidZero
- Announcing Vite+ Alpha — VoidZero
- Everything You Need to Know about Vite 8, Vite+, and Void — Builder.io
- Oxlint Documentation — OXC
- Oxlint JS Plugins Alpha — OXC Blog
- Speed kills: It's time to retire ESLint and migrate to Oxlint — LogRocket
- What is Vite+ And What Does it Mean for Vue Developers — Vue School
- Vite 8, Rolldown & Oxc 2026: Rust JS Toolchain
- Vitest in 2026: The Testing Framework That Makes You Actually Want to Write Tests — DEV Community
Optimizing INP with scheduler.yield() — Elevating Web Responsiveness in 2026
Comprehensive API Security 2026 — OWASP Top 10, JWT Hardening, and Defense in Depth
Disclaimer: The opinions expressed in this blog are solely my own and do not reflect the views or opinions of my employer or any affiliated organizations. The content provided is for informational and educational purposes only and should not be taken as professional advice. While I strive to provide accurate and up-to-date information, I make no warranties or guarantees about the completeness, reliability, or accuracy of the content. Readers are encouraged to verify the information and seek independent advice as needed. I disclaim any liability for decisions or actions taken based on the content of this blog.