DEV Community

Cover image for Modernizing the Angular Quality Stack: Moving to Vite+
Nikhil Raj A
Nikhil Raj A

Posted on

Modernizing the Angular Quality Stack: Moving to Vite+

This didn’t start as a tooling migration—it started with my own curiosity.

Oxlint and Oxfmt were suddenly everywhere. People were claiming massive speed improvements and near-instant TypeScript linting. That sounded great, but most benchmarks were tiny demo projects. I wanted to see what happens in a real Angular codebase.

There was also a real problem underneath the curiosity. ESLint + Prettier had slowly become friction in the developer workflow. Linting was slow enough that people stopped running it locally and relied more on pre-commit and CI checks instead. Over time, this created slower feedback loops, which is usually the point where tooling starts getting in the way instead of helping developers move faster.

So, I tried replacing Prettier and most TypeScript ESLint checks with Oxfmt + Oxlint using Vite+, while keeping ESLint for Angular templates. Here’s what actually happened.

First Important Clarification

I did not replace Angular CLI. Angular CLI still handles:

  • ng serve
  • ng build
  • ng test

What I changed was only the quality tooling layer:

  • Prettier → replaced by Oxfmt
  • Most TypeScript ESLint rules → replaced by Oxlint
  • ESLint → kept only for Angular templates
  • Stylelint → kept for CSS rules
  • Vite+ → used as the orchestrator for staged linting and formatting

Angular CLI still builds and serves the app. Vite+ is only used for linting, formatting, and staged file checks.

Why Not Biome?

Biome came up immediately. Biome is great, especially for greenfield JS or React projects.

But Angular has one big requirement: @angular-eslint rules. Biome doesn’t support Angular ESLint plugins. Oxlint doesn’t fully support Angular either, but it allows Angular TypeScript rules via plugins, which made it usable for Angular projects.

The Angular Limitation (This Is Important)

Oxlint cannot lint Angular templates yet. Templates require a custom parser, so ESLint is still required for .html files. However, Angular TypeScript rules can now run inside Oxlint, meaning ESLint is only needed for templates.

Final Tool Responsibility Split

File Type Formatting Linting
.ts Oxfmt Oxlint + Angular TS rules
.html Oxfmt ESLint (Angular template rules)
.css / .scss Oxfmt Stylelint (rules only)
  • Oxfmt handles formatting everywhere.
  • Oxlint handles TypeScript linting.
  • ESLint handles Angular templates.
  • Stylelint handles CSS rules.
  • Vite+ orchestrates everything.

Once this responsibility split was clear, the setup actually felt simpler than the traditional ESLint + Prettier + Stylelint pipeline.


Hybrid Tooling Setup for Angular


Core Idea of the Setup

What this really means is... Vite+ acts as a high-speed traffic controller. Instead of running one heavy ESLint process on every file, it routes each file type to the fastest tool that can handle it correctly.

The orchestration lives in the Vite+ config:

// vite.config.js
import { defineConfig } from 'vite-plus';

export default defineConfig({
  ignorePatterns: ['dist/**', '.angular/**', 'node_modules/**'],

  staged: {
    '*.ts': 'vp lint --fix',
    '*.{json,html}': 'vp fmt',
    '*.html': 'eslint --fix',
    '*.{css,scss}': ['vp fmt', 'stylelint --fix'],
  },
});
Enter fullscreen mode Exit fullscreen mode

Formatting rules live separately in .oxfmtrc.json:

{
  "$schema": "./node_modules/oxfmt/configuration_schema.json",
  "singleQuote": true,
  "semi": true,
  "printWidth": 100,
  "tabWidth": 2,
  "trailingComma": "all"
}
Enter fullscreen mode Exit fullscreen mode

Keeping Oxfmt rules in .oxfmtrc.json turned out to be more reliable than nesting them inside vite.config.js. That split ended up being the more maintainable option: runner config in vite.config.js, tool rules in tool-specific config files.


Step-by-Step Setup for Your Angular Project

1. Install dependencies

npm install --save-dev vite-plus stylelint stylelint-config-standard stylelint-order stylelint-config-recess-order @angular-eslint/eslint-plugin @angular-eslint/template-parser @angular-eslint/eslint-plugin-template
Enter fullscreen mode Exit fullscreen mode

2. Configure Oxlint (.oxlintrc.json)

{
  "jsPlugins": ["@angular-eslint/eslint-plugin"],
  "ignorePatterns": ["dist/**", ".angular/**", "node_modules/**"],
  "rules": {
    "no-debugger": "error",
    "no-var": "error",
    "prefer-const": "error",
    "@typescript-eslint/no-unused-vars": "warn",
    "@angular-eslint/no-output-native": "error",
    "@angular-eslint/use-lifecycle-interface": "warn",
    "@angular-eslint/component-selector": [
      "error",
      { "type": "element", "prefix": "app", "style": "kebab-case" }
    ]
  }
}
Enter fullscreen mode Exit fullscreen mode

3. Configure Oxfmt (.oxfmtrc.json)

{
  "$schema": "./node_modules/oxfmt/configuration_schema.json",
  "singleQuote": true,
  "semi": true,
  "printWidth": 100,
  "tabWidth": 2,
  "trailingComma": "all"
}
Enter fullscreen mode Exit fullscreen mode

4. Configure ESLint for Templates (eslint.config.js)

import templateParser from '@angular-eslint/template-parser';
import templatePlugin from '@angular-eslint/eslint-plugin-template';

export default [
  {
    files: ['**/*.html'],
    languageOptions: {
      parser: templateParser,
    },
    plugins: {
      '@angular-eslint/template': templatePlugin,
    },
    rules: {
      '@angular-eslint/template/button-has-type': 'error',
      '@angular-eslint/template/no-negated-async': 'warn',
      '@angular-eslint/template/alt-text': 'error',
    },
  },
];
Enter fullscreen mode Exit fullscreen mode

5. Configure Stylelint (.stylelintrc.json)

{
  "extends": ["stylelint-config-standard", "stylelint-config-recess-order"],
  "ignoreFiles": ["dist/**", "node_modules/**", ".angular/**"],
  "rules": {
    "color-no-invalid-hex": true,
    "unit-no-unknown": true,
    "property-no-unknown": true,
    "declaration-block-no-duplicate-properties": true,
    "color-named": "never",
    "no-empty-source": null
  }
}
Enter fullscreen mode Exit fullscreen mode

6. Update package.json scripts

"scripts": {
  "ng": "ng",
  "start": "ng serve",
  "build": "ng build",
  "test": "ng test",
  "lint": "vp lint",
  "lint:fix": "vp lint --fix",
  "lint:html": "eslint \"src/**/*.html\"",
  "lint:styles": "stylelint \"src/**/*.{css,scss}\"",
  "lint:styles:fix": "stylelint \"src/**/*.{css,scss}\" --fix",
  "format": "vp fmt --check",
  "format:fix": "vp fmt"
}
Enter fullscreen mode Exit fullscreen mode

7. Add a Pre-Commit Hook (Important)

Vite+ handles staged file processing, but you still need a Git hook to trigger it during commits.

Create the file: .git/hooks/pre-commit and add:

#!/bin/sh
npx vp check
Enter fullscreen mode Exit fullscreen mode

Make it executable: chmod +x .git/hooks/pre-commit

Now every commit will automatically format staged files and run the relevant linters. This replaces the need for Husky + lint-staged.


Before vs After Tooling

Before After
ESLint (TS + HTML) Oxlint (TypeScript)
Prettier ESLint (HTML templates only)
Stylelint Stylelint (CSS rules only)
Manual formatting drift Oxfmt (formatting everywhere)
One big lint pipeline Vite+ orchestrating specialized tools

Biggest Improvement

Yes, Oxlint is faster than ESLint. But the biggest win was the workflow:

  • Before: Linting mostly happened in CI → After: Linting happens locally again.
  • Before: Pre-commit was slow → After: Pre-commit fast again.
  • Result: Faster feedback loops and a smoother development workflow.

Things to Watch Out For

  • Oxlint auto-fix is more limited than ESLint.
  • ESLint is still required for Angular templates.
  • Multiple config files: You still have to manage them, but it’s a fair trade for speed.
  • Keep rules separate from orchestration: For larger apps, don't force everything into vite.config.js.

Final Thoughts

For Angular projects today, completely replacing ESLint is still not realistic because templates require specialized rules. But splitting responsibilities between specialized tools—Oxlint for TS, ESLint for templates, Stylelint for styles, and Oxfmt for formatting—with Vite+ as the orchestrator is a game-changer.

The biggest improvement was not just lint speed, but developer behavior. When the tools are fast, developers actually use them.

🚀 Full boilerplate and working config: angular-viteplus

Top comments (0)