npm.io
15.0.0 • Published 5d ago

@gabegabegabe/eslint-config

Licence
GPL-3.0-or-later
Version
15.0.0
Deps
0
Size
89 kB
Vulns
0
Weekly
0

@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, and bun are TypeScript-only (no JavaScript variant): Vue's vue/block-lang already 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 both javascript-* and typescript-* flavours because plain-JS and JSX-without-TS projects are real.
  • The TypeScript leaves enable typescript-eslint's projectService, so the type-checked rules auto-discover your nearest tsconfig.json with no extra wiring. Make sure the files you lint are covered by that tsconfig; set parserOptions.tsconfigRootDir in 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/.js repo (e.g. a TS project with .js config 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:

  1. Defer to each plugin's upstream recommended (and strict/stylistic) config first; compose this package's opinions last.
  2. 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.
  3. Severity is always the numeric constants DISABLED / WARN / ERROR from lib/eslint-enforcement-codes.js — never string literals.
  4. Leaves own composition order and files-scoping so consumers never wrangle either; the ordering in each leaf is load-bearing.
  5. Be explicit; scope precisely. Type-checked rules only on TS files; test rules only on test files; framework rules only on framework files.
  6. Every flat-config object has a name for the ESLint config inspector.
  7. Tabs for indentation; no rule conflicts with Prettier.

Requirements

  • ESLint ^10 and globals ^17 are required by every config.
  • Node ^20.19.0 || ^22.13.0 || >=24 — ESLint 10's supported range, declared in engines.
  • eslint-plugin-react, eslint-plugin-react-hooks, and eslint-plugin-jsx-a11y run 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.