@gabegabegabe/eslint-config
@gabegabegabe/eslint-config
Gabe's personal ESLint configs
A collection of shareable ESLint flat configs, tuned to a
specific, modern toolchain: ESLint v10 flat config, ESM, typescript-eslint
strictness, and per-framework opinions. They are published for anyone to use,
but make no attempt to accommodate stacks other than the author's.
The configs are assembled from small composable layers. You normally spread a single leaf (and optionally a few overlays); the layers are the building blocks underneath.
Usage
Install the package, ESLint, and the peers your chosen config needs (see Requirements):
bun add -D @gabegabegabe/eslint-config eslint globals
Then create an eslint.config.js and spread the one leaf that matches what you
are building:
import config from '@gabegabegabe/eslint-config/typescript-node';
export default config;
Each leaf is a flat-config array, so add your own overrides (and any overlays) by spreading:
import config from '@gabegabegabe/eslint-config/typescript-node';
import test from '@gabegabegabe/eslint-config/test';
export default [
...config,
...test,
{
name: 'my-project/overrides',
rules: {
'no-console': 'off'
}
}
];
Which config do I extend?
Pick by what you are building. Spread exactly one leaf. There is no library/app split — ESLint does not emit, so a library and an app lint identically; environment (Node / browser / Bun) and framework are the only axes.
| You are building | Spread |
|---|---|
| A Node library, CLI, or service in JavaScript | ./javascript-node |
| A Node library, CLI, or service in TypeScript | ./typescript-node |
| A browser library or app in JavaScript | ./javascript-browser |
| A browser library or app in TypeScript | ./typescript-browser |
| A React library or app in JavaScript (JSX) | ./javascript-react |
| A React library or app in TypeScript (TSX) | ./typescript-react |
| A Vue library or app | ./vue |
| A Svelte library or app | ./svelte |
| A Bun service, CLI, or app | ./bun |
Notes:
vue,svelte, andbunare TypeScript-only (no JavaScript variant): Vue'svue/block-langalready enforces<script lang="ts">, Svelte 5 is TypeScript-first, and Bun's purpose is running TypeScript directly. Plain-Node, browser, and React projects come in bothjavascript-*andtypescript-*flavours because plain-JS and JSX-without-TS projects are real.- The TypeScript leaves enable
typescript-eslint'sprojectService, so the type-checked rules auto-discover your nearesttsconfig.jsonwith no extra wiring. Make sure the files you lint are covered by thattsconfig; setparserOptions.tsconfigRootDirin your own config if discovery needs a hint. - Inside every TypeScript leaf, type-checked rules are scoped to TS files and
turned off for JS files, so a mixed
.ts/.jsrepo (e.g. a TS project with.jsconfig files) Just Works.
Overlays
Overlays are composed alongside a leaf. They are files-scoped and
additive.
| Overlay | Compose with | Role |
|---|---|---|
./test |
any leaf | Test-file rules for bun:test (its API mirrors Jest's) |
./test-vitest |
any leaf | Test-file rules for Vitest — Vue/Svelte SFC component tests bun:test can't transform |
./json |
any leaf | Lints .json / .jsonc / .json5 via the official @eslint/json |
./vue-pug |
./vue |
Adds <template lang="pug"> support (Pug tokenizer + Pug-aware rules) |
The test and test-vitest overlays self-scope to test files
(**/*.{test,spec}.*, **/__tests__/**); their typed rules apply only to
TypeScript test files when composed alongside a TypeScript leaf. bun:test is
the default (./test); compose ./test-vitest in projects that also run Vitest
— for example, Vue/Svelte component tests, which bun:test cannot transform.
Example configs
TypeScript Node project, with tests and JSON:
import config from '@gabegabegabe/eslint-config/typescript-node';
import json from '@gabegabegabe/eslint-config/json';
import test from '@gabegabegabe/eslint-config/test';
export default [...config, ...test, ...json];
TypeScript browser app:
import config from '@gabegabegabe/eslint-config/typescript-browser';
import test from '@gabegabegabe/eslint-config/test';
export default [...config, ...test];
React (TypeScript):
import config from '@gabegabegabe/eslint-config/typescript-react';
import test from '@gabegabegabe/eslint-config/test';
export default [...config, ...test];
Vue (with Pug templates):
import config from '@gabegabegabe/eslint-config/vue';
import pug from '@gabegabegabe/eslint-config/vue-pug';
import test from '@gabegabegabe/eslint-config/test';
export default [...config, ...pug, ...test];
Bun:
import config from '@gabegabegabe/eslint-config/bun';
import test from '@gabegabegabe/eslint-config/test';
export default [...config, ...test];
Architecture
There are three tiers.
Leaves (top-level — spread exactly one)
A leaf is a self-contained flat-config array that bakes in (a) the correct
internal composition order and (b) files-scoping, so you never have to
wrangle either. The leaves are javascript-node, typescript-node,
javascript-browser, typescript-browser, javascript-react,
typescript-react, vue, svelte, and bun.
For example, typescript-node composes (left → right, later overrides earlier):
base → node → javascript → typescript → typescript-disable-type-checked (JS-scoped)
Overlays (top-level — compose alongside a leaf)
test, test-vitest, json, and vue-pug (see Overlays). Each
is files-scoped so it only touches the files it should.
Layers (./layers/* — power-user building blocks)
The primitives the leaves are built from: ./layers/base,
./layers/javascript, ./layers/typescript,
./layers/typescript-disable-type-checked, ./layers/browser, ./layers/node,
./layers/bun, ./layers/vue, ./layers/vue-typescript, ./layers/react, and
./layers/svelte. Compose these yourself only if no leaf fits. The root (.)
export aliases ./layers/base.
Conventions
When adding or changing a config, follow these invariants:
- Defer to each plugin's upstream
recommended(andstrict/stylistic) config first; compose this package's opinions last. - Preserve the rule-promotion pattern. When a base rule has a plugin
equivalent (e.g.
@typescript-eslint/*,vue/*,svelte/*), disable the base rule and enable the prefixed version with the same configuration. - Severity is always the numeric constants
DISABLED/WARN/ERRORfromlib/eslint-enforcement-codes.js— never string literals. - Leaves own composition order and
files-scoping so consumers never wrangle either; the ordering in each leaf is load-bearing. - Be explicit; scope precisely. Type-checked rules only on TS files; test rules only on test files; framework rules only on framework files.
- Every flat-config object has a
namefor the ESLint config inspector. - Tabs for indentation; no rule conflicts with Prettier.
Requirements
- ESLint
^10andglobals^17are required by every config. - Node
^20.19.0 || ^22.13.0 || >=24— ESLint 10's supported range, declared inengines. eslint-plugin-react,eslint-plugin-react-hooks, andeslint-plugin-jsx-a11yrun on ESLint 10 but do not yet declare it in their peer ranges, so the React leaves emit a harmless peer-dependency warning on install (Bun does not hard-fail on it).- Each leaf/overlay needs its plugin peers installed (all are optional peers, so you only install what you use):
| Config | Additional peer packages |
|---|---|
./javascript-node, ./javascript-browser |
@eslint/js, @eslint-community/eslint-plugin-eslint-comments, eslint-plugin-promise |
./javascript-react |
the above + eslint-plugin-react, eslint-plugin-react-hooks, eslint-plugin-jsx-a11y |
./typescript-node, ./typescript-browser, ./bun |
the JavaScript set + typescript-eslint, typescript |
./typescript-react |
the TypeScript set + eslint-plugin-react, eslint-plugin-react-hooks, eslint-plugin-jsx-a11y |
./vue |
the TypeScript set + eslint-plugin-vue, vue-eslint-parser |
./svelte |
the TypeScript set + eslint-plugin-svelte, svelte-eslint-parser |
./test |
eslint-plugin-jest |
./test-vitest |
@vitest/eslint-plugin |
./json |
@eslint/json |
./vue-pug |
eslint-plugin-vue-pug — use the flat-config build: eslint-plugin-vue-pug@^1.0.0-alpha |
The @eslint/js, @eslint-community/eslint-plugin-eslint-comments, and
eslint-plugin-promise peers are needed by every leaf, because every leaf
includes the javascript layer.
Development
bun run lint # Lint this repo with its own configs
bun run format # Format with Prettier (@gabegabegabe/prettier-config)
bun run format:check # Check formatting without writing
bun run check-updates # Check for dependency updates
There are no tests; the repo lints and formats itself (prepublishOnly runs
both format:check and lint). See CLAUDE.md for the internal architecture
and the rules for adding or changing a config.