Zapisujesz plik, Prettier go formatuje, a ESLint natychmiast zgłasza błąd w linii, którą Prettier właśnie dotknął. Oba narzędzia są skonfigurowane, oba działają — i zamiast pomagać, walczą ze sobą.

Te dwa narzędzia robią różne rzeczy. Problem w tym, że oba mają zdanie na temat jednej sprawy: jak kod ma wyglądać na ekranie.

Co ESLint faktycznie sprawdza

ESLint rozkłada kod na abstrakcyjne drzewo składniowe i uruchamia reguły na jego strukturze. Największa wartość pochodzi z reguł, które wyłapują realne bugi: zmienną, która nigdy nie została zdefiniowana, nieosiągalny return po innym return, nieużywany import, który został po refaktoryzacji, zduplikowany klucz w obiekcie, który bez ostrzeżenia nadpisuje pierwszy. To kontrole semantyczne — ESLint czyta, co kod robi.

ESLint zawiera też reguły stylistyczne, jak wymuszanie pojedynczych cytatów czy średników i limit długości linii — choć od wersji 8.53 są uznawane za przestarzałe na rzecz osobnego projektu ESLint Stylistic. Nie wyłapują bugów, tylko różnice w stylu. I właśnie ta część zderza się z Prettierem.

Co faktycznie robi Prettier

Prettier nie czyta kodu, żeby zrozumieć jego sens. Parsuje go, odrzuca oryginalne formatowanie i przepisuje cały plik według własnych reguł wcięć, długości linii, stylu cytatów i miejsc łamania długich linii. Uruchom go dwa razy na tym samym wejściu i dostaniesz identyczny wynik co do bajtu — to jest cel. Praktycznie nie ma nic do konfigurowania: jeden .prettierrc, garstka opcji. Każdy plik w projekcie wygląda tak samo, niezależnie od tego, kto go napisał.

Uruchom prettier --check . w CI i powie, czy każdy plik zgadza się z tym kanonicznym formatowaniem. Nie wie i nie obchodzi go, czy kod działa.

Dlaczego ESLint i Prettier wchodzą w konflikt

Pokrywają się tylko w jednym miejscu: reguły stylistyczne ESLint (cytaty, średniki, wcięcia, przecinki końcowe) obejmują ten sam obszar co Prettier. Czasem się nie zgadzają. Kiedy się nie zgadzają, dostajesz pętlę z pierwszego akapitu: Prettier formatuje linię, ESLint zgłasza wynik, “naprawiasz” ręcznie, Prettier przywraca, jak było.

Przestań więc oczekiwać od ESLinta decyzji o formatowaniu. Pozwól mu robić tylko to, czego Prettier nie może: wyłapywać bugi i złe wzorce. eslint-config-prettier to konfiguracja, która wyłącza każdą regułę ESLinta mogącą kolidować z Prettierem lub powtarzać jego wynik. Nic nie dodaje. Tylko wyłącza.

Drugi pakiet, eslint-plugin-prettier, idzie w odwrotną stronę: uruchamia Prettiera jako reguła ESLinta, więc każda różnica w formatowaniu pojawia się jako osobny błąd ESLinta. Dokumentacja samego Prettiera opisuje taki setup jako “zasadniczo nierekomendowany”. Jest wolniejszy, bo ESLint wywołuje Prettiera dla każdego pliku na nowo. Jest też głośniejszy: jedno przeformatowanie może zmienić się w dziesiątki błędów ESLinta zamiast jednego diffa. Zamiast tego uruchom Prettiera samodzielnie.

Jak skonfigurować ESLint i Prettier z flat config

Od ESLinta 9 plikiem konfiguracyjnym jest eslint.config.js, płaska tablica obiektów konfiguracyjnych, która zastępuje starszy format .eslintrc. Jeśli zaczynasz projekt od zera:

1
npm init @eslint/config@latest

To przeprowadzi cię przez kilka pytań i zapisze eslint.config.js. Potem dodaj Prettiera i konfigurację, która wycisza kolidujące reguły:

1
npm install --save-dev --save-exact prettier eslint-config-prettier

eslint-config-prettier dostarcza punkt wejścia dla flat config. Zaimportuj go i umieść jako ostatni element tablicy. Późniejsze wpisy nadpisują wcześniejsze, więc wszystko umieszczone po nim może ponownie włączyć wyłączoną regułę:

1
2
3
4
5
6
7
8
// eslint.config.js
import js from "@eslint/js";
import eslintConfigPrettier from "eslint-config-prettier/flat";

export default [
  js.configs.recommended,
  eslintConfigPrettier,
];

Dodaj .prettierrc dla własnych opcji Prettiera (większość zespołów nie potrzebuje prawie żadnych):

1
2
3
4
{
  "semi": true,
  "singleQuote": true
}

Dodaj też dwa skrypty w package.json, żeby oba narzędzia działały niezależnie:

1
2
3
4
5
6
{
  "scripts": {
    "lint": "eslint .",
    "format": "prettier --write ."
  }
}

eslint-config-prettier dostarcza też komendę CLI, która wypisuje każdą regułę w twojej konfiguracji wciąż kolidującą z Prettierem:

1
npx eslint-config-prettier eslint.config.js

Kod wyjścia 0 oznacza brak konfliktów. 2 oznacza, że jakieś znalazł, a wynik nazywa każdą regułę, żebyś mógł ją usunąć.

Używanie eslint-config-prettier z typescript-eslint

W projekcie TypeScript typescript-eslint podłącza się do tej samej tablicy flat config i dodaje własne reguły semantyczne na szczycie zwykłego ESLinta — tseslint.configs.recommended zabrania any wpisanego bezpośrednio w kodzie (no-explicit-any). Wyłapanie any, który przeciekł z nietypowanego wywołania biblioteki, to osobny, cięższy krok: przełącz się na recommendedTypeChecked, który wymaga parserOptions.project wskazującego na twój tsconfig.json, żeby reguły mogły pytać kompilator TypeScript o rzeczywiste typy. Reguła kolejności się nie zmienia: eslint-config-prettier zawsze idzie na końcu.

1
2
3
4
5
6
7
8
9
import js from "@eslint/js";
import tseslint from "typescript-eslint";
import eslintConfigPrettier from "eslint-config-prettier/flat";

export default [
  js.configs.recommended,
  ...tseslint.configs.recommended,
  eslintConfigPrettier,
];

Żadne z tych dwóch narzędzi nie sprawdza, czy twoje typy są dobrze zaprojektowane. Generic, który powinien być ograniczony extends, przechodzi obie kontrole — to zadanie kompilatora, opisane w Generics w TypeScript Wyjaśnione z Przykładami.

Uruchamianie ESLinta i Prettiera w edytorze i w CI

W edytorze włącz “format on save” dla Prettiera, a ESLintowi pozwól zgłaszać się osobno, przy zapisie albo w hooku pre-commit. Gdy powyższe pliki konfiguracyjne już istnieją, rozszerzenia edytora dla obu narzędzi działają niezależnie. Nie trzeba łączyć jednego z drugim.

W CI uruchom oba jako osobne kroki:

1
2
npx eslint .
npx prettier --check .

Nieudane prettier --check znaczy, że ktoś nie uruchomił formattera. Nieudane eslint znaczy, że kod ma realny problem. Dwa kroki sprawiają, że ta różnica jest widoczna w logu CI.

Częste błędy w konfiguracji ESLinta i Prettiera

Najczęstszy to instalowanie eslint-plugin-prettier, bo jakiś stary tutorial tak radzi, a potem zastanawianie się, czemu eslint . jest wolny i lista problemów jest trzy razy dłuższa niż oczekiwano. Usuń plugin, zostaw eslint-config-prettier, uruchamiaj Prettiera samodzielnie.

Drugi to kolejność. Jeśli wspólna konfiguracja albo override specyficzny dla projektu znajduje się w tablicy po eslintConfigPrettier, ponownie włącza regułę, która była wyłączona — bez ostrzeżenia. Ta komenda wyłapuje to, zanim trafi do PR.

Trzeci to zakres. Ta para jest zbudowana dla JavaScriptu i TypeScriptu. Jeśli zaczynasz nowy projekt i wolisz jedno, szybsze narzędzie robiące obie rzeczy, spójrz na Biome. To inny kompromis, nie zamiennik tego setupu.

Co zrobić dzisiaj w swoim projekcie

Otwórz swój package.json. Jeśli jest tam eslint-plugin-prettier, odinstaluj go. Dodaj eslint-config-prettier jako ostatni wpis w eslint.config.js, uruchom npx eslint-config-prettier eslint.config.js i napraw to, co wskaże. Potem każdy błąd ESLinta, który zobaczysz, dotyczy czegoś, co ESLint znalazł w strukturze kodu, a nie linii, którą Prettier i tak by przeformatował. Wciąż nie wyłapuje wszystkiego: zapomnienie await w pętli albo nieobsłużona promise rejection to błąd logiki, nie błąd lintowania, chyba że dodasz do tego odpowiedni plugin — zobacz Async/Await i Event Loop w JavaScript Wyjaśnione, jak wyglądają takie błędy.

Powiązane artykuły