Vite+ 2026 — One Toolchain to Replace Webpack, ESLint, and Prettier

Posted on: 4/17/2026 6:12:30 PM

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.

1 CLI Replacing 5+ separate tools
100x Oxlint vs ESLint
30x Oxfmt vs Prettier
7.7x Faster build with Rolldown

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:

CriterionRollup (old)esbuild (old)Rolldown (new)
LanguageJavaScriptGoRust
Role in ViteProduction buildDev transformBoth dev and prod
Tree-shakingExcellentBasicExcellent
Build speedSlow (JS-based)Very fastVery fast
Rollup plugin compat100%None~95%
Dev/prod consistencyNot guaranteedGuaranteed

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

ProjectFilesESLintOxlintSpeedup
Small Vue SPA503.2s0.04s80× faster
Medium Vue + Nuxt30018s0.2s90× faster
Enterprise monorepo2000+90s0.8s112× 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:

30x Faster than Prettier
~97% Prettier-compatible output
0 config Zero-config by default

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

CriterionVite+BiomeTurbopack + ESLintTraditional toolchain
BuildRolldown (Rust)No bundlerTurbopack (Rust)Webpack/Rollup (JS)
LintOxlint (Rust)Biome (Rust)ESLint (JS)ESLint (JS)
FormatOxfmt (Rust)Biome (Rust)Prettier (JS)Prettier (JS)
TestVitestNoneJestJest/Vitest
Scaffoldvite newNonecreate-next-appcreate-vite
Vue.js supportFirst-classYes (lint/fmt)NoVia plugins
Unified configvite.config.tsbiome.jsonMultiple filesMultiple files
Shared parserOXC (parse once)Yes (parse once)NoNo

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

StageOld toolchainVite+Improvement
Install dependencies35s25sFewer deps
Lint45s (ESLint)0.5s (Oxlint)90×
Format check12s (Prettier)0.4s (Oxfmt)30×
Unit tests65s (Vitest)60s (Vitest)~1×
Production build28s (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/FrameworkStatusNotes
Vue 3.6 (Vapor Mode)✅ Fully supportedRolldown optimizes Vapor output
Nuxt 4✅ Fully supportedNuxt 4 already uses Vite 8 natively
Pinia 3✅ UnchangedStore logic isn't affected
Vue Router 4✅ UnchangedRouting isn't affected
TanStack Query Vue✅ UnchangedData fetching isn't affected
VueUse✅ UnchangedComposables aren't affected
Vuetify / PrimeVue✅ CompatibleRolldown is Rollup-plugin-compatible
Storybook⚠️ In migrationStorybook 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

Q1 2026
Vite+ Alpha goes open source — scaffold, dev, build, lint, fmt all functional. Rolldown stable for production builds.
Q2 2026
Oxlint JS Plugins Alpha — custom JS rules. Type-aware lint rules in progress.
Q3 2026 (planned)
Vite+ Beta — vite run for monorepos, stable Browser Mode testing, Storybook 9 integration.
Q4 2026 (planned)
Vite+ 1.0 stable — type-aware lint rules in Oxlint, full Rollup plugin compatibility in Rolldown.

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