Skip to content

FabienDehopre/eslint-config

Repository files navigation

@fabdeh/eslint-config

CI NPM Version Netlify Status

  • Auto fix for formatting (aimed to be used standalone without Prettier)
  • Reasonable defaults, best practices, only one line of config
  • Designed to work with TypeScript, JSX, etc. Out-of-box.
  • Opinionated, but very customizable
  • ESLint Flat config, compose easily!
  • Automatic Angular, NGRX, TypeScript, Vitest support when the corresponding dependency is detected.
  • Optional formatters support for formatting CSS, HTML, XML, etc.
  • Style principle: Minimal for reading, stable for diff, consistent
  • Respects .gitignore by default
  • Requires ESLint v9.21.0+

Warning

Please keep in mind that this is a personal config with a lot opinions. Changes might not always be pleased by everyone and every use cases.

If you are using this config directly, I'd suggest you review the changes everytime you update. Or if you want more control over the rules, always feel free to fork it. Thanks!

Usage

Install

Run the command in your terminal:

pnpm add -D eslint @fabdeh/eslint-config

And create an eslint.config.mjs in you project root:

// eslint.config.mjs
import { defineConfig } from '@fabdeh/eslint-config';

export default defineConfig();

Add script for package.json

For example:

{
  "scripts": {
    "lint": "eslint . --fix",
    "lint:ci": "eslint ."
  }
}

IDE Support (auto fix on save)

🟦 VS Code support

Install VS Code ESLint extension

Add the following settings to your .vscode/settings.json:

{
  // Disable the default formatter, use eslint instead
  "prettier.enable": false,
  "editor.formatOnSave": false,

  // Auto fix
  "editor.codeActionsOnSave": {
    "source.fixAll.eslint": "explicit",
    "source.organizeImports": "never"
  },

  // Silence the style rules in you IDE, but still fix them automatically
  "eslint.rules.customizations": [{ "rule": "@stylistic/*", "severity": "off", "fixable": true }],

  // Enable eslint for all supported languages
  "eslint.validate": [
    "css",
    "html",
    "javascript",
    "javascriptreact",
    "json",
    "jsonc",
    "json5",
    "less",
    "markdown",
    "scss",
    "typescript",
    "typescriptreact",
    "toml",
    "yaml",
    "xml"
  ]
}

Customization

Since the beginning, we used ESLint Flat config. It provides much better organization and composition.

Normally you only need to import the defineConfig function:

// eslint.config.js
import { defineConfig } from '@fabdeh/eslint-config';

export default defineConfig();

And that's it! Or you can configure each integration individually, for example:

// eslint.config.js
import { defineConfig } from '@fabdeh/eslint-config';

export default defineConfig({
  // Enable stylistic formatting rules
  // stylistic: true,

  // Or customize the stylistic rules
  stylistic: {
    indent: 2, // 4, or 'tab'
    quotes: 'single', // or 'double'
  },

  // Angular and NgRx are autodetected, you can also explicitly enable them:
  angular: true,
  ngrx: true,

  // Disable vitest
  vitest: false,

  // `.eslintignore` is no longer supported in Flat config, use `ignores` instead
  ignores: [
    '**/fixtures',
    // ...globs
  ],
});

The defineConfig factory function also accepts any number of arbitrary custom config overrides:

// eslint.config.js
import { defineConfig } from '@fabdeh/eslint-config';

export default defineConfig(
  {
    // Configure the fabdeh's config
  },

  // From the second arguments they are ESLint Flat Configs
  // you can have multiple configs
  {
    files: ['**/*.ts'],
    rules: {},
  },
  {
    rules: {},
  }
);

Going more advanced, you can also import fine-grained configs and compose them as you wish:

Advanced Example

We wouldn't recommend using this style in general unless you know exactly what they are doing, as there are shared options between configs and might need extra care to make them consistent.

// eslint.config.js
import {
  angular,
  comments,
  ignores,
  imports,
  javascript,
  jsdoc,
  ngrx,
  stylistic,
  tailwindcss,
  typescript,
  unicorn,
  vitest,
} from '@fabdeh/eslint-config';
import tseslint from 'typescript-eslint';

export default tseslint.config(
  ignores(),
  javascript(/* Options */),
  comments(),
  jsdoc(),
  imports(),
  unicorn(),
  typescript(/* Options */),
  stylistic(),
  angular(),
  ngrx(),
  vitest(),
  tailwindcss()
);

Check out the configs and factory for more details.

Thanks to antfu/eslint-config for the inspiration and reference.

Rules Overrides

All the rules are always bound to one or more file extensions (via minimatch pattern. i.e.: */.?([cm])[jt]s?(x) for all JS and TS file types including JSX syntax). If you want to override the rules, you need to specify the file extension:

// eslint.config.js
import { defineConfig } from '@fabdeh/eslint-config';

export default defineConfig(
  {
    typescript: true,
    vitest: true
  },
  {
    // Remember to specify the file glob here, otherwise it might cause the vitest plugin to handle non-spec files
    files: ['**/*.spec.?([cm])[jt]s', '**/*.test.?([cm])[jt]s'],
    rules: {
      'vitest/consistent-test-it': ['error', { fn: 'it' }],
    }
  },
  {
    // Without `files`, they are general rules for all files
    rules: {
      '@stylistic/semi': ['error', 'never']
    }
  }
);

We also provide the overrides option in each integration to make it easier:

// eslint.config.js
import { defineConfig } from '@fabdeh/eslint-config';

export default defineConfig({
  typescript: {
    overrides: {
      '@typescript-eslint/consisten-type-definitions': ['error', 'interface'],
    },
  },
  angular: {
    tsOverrides: {
      '@angular-eslint/prefer-signals': 'off',
    },
    htmlOverrides: {
      '@angular-eslint/template/no-any': 'warn',
    },
  },
  yaml: {
    overrides: {
      // ...
    }
  }
});

Auto-detected integrations

The following integrations are automatically enabled if the corresponding package is installed in your project:

TypeScript

Most of TypeScript rules are enable automatically if typescript package is installed in you project. Some @typescript-eslint rules are also enabled by default for JavaScript files. You can explicitly enable/disable TypeScript integration manually:

// eslint.config.js
import { defineConfig } from '@fabdeh/eslint-config';

export default defineConfig({
  typescript: true,
});
Erasable Syntax Only

The TypeScript integration also allow you to turn on/off rules that will report on using syntax that will not be allowed by TypeScript's --erasableSyntaxOnly option:

Recently, Node.js 23.6 unflagged experimental support for running TypeScript files directly; however, only certain constructs are supported under this mode.

...

TypeScript 5.8 introduces the --erasableSyntaxOnly flag. When this flag is enabled, TypeScript will only allow you to use constructs that can be erased from a file, and will issue an error if it encounters any constructs that cannot be erased.

You can enable these rules as follow:

// eslint.config.js
import { defineConfig } from '@fabdeh/eslint-config';

export default defineConfig({
  typescript: {
    enableErasableSyntaxOnly: true,
  },
});

These rules are disabled by default and therefore the associated ESLint plugin is also not installed by default. Running pnpx eslint should prompt you to install the required plugin; otherwise, you can install it manually:

pnpm add -D eslint-plugin-erasable-syntax-only

Angular

Angular support is detected automatically by checking if @angular/core is installed in your project. You can also explicitly enable/disable it:

// eslint.config.js
import { defineConfig } from '@fabdeh/eslint-config';

export default defineConfig({
  angular: true,
});

NgRx

NgRx support is also detected automatically if any of the following package is installed in your project.

  • @ngrx/store
  • @ngrx/effects
  • @ngrx/signals
  • @ngrx/operators

As the Angular integration is can be explicitly enabled/disabled:

// eslint.config.js
import { defineConfig } from '@fabdeh/eslint-config';

export default defineConfig({
  ngrx: true,
});

Of course, NgRx depends on Angular so the Angular integration will be enabled as well.

Vitest

The vitest integration is detected automatically by checking if vitest is installed in your project. It can be enable/disable manually in the configuration:

// eslint.config.js
import { defineConfig } from '@fabdeh/eslint-config';

export default defineConfig({
  vitest: true,
});

Optional integrations

We provide some optional integrations for specific use cases, that we don't include their dependencies by default.

Formatters

Use external formatters to format files that ESLint cannot handle yet (.css, .html, etc). Powered by eslint-plugin-format.

// eslint.config.js
import { defineConfig } from '@fabdeh/eslint-config';

export default defineConfig({
  formatters: {
    /**
     * Format CSS, LESS, SCSS files
     */
    css: true,
    /**
     * Format HTML files
     */
    html: true,
    /**
     * Format Markdown files
     */
    markdown: true,
  }
});

Running npx eslint should prompt you to install the required dependencies; otherwise, you can install them manually:

pnpm add -D eslint-plugin-format

Lint Staged

If you want to apply lint and auto-fix before every commit, you can add the following to your package.json:

{
  "simple-git-hooks": {
    "pre-commit": "pnpm nano-staged"
  },
  "nano-staged": {
    "*": "eslint --fix"
  }
}

and then

pnpm add -D nano-staged simple-git-hooks

# then, to activate the hooks
pnpm simple-git-hooks

Versioning Policy

This project follows Semantic Versioning for releases. However, since this is just a config and involves opinions and many moving parts, we don't treat rules changes as breaking changes.

Changes Considered as Breaking Changes

  • Node.js version requirement changes
  • Huge refactors that might break the config
  • Plugins made major changes that might break the config
  • Changes that might affect most of the codebases

Changes Considered as Non-breaking Changes

  • Enable/disable rules and plugins (that might become stricter)
  • Rules options changes
  • Version bumps of dependencies

License

MIT License © 2025-PRESENT Fabien Dehopré