Skip to content

Repository files navigation

ng-viteplus-config

Baseline code quality setup for Angular using Vite+.

Covers TypeScript linting, Angular template linting, SCSS linting, formatting, and staged-file pre-commit enforcement.


Table of Contents

  1. What Is Included
  2. Setup Guide
  1. How Pre-commit Works
  2. Commands You Can Run
  3. Customization
  4. Initial Cleanup for Existing Projects

What Is Included

File Purpose
.oxfmtrc.json Formatter options for oxfmt
.oxlintrc.json TypeScript and JavaScript lint rules through oxlint
.stylelintrc.json CSS and SCSS lint rules
eslint.config.ts Angular HTML template lint rules (ESLint flat config)
vite.config.ts Staged-file tool mapping and global lint ignore patterns
scripts/pre-commit Git hook script that runs npx vp staged

Setup Guide

Step 1: Install required packages

Run this in your Angular project root:

pnpm install --save-dev vite-plus eslint jiti @typescript-eslint/utils @angular-eslint/eslint-plugin @angular-eslint/eslint-plugin-template @angular-eslint/template-parser stylelint stylelint-config-standard-scss stylelint-config-recess-order stylelint-order
What each package does
Package Purpose
vite-plus Provides the vp CLI for linting, formatting, and staged-file orchestration
eslint Core ESLint engine (used here only for Angular HTML templates)
jiti Runtime TypeScript loader so ESLint can read eslint.config.ts directly
@typescript-eslint/utils Shared utilities required by Angular ESLint plugins
@angular-eslint/eslint-plugin ESLint rules for Angular component and directive classes
@angular-eslint/eslint-plugin-template ESLint rules for Angular HTML templates
@angular-eslint/template-parser Parser that lets ESLint understand Angular template syntax
stylelint Core Stylelint engine for CSS and SCSS linting
stylelint-config-standard-scss Shared SCSS rule preset (extends stylelint-config-standard)
stylelint-config-recess-order Enforces a consistent CSS property declaration order
stylelint-order Plugin that powers property-order rules used by the recess preset

Step 2: Add config files

.oxfmtrc.json

{
  "$schema": "./node_modules/oxfmt/configuration_schema.json",
  "singleQuote": true,
  "semi": true,
  "printWidth": 180,
  "tabWidth": 2,
  "trailingComma": "all"
}
What each option does
Option Type Value Description
$schema string path to JSON schema Points to the local schema so your editor can validate and autocomplete this file
singleQuote boolean true Uses single quotes instead of double quotes wherever possible
semi boolean true Appends a semicolon at the end of every statement
printWidth number 180 Maximum preferred line length before the formatter wraps code
tabWidth number 2 Number of spaces per indentation level
trailingComma string "all" Adds trailing commas in every position where they are syntactically valid (produces cleaner diffs)

.oxlintrc.json

If your app uses a different selector prefix (for example ngvpc), update the @angular-eslint/component-selector and @angular-eslint/directive-selector rules below.

{
  "$schema": "./node_modules/oxlint/configuration_schema.json",
  "plugins": [
    "typescript",
    "import"
  ],
  "jsPlugins": [
    "@angular-eslint/eslint-plugin"
  ],
  "ignorePatterns": [
    "dist/**",
    ".angular/**",
    "node_modules/**"
  ],
  "env": {
    "browser": true,
    "es2021": true
  },
  "categories": {
    "correctness": "warn"
  },
  "rules": {
    "no-debugger": "error",
    "no-eval": "error",
    "no-var": "error",
    "prefer-const": "error",
    "eqeqeq": "error",
    "no-fallthrough": "error",
    "no-constant-binary-expression": "error",
    "no-self-assign": "error",
    "no-self-compare": "error",
    "no-constant-condition": "warn",
    "no-unsafe-optional-chaining": "error",
    "no-loss-of-precision": "warn",
    "no-duplicate-enum-values": "error",
    "no-dupe-else-if": "error",
    "no-console": "warn",
    "no-alert": "error",
    "no-caller": "error",
    "no-new-wrappers": "error",
    "no-throw-literal": "error",
    "no-useless-escape": "warn",
    "@typescript-eslint/no-unused-vars": "warn",
    "@typescript-eslint/no-non-null-assertion": "warn",
    "@typescript-eslint/no-wrapper-object-types": "warn",
    "@typescript-eslint/no-explicit-any": "warn",
    "@typescript-eslint/no-empty-interface": "warn",
    "import/no-duplicates": "warn",
    "import/no-self-import": "error",
    "@angular-eslint/no-output-native": "error",
    "@angular-eslint/use-lifecycle-interface": "warn",
    "@angular-eslint/no-input-rename": "error",
    "@angular-eslint/no-output-rename": "error",
    "@angular-eslint/component-class-suffix": "error",
    "@angular-eslint/directive-class-suffix": "error",
    "@angular-eslint/component-selector": [
      "error",
      {
        "type": "element",
        "prefix": "ngvpc",
        "style": "kebab-case"
      }
    ],
    "@angular-eslint/directive-selector": [
      "error",
      {
        "type": "attribute",
        "prefix": "ngvpc",
        "style": "camelCase"
      }
    ]
  },
  "overrides": [
    {
      "files": [
        "*.spec.ts",
        "*.mock.ts",
        "**/test/**",
        "**/mock/**"
      ],
      "rules": {
        "no-console": "off",
        "@typescript-eslint/no-explicit-any": "off",
        "@typescript-eslint/no-unused-vars": "off"
      }
    }
  ]
}
What each top-level option does
Option Description
$schema Enables schema-based validation and autocomplete in your editor
plugins Loads built-in oxlint plugin packs (typescript for TS-specific rules, import for module import rules)
jsPlugins Loads external JS-based ESLint plugins from node_modules (here: @angular-eslint/eslint-plugin)
ignorePatterns Glob patterns for folders that should be excluded from linting entirely
env Declares global environment assumptions so the linter knows which globals exist (browser, es2021)
categories Sets default severity for entire rule categories (correctness rules default to warn)
rules Explicit severity overrides and options for individual lint rules (see rule breakdown below)
overrides File-pattern-specific rule changes; used here to relax rules in test and mock files
Rules -- safety and correctness
Rule Severity What it catches
no-debugger error Leftover debugger statements
no-eval error Use of eval(), which is a security risk
no-var error var declarations (use let or const instead)
prefer-const error Variables declared with let that are never reassigned
eqeqeq error Loose equality (==, !=) instead of strict (===, !==)
no-fallthrough error Switch cases that fall through without a break or return
no-constant-binary-expression error Binary expressions where the result is always the same
no-self-assign error Assigning a variable to itself (x = x)
no-self-compare error Comparing a variable to itself (x === x)
no-constant-condition warn Conditions that are always truthy or always falsy
no-unsafe-optional-chaining error Optional chaining in positions where undefined would cause a runtime error
no-loss-of-precision warn Number literals that lose precision at runtime
no-duplicate-enum-values error Duplicate values inside a TypeScript enum
no-dupe-else-if error Duplicate conditions in if-else-if chains
no-console warn console.* calls (warns so you remember to remove them)
no-alert error alert(), confirm(), and prompt() calls
no-caller error Deprecated arguments.caller and arguments.callee
no-new-wrappers error Primitive wrapper constructors like new String()
no-throw-literal error Throwing non-Error values (strings, numbers)
no-useless-escape warn Escape characters that have no effect
Rules -- TypeScript
Rule Severity What it catches
@typescript-eslint/no-unused-vars warn Declared variables, imports, or parameters that are never used
@typescript-eslint/no-non-null-assertion warn Non-null assertions (value!) that bypass null checking
@typescript-eslint/no-wrapper-object-types warn Use of wrapper object types (String, Number) instead of primitives (string, number)
@typescript-eslint/no-explicit-any warn Explicit any type annotations
@typescript-eslint/no-empty-interface warn Empty interface declarations that add no value
Rules -- import hygiene
Rule Severity What it catches
import/no-duplicates warn Multiple import statements from the same module
import/no-self-import error A module importing itself
Rules -- Angular conventions
Rule Severity What it enforces
@angular-eslint/no-output-native error Prevents @Output names that collide with native DOM events
@angular-eslint/use-lifecycle-interface warn Requires implementing the interface for any lifecycle hook used (e.g., OnInit)
@angular-eslint/no-input-rename error Prevents renaming @Input bindings (the alias must match the property name)
@angular-eslint/no-output-rename error Prevents renaming @Output bindings
@angular-eslint/component-class-suffix error Component classes must end with Component
@angular-eslint/directive-class-suffix error Directive classes must end with Directive
@angular-eslint/component-selector error Component selectors must be elements, use the configured prefix, and follow kebab-case
@angular-eslint/directive-selector error Directive selectors must be attributes, use the configured prefix, and follow camelCase
Overrides -- test and mock files

Files matching *.spec.ts, *.mock.ts, **/test/**, or **/mock/** get these relaxations:

Rule Changed to Reason
no-console off Console output is common in test debugging
@typescript-eslint/no-explicit-any off Test helpers often use any for convenience
@typescript-eslint/no-unused-vars off Partial setups may declare unused vars intentionally

.stylelintrc.json

{
  "extends": [
    "stylelint-config-standard-scss",
    "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,
      {
        "ignore": [
          "consecutive-duplicates-with-different-values"
        ]
      }
    ],
    "color-named": "never",
    "no-empty-source": null,
    "no-descending-specificity": null,
    "declaration-property-value-no-unknown": null,
    "scss/no-global-function-names": null,
    "scss/dollar-variable-pattern": null
  }
}
What each top-level option does
Option Description
extends Imports shared rule presets: stylelint-config-standard-scss for SCSS conventions and stylelint-config-recess-order for consistent property declaration order
ignoreFiles Glob patterns for folders that Stylelint should skip entirely
rules Per-rule severity overrides (see rule breakdown below)
Rules -- validation
Rule Value What it does
color-no-invalid-hex true Rejects malformed hex color values (e.g., #fff00z)
unit-no-unknown true Rejects unrecognized CSS units (e.g., 10pixels)
property-no-unknown true Rejects unrecognized CSS property names
Rules -- duplicate control
Rule Value What it does
declaration-block-no-duplicate-properties true with ignore consecutive-duplicates-with-different-values Flags duplicate properties in the same block, but allows consecutive duplicates that have different values (common for fallback declarations like color: #000; color: var(--text))
Rules -- style preference
Rule Value What it does
color-named "never" Disallows named colors like red or blue; forces hex, rgb, or hsl values instead
Rules -- disabled (set to null)
Rule Why it is disabled
no-empty-source Angular component styles are often empty files; this avoids false positives
no-descending-specificity Frequently produces noise in Angular component-scoped styles
declaration-property-value-no-unknown Has limited awareness of modern CSS features and custom properties
scss/no-global-function-names Many SCSS codebases still use global functions like darken() or lighten()
scss/dollar-variable-pattern Avoids enforcing a naming convention that may conflict with existing variable names

eslint.config.ts

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',
      '@angular-eslint/template/click-events-have-key-events': 'warn',
      '@angular-eslint/template/no-duplicate-attributes': 'error',
      '@angular-eslint/template/no-autofocus': 'warn',
      '@angular-eslint/template/table-scope': 'error',
      '@angular-eslint/template/valid-aria': 'error'
    }
  }
];
What each top-level option does
Option Description
files Limits this config block to **/*.html files only
languageOptions.parser Registers @angular-eslint/template-parser so ESLint can understand Angular template syntax
plugins Registers the @angular-eslint/eslint-plugin-template plugin under the @angular-eslint/template namespace
rules Individual rule severities for Angular HTML templates (see rule breakdown below)
Rules -- accessibility
Rule Severity What it enforces
button-has-type error Every <button> must have an explicit type attribute (button, submit, or reset)
alt-text error Images and other replaced elements must have meaningful alt text
click-events-have-key-events warn Elements with (click) should also handle keyboard events for accessibility
table-scope error <th> elements must have a valid scope attribute
valid-aria error ARIA attributes must use valid roles, states, and properties
no-autofocus warn Discourages the autofocus attribute, which can disorient screen reader users
Rules -- template quality
Rule Severity What it enforces
no-negated-async warn Prevents negating async pipe results (e.g., *ngIf="!(obs$ | async)"), which is error-prone
no-duplicate-attributes error Prevents the same attribute from appearing more than once on an element

vite.config.ts

import { defineConfig } from 'vite-plus';

export default defineConfig({
  lint: {
    ignorePatterns: ['dist/**', '.angular/**', 'node_modules/**']
  },
  staged: {
    '*.ts': 'vp lint --fix',
    '*.{json,html}': 'vp fmt',
    '*.html': 'eslint --fix',
    '*.{css,scss}': ['vp fmt', 'stylelint --fix']
  }
});
What each option does
Option Description
lint.ignorePatterns Glob patterns for folders excluded from all lint runs invoked through the vp CLI
staged Maps file-glob patterns to one or more commands that run against staged files during a pre-commit check
Staged glob breakdown
Glob Command(s) What happens
*.ts vp lint --fix Lint staged TypeScript files with oxlint and auto-fix what it can
*.{json,html} vp fmt Format staged JSON and HTML files with oxfmt
*.html eslint --fix Run Angular template ESLint rules on staged HTML files and auto-fix
*.{css,scss} vp fmt, then stylelint --fix Format staged style files with oxfmt, then run Stylelint auto-fix

A single file can match multiple globs. For example, a staged .html file is both formatted (vp fmt) and linted (eslint --fix).


scripts/pre-commit

#!/bin/sh
npx vp staged
What this script does
Line Description
#!/bin/sh Shebang that tells the OS to run this script with the POSIX shell
npx vp staged Invokes the vp staged command from vite-plus, which reads the staged section of vite.config.ts, resolves currently staged files, and runs the matching commands

This file is copied into .git/hooks/pre-commit by the postinstall script so that Git executes it automatically before every commit.


Step 3: Add scripts to package.json

{
  "scripts": {
    "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",
    "postinstall": "node -e \"const fs=require('fs');const src='scripts/pre-commit';const dst='.git/hooks/pre-commit';fs.mkdirSync('.git/hooks',{recursive:true});fs.copyFileSync(src,dst);if(process.platform!=='win32')fs.chmodSync(dst,0o755);\""
  }
}
What each script does
Script Command Description
lint vp lint Runs oxlint across the project and reports issues without modifying files
lint:fix vp lint --fix Runs oxlint and auto-fixes everything it can
lint:html eslint "src/**/*.html" Runs ESLint Angular template rules on all HTML files under src/
lint:styles stylelint "src/**/*.{css,scss}" Runs Stylelint on all CSS and SCSS files under src/
lint:styles:fix stylelint "src/**/*.{css,scss}" --fix Runs Stylelint with auto-fix on all CSS and SCSS files under src/
format vp fmt --check Checks formatting without writing changes; exits non-zero if files are unformatted
format:fix vp fmt Formats all files in place
postinstall node -e "..." Runs automatically after pnpm install; copies scripts/pre-commit into .git/hooks/pre-commit and sets executable permissions on non-Windows systems
How the postinstall hook installer works

The inline Node.js script does the following:

  1. Creates the .git/hooks/ directory if it does not exist (recursive: true).
  2. Copies scripts/pre-commit to .git/hooks/pre-commit.
  3. On non-Windows platforms, sets the file permission to 755 so Git can execute it.

This approach works across Windows, macOS, and Linux without requiring any additional tooling.


Step 4: Install dependencies and hook

pnpm install

This installs all packages and then runs the postinstall script, which copies the pre-commit hook into .git/hooks/.


Step 5: Test the flow

git add .
git commit -m "test: verify code quality pipeline"

If everything is configured correctly, the pre-commit hook will run all staged-file checks. If any check fails with unfixable errors, the commit will be blocked.


How Pre-commit Works

When you run git commit, Git looks for an executable file at .git/hooks/pre-commit. If found, it runs that file before creating the commit.

The hook runs:

npx vp staged

vp staged follows this sequence:

  1. Reads the staged section of vite.config.ts.
  2. Resolves the list of currently staged files from Git.
  3. Matches each staged file against the configured glob patterns.
  4. Runs the corresponding command(s) for each match.

If any command exits with a non-zero code (unfixable errors), the commit is blocked. You must fix the reported issues, re-stage the files, and commit again.


Commands You Can Run

Report only (no file changes)

Command What it checks
pnpm run lint TypeScript and JavaScript lint issues
pnpm run lint:html Angular HTML template lint issues
pnpm run lint:styles CSS and SCSS lint issues
pnpm run format Whether all files match the configured format

Auto-fix

Command What it fixes
pnpm run lint:fix Auto-fixable TypeScript and JavaScript lint issues
pnpm run lint:styles:fix Auto-fixable CSS and SCSS lint issues
pnpm run format:fix Reformats all files in place

Full project (all tools at once)

Command Description
npx vp check Runs all linters and format checks across the entire project
npx vp check --fix Same as above, with auto-fix enabled

Staged files only

Command Description
npx vp staged Runs checks only on files currently staged in Git

Customization

Disable a rule for one line

TypeScript:

// oxlint-disable-next-line no-console
console.log('Intentional log');

SCSS:

/* stylelint-disable-next-line color-named */
color: red;

HTML:

<!-- eslint-disable-next-line @angular-eslint/template/alt-text -->
<img src="decorative.png" />

Initial Cleanup for Existing Projects

For projects with many existing issues, run each step as a separate commit to keep the history clean.

Step 1 -- Format all files:

npx vp fmt

Step 2 -- Auto-fix lint issues:

npx vp lint --fix

Step 3 -- Auto-fix style issues:

npx stylelint "src/**/*.{css,scss}" --fix

After each step, review the changes, stage them, and commit before moving to the next step.


About

code quality config for angular and vite+(includes oxlint, eslint, stylelint) with pre-commit hook

Topics

Resources

Stars

Watchers

Forks

Contributors

Languages