← Blog
Frontend15 min czytania

epicweb-dev/config — gotowa konfiguracja Oxlint, Oxfmt i TypeScriptu

Jedna paczka zdejmująca dzień konfigurowania narzędzi jakości kodu w nowym projekcie — z linterem i formaterem od Oxc zamiast ESLinta i Prettiera, dziesięcioopcyjnym tsconfigiem i trzema własnymi regułami wymuszającymi jawne zarządzanie zasobami. Sprawdzamy, czego w tym zestawie brakuje (a brakuje reguł haków Reacta), oraz nietypowy problem licencyjny: plik licencji, do którego prowadzi odznaka, nie istnieje.

Każdy nowy projekt frontendowy zaczyna się tym samym rytuałem: linter, formater, konfiguracja TypeScriptu, uzgodnienie, czy używamy średników, i pół dnia na to, żeby narzędzia przestały się ze sobą kłócić. Przy jednym projekcie to strata czasu. Przy sześciu prowadzonych równolegle dla różnych klientów to coś gorszego — sześć rozjeżdżających się konfiguracji, z których każda jest trochę inna i żadna nie jest udokumentowana.

@epic-web/config (github.com/epicweb-dev/config) jest odpowiedzią na to w formie jednej paczki: gotowe ustawienia lintera, formatera i kompilatora, wraz z dokumentacją dlaczego są takie, a nie inne. Autorem jest Kent C. Dodds, a zestaw przeszedł niedawno zmianę, o której warto wiedzieć: ESLint i Prettier zostały w nim zastąpione przez Oxlint i Oxfmt.

Stan projektu

Dane z API GitHuba i npm na 9 października 2026:

  • 267 gwiazdek i 18 forków, repozytorium założone 24 maja 2024,
  • wersja 3.2.1 opublikowana 3 sierpnia 2026, wtedy też był ostatni push,
  • zero otwartych zgłoszeń,
  • i liczba, która przy paczce konfiguracyjnej mówi więcej niż gwiazdki: 69 220 pobrań z npm w ostatnim miesiącu.

Ten stosunek jest tu najciekawszy. Nikt nie gwiazdkuje paczki z konfiguracją — po prostu się ją instaluje i zapomina. Dwieście sześćdziesiąt siedem gwiazdek przy siedemdziesięciu tysiącach pobrań miesięcznie to profil narzędzia używanego, a nie obserwowanego.

Drobiazg porządkowy: opis repozytorium na GitHubie jest nieaktualny — mówi o ESLincie i Prettierze, choć opis w samej paczce brzmi już „Oxlint, Oxfmt i TypeScript". Tytuł w naszym planie był więc trafniejszy niż to, co widać na stronie projektu.

Licencja: nietypowy problem do sprawdzenia od razu

Zwykle omawiam licencję na końcu. Tutaj wyjątkowo trzeba na początku, bo znalazłem coś, czego nie widziałem w żadnym innym projekcie z tej serii.

W package.json licencja jest zadeklarowana jako MIT. README kończy się sekcją „License: MIT", a na górze wisi odznaka licencyjna prowadząca do /blob/main/LICENSE. Sprawdziłem zawartość katalogu głównego repozytorium: pliku LICENSE tam nie ma. Odnośnik z odznaki prowadzi w pustkę, a API GitHuba nie rozpoznaje żadnej licencji dla tego repozytorium.

Jak to ocenić uczciwie? Intencja jest niewątpliwa — deklaracja w metadanych paczki oraz dwa miejsca w README nie zostawiają wątpliwości, że to MIT, a narzędzia skanujące zależności czytają właśnie pole z package.json i zobaczą MIT. Praktyczne ryzyko jest więc bliskie zeru. Ale sama licencja MIT wymaga, żeby jej tekst towarzyszył kopiom oprogramowania — a tekstu w repozytorium nie ma. Przy formalnym przeglądzie zależności u klienta z procedurami jest to uwaga do zaraportowania i najprawdopodobniej do zamknięcia jednym zgłoszeniem do autora, nie powód do rezygnacji. Wspominam o tym, bo lepiej wiedzieć zawczasu niż tłumaczyć pod presją terminu.

Co jest w paczce

Zawartość jest zaskakująco oszczędna — pięć punktów wejścia i trzy zależności produkcyjne:

  • @epic-web/config/typescript — wspólny tsconfig.json do rozszerzenia,
  • @epic-web/config/oxlint — plik JSON z konfiguracją lintera,
  • @epic-web/config/oxfmt — preset formatera,
  • @epic-web/config/reset.d.ts — deklaracje poprawiające typy wbudowane,
  • oraz punkt główny udostępniający rozszerzenia lintera.

Zależności to @oxlint/plugins, @total-typescript/ts-reset i tslib, a Oxfmt jest zależnością równorzędną w zakresie od 0.45 do wersji poniżej 1 — czyli instaluje się go samodzielnie i świadomie.

TypeScript w dziesięciu opcjach

Cała konfiguracja kompilatora to dziesięć ustawień i warto ją zobaczyć w całości, bo jest dobrym punktem odniesienia dla własnego pliku:

{
  "compilerOptions": {
    "isolatedModules": true,
    "jsx": "react-jsx",
    "module": "preserve",
    "target": "ES2022",
    "strict": true,
    "skipLibCheck": true,
    "allowImportingTsExtensions": true,
    "noUncheckedIndexedAccess": true,
    "forceConsistentCasingInFileNames": true,
    "noEmit": true
  }
}

Dwie rzeczy zasługują na wyróżnienie. Pierwsza to noUncheckedIndexedAccess — opcja, która sprawia, że odczyt z tablicy albo obiektu po kluczu zwraca typ z możliwym undefined. Jest to jedyna opcja z tej listy, która realnie łapie błędy, a nie tylko porządkuje projekt: sięgnięcie po element, którego nie ma, przestaje być cichym problemem czasu wykonania. Bywa uciążliwa w istniejącym kodzie i to jest właśnie powód, żeby włączyć ją w nowym.

Druga to zestaw module: preserve, allowImportingTsExtensions, isolatedModules i noEmit, wyrażający jedną spójną decyzję: TypeScript sprawdza typy, a wynikowy kod produkuje narzędzie budujące. Kompilator nic nie emituje, a import z rozszerzeniem .ts jest dozwolony, bo i tak nie on rozwiązuje ścieżki.

Do tego dochodzi reset.d.ts, czyli jedna linia importu wciągająca poprawki typów wbudowanych — na przykład takie, po których JSON.parse przestaje zwracać any.

Oxfmt zamiast Prettiera — z uczciwym rachunkiem kosztów

Ta zmiana jest udokumentowana zapisem decyzji z 14 kwietnia 2026 i to jest dokument, który warto przeczytać niezależnie od tej paczki, bo pokazuje, jak taką decyzję opisać.

Powody: jeden dostawca narzędzi do lintowania i formatowania zamiast dwóch, jedno natywne narzędzie wiersza polecenia, sortowanie klas Tailwinda wbudowane natywnie zamiast osobnej wtyczki do Prettiera, oraz koniec z duplikowaniem list ignorowanych ścieżek między plikami konfiguracyjnymi różnych narzędzi.

Koszty — wypisane przez autora, nie przeze mnie:

  • wtyczki Prettiera nie są obsługiwane, więc to, co na którejś polegało, wymaga innego narzędzia albo rezygnacji,
  • wynik jest „prettierowaty", ale nie identyczny wszędzie — zespół powinien liczyć się z jednorazowym przeformatowaniem całego projektu,
  • część opcji i form komentarzy nie ma odpowiednika.

Styl, który dostajesz: szerokość 80 znaków, tabulatory (ze spacjami tylko w package.json), bez średników, apostrofy pojedyncze, przecinki na końcu wszędzie, sortowanie klas Tailwinda i nadpisania dla plików MDX.

Jest tu też szczegół techniczny wart zapamiętania przy pisaniu własnych paczek konfiguracyjnych: Oxfmt nie ma pola extends, więc preset się rozpakowuje i nadpisuje pola po nim:

import epicOxfmt from '@epic-web/config/oxfmt'
import { defineConfig } from 'oxfmt'

export default defineConfig({
  ...epicOxfmt,
  printWidth: 100,
})

I drugi, jeszcze mniej oczywisty: punkt wejścia presetu jest zwykłym plikiem .js, a nie TypeScriptem — świadomie, żeby Node nie musiał zdejmować typów z plików leżących w katalogu zależności. To jest ta klasa detali, których nie widać, dopóki nie zaczną kosztować sekund przy każdym uruchomieniu.

Oxlint zamiast ESLinta

Wcześniejszy zapis decyzji, z 26 marca 2026, uzasadnia porzucenie ESLinta czterema punktami: Oxlint jest dramatycznie szybszy, konfiguracja jest prostsza, gdy dostarcza się jeden łańcuch narzędzi, projekt ma aktywny zespół, a zgodność z wieloma identyfikatorami reguł ESLinta sprawia, że migracja nie wymaga przemyślenia zestawu reguł od nowa.

Konsekwencja tej zgodności jest widoczna w konfiguracji i bez wyjaśnienia bywa myląca: część reguł nadal ma identyfikatory w przestrzeni eslint/, bo tak Oxlint wystawia reguły zgodnościowe. ESLinta nie trzeba do tego instalować.

Użycie jest jednolinijkowe:

{
  "extends": ["./node_modules/@epic-web/config/oxlint-config.json"]
}

Jedno zastrzeżenie techniczne: reguły świadome typów — no-misused-promises i no-floating-promises, czyli dokładnie te, które łapią zapomniane await — wymagają w Oxlincie osobnej konfiguracji analizy typów. Nie działają „z pudełka" po samym rozszerzeniu presetu.

Co dokładnie jest w tym zestawie reguł

Przeczytałem plik konfiguracyjny lintera w całości i jest krótszy, niż można się spodziewać — cztery wtyczki (import, React, TypeScript, Vitest), lista ignorowanych ścieżek i zaledwie kilka reguł włączonych globalnie, z resztą rozdzieloną po nadpisaniach dla konkretnych typów plików. To jest bezpośrednia realizacja zasady „minimalny linter", zapisanej w jednym z zapisów decyzji.

Pięć wyborów zasługuje na wyróżnienie, bo każdy z nich jest decyzją, a nie ustawieniem domyślnym:

  • Komentarze FIXME są błędem, a TODO nie. Reguła ostrzegająca przed komentarzami sygnalizującymi problemy jest ustawiona na poziom błędu wyłącznie dla słowa FIXME, w dowolnym miejscu komentarza. Rozróżnienie jest znakomite: „zrobić kiedyś" wolno zostawić w kodzie, „to jest zepsute" — nie. Jedna linia konfiguracji zamienia zwyczaj zespołu w regułę sprawdzaną w CI,
  • Zakaz importowania plików testowych z kodu produkcyjnego. Wzorce obejmujące katalogi testów oraz pliki z rozszerzeniami testowymi są zablokowane z komunikatem wyjaśniającym, a w samych plikach testowych ta reguła jest wyłączona. Prosty i skuteczny bezpiecznik przed przypadkowym wciągnięciem atrapy albo pomocnika testowego do pakietu produkcyjnego,
  • Importy typów w formie wplecionej — dwie współpracujące reguły wymuszają zapis import { type Foo } zamiast osobnej linii importu typów, z automatyczną poprawką. Jedna decyzja mniej do podejmowania przy każdym pliku,
  • Nieużywane zmienne z konwencją nazw — ostrzeżenie pomija nazwy zaczynające się od podkreślenia albo od słowa oznaczającego świadome pominięcie. Czyli „nie użyłem tego celowo" jest wyrażalne w kodzie, a nie komentarzem wyłączającym regułę,
  • Wykrywanie porzuconych obietnic jest błędem, ale w wersji pragmatycznej — reguła pilnująca niedopilnowanych obietnic ma poziom błędu, natomiast sprawdzanie zwrotu typu pustego jest wyłączone. Bez tego każdy asynchroniczny obsługiwacz zdarzenia w Reakcie byłby zgłaszany, a reguła zostałaby wyciszona w tydzień.

Reguła pilnująca atrybutu key w listach JSX jest przy tym tylko ostrzeżeniem i tylko dla plików z JSX-em — co dobrze ilustruje ogólne podejście tego zestawu: błędem jest to, co psuje program, a ostrzeżeniem to, co psuje czytelność.

Czego w tym zestawie nie ma

To najważniejsza sekcja tego wpisu i chwała autorowi, że sam ją napisał. README ma podrozdział wyliczający rodziny reguł świadomie nieobjęte tą konfiguracją:

  • react-hooks/rules-of-hooks i react-hooks/exhaustive-deps,
  • import/order,
  • testing-library/* i jest-dom/*,
  • większość reguł vitest/* oraz playwright/*.

Dla zespołu pracującego w Reakcie te dwie pierwsze pozycje są kluczowe i trzeba je nazwać wprost: reguły haków to prawdopodobnie najbardziej wartościowa rodzina reguł, jaka istnieje w ekosystemie Reacta. Wywołanie haka warunkowo albo w pętli to błąd, którego nie widać w przeglądzie kodu, a który psuje aplikację w sposób trudny do zdiagnozowania. Nieobjęcie tej rodziny nie jest wadą paczki — jest granicą tego, co Oxlint dziś obejmuje — ale znaczy, że przy wdrożeniu w projekcie reactowym trzeba świadomie zdecydować, czym te reguły zastąpić.

Trzy własne reguły i teza o zasobach

Paczka dostarcza trzy reguły napisane jako wtyczki JavaScript do Oxlinta, a dwie z nich forsują jedną tezę: jawne zarządzanie zasobami przez using i await using jest lepsze niż ręczne sprzątanie.

epic-web/no-manual-dispose ostrzega przed ręcznym zwalnianiem zasobów tam, gdzie język ma na to składnię. Zamiast konstrukcji z blokiem finally:

let tempFile
try {
  tempFile = createTempFile()
} finally {
  tempFile?.dispose()
}

pisze się jedną linię, a zwolnienie zasobu dzieje się przy wyjściu z zakresu:

using tempFile = createTempFile()

await using db = await createDisposableDatabase()

epic-web/prefer-dispose-in-tests idzie dalej i to jest reguła najciekawsza koncepcyjnie: zgłasza jako ostrzeżenie użycie haków cyklu życia testu — odpowiedników naszych metod przygotowania i sprzątania w PHPUnicie — gdy sprzątanie mogłoby równie dobrze żyć w ciele testu jako zasób jednorazowy. Uzasadnienie jest przekonujące: przygotowanie i sprzątanie zostają w tym samym zakresie leksykalnym, a ukrytego zachowania globalnego jest mniej.

Doceniam przy tym, ile pracy poszło w unikanie fałszywych trafień. Reguła świadomie nie zgłasza haków w plikach konfiguracyjnych bez testów obok, haków korzystających z parametrów zwrotnych albo kontekstu, haków mutujących stan zewnętrzny, znanych haków od zegarów i atrap oraz wspólnego przygotowania w większych zestawach testów — z parametrem określającym, od ilu testów wspólne przygotowanie uznaje się za uzasadnione. To jest reguła napisana przez kogoś, kto wie, że ostrzeżenie krzyczące bez powodu zostanie wyłączone w tydzień.

Trzecia reguła, epic-web/no-prettier-ignore, jest pomocą migracyjną: ostrzega przed komentarzami wyłączającymi Prettiera i umie je automatycznie zamienić na odpowiednik dla Oxfmt — z zachowaniem starej formy tam, gdzie Oxfmt sam ją zaleca.

Dwanaście zapisów decyzji

Rzecz, którą warto skopiować niezależnie od tego, czy sięgniemy po tę paczkę. W katalogu dokumentacji leży dwanaście zapisów decyzji, każdy w formacie kontekst — decyzja — konsekwencje, z datą i statusem. Tytuły mówią same za siebie: minimalny ESLint, semantyczne wersjonowanie, paczki z typami, sortowanie importów, składnia modułów dosłowna, nawiasy w funkcjach strzałkowych, brak średników, spójność wielkości liter w nazwach plików, przejście na Oxlint, Oxfmt zamiast Prettiera.

Wartość tego jest podwójna. Po pierwsze, gdy ktoś w zespole zapyta „dlaczego bez średników", odpowiedź jest dokumentem, nie dyskusją. Po drugie, w zapisie o Oxfmt jest zdanie rozstrzygające typowy problem takich archiwów: starsze dokumenty wspominające Prettiera pozostają obowiązujące co do stylu formatowania, a nowa decyzja zmienia jedynie wybór narzędzia, które ten styl stosuje. Rozdzielenie „co" od „czym" w archiwum decyzji jest dokładnie tym, czego takim archiwom zwykle brakuje.

Pułapki

  • Brak pliku licencji w repozytorium przy zadeklarowanym MIT w metadanych paczki; odnośnik z odznaki jest martwy,
  • Brak reguł haków Reacta oraz porządkowania importów, reguł bibliotek testowych i Playwrighta — trzeba to pokryć osobno,
  • Reguły świadome typów wymagają dodatkowej konfiguracji analizy typów w Oxlincie,
  • Przejście na Oxfmt oznacza jednorazowe przeformatowanie projektu — commit, który dotknie każdego pliku, więc lepiej zrobić go osobno i przed zmianami merytorycznymi,
  • Wtyczki Prettiera przestają działać. Jeśli któraś jest w projekcie potrzebna, ta migracja nie jest dla tego projektu,
  • Oxfmt nie ma extends — nadpisania robi się przez rozpakowanie presetu, co przy nieuwadze łatwo zepsuć,
  • Paczka usunęła punkty wejścia dla ESLinta, więc przejście z wcześniejszych wersji jest zmianą łamiącą zgodność, nie aktualizacją,
  • Konfiguracja jest opiniotwórcza z założenia: tabulatory, brak średników, apostrofy pojedyncze. Jeśli zespół ma inne przyzwyczajenia, dyskusja o stylu wróci — tylko teraz o cudzych ustawieniach,
  • To jeden autor i jeden ekosystem. Zestaw odzwierciedla sposób pracy konkretnej osoby i konkretnego kursu; nie jest standardem branżowym.

Gdzie to ma sens w naszej pracy

  • Nowy projekt frontendowy — trzy pliki po kilka linii zamiast dnia konfigurowania. To jest główny i wystarczający powód,
  • Spójność między projektami klientów. Ta sama paczka w kilku repozytoriach znaczy, że osoba przechodząca między projektami nie zmienia nawyków, a przeglądy kodu nie schodzą na formatowanie,
  • Punkt odniesienia dla własnej konfiguracji. Nawet nie instalując tej paczki, dziesięć opcji TypeScriptu i lista reguł są dobrym materiałem do porównania z tym, co mamy — szczególnie noUncheckedIndexedAccess,
  • Wzorzec zapisów decyzji. Kilkanaście krótkich dokumentów z datą i statusem to najtańszy sposób, żeby ustalenia zespołu przestały żyć wyłącznie w pamięci osób, które przy nich były,
  • Pretekst do sprawdzenia Oxlinta i Oxfmt na jednym projekcie. Jeśli obietnica szybkości się potwierdzi, zysk jest odczuwalny przy każdym uruchomieniu w CI.

Podsumowanie

  • Trzy pliki konfiguracyjne po kilka linii zamiast dnia pracy w nowym projekcie,
  • Oxlint i Oxfmt zamiast ESLinta i Prettiera — jeden dostawca, natywne narzędzia, sortowanie klas Tailwinda bez wtyczki,
  • Dziesięć opcji TypeScriptu, z noUncheckedIndexedAccess jako jedyną łapiącą realne błędy,
  • Brak reguł haków Reacta — sprawdź to przed wdrożeniem w projekcie reactowym,
  • Trzy własne reguły forsujące using, w tym jedna zniechęcająca do haków cyklu życia w testach,
  • FIXME jest błędem, TODO nie, a importowanie plików testowych z kodu produkcyjnego jest zablokowane,
  • Migracja na Oxfmt to jednorazowe przeformatowanie i koniec z wtyczkami Prettiera,
  • Dwanaście zapisów decyzji — wzorzec wart skopiowania nawet bez tej paczki,
  • Siedemdziesiąt tysięcy pobrań miesięcznie przy 267 gwiazdkach — narzędzie używane, nie obserwowane,
  • Zadeklarowane MIT, ale bez pliku licencji w repozytorium — do zaraportowania przy formalnym audycie.

Licencja: paczka jest zadeklarowana jako MIT w polu licencji w package.json oraz w dwóch miejscach README, więc intencja autora nie budzi wątpliwości i wolno używać jej komercyjnie, modyfikować i redystrybuować na zwykłych, permisywnych warunkach. Narzędzia skanujące zależności odczytają MIT z metadanych paczki i przy typowym przeglądzie nic się nie wydarzy. Zostaje jednak formalna nieścisłość, którą opisałem na początku i którą powtarzam tutaj, bo należy do tej sekcji: w repozytorium nie ma pliku z tekstem licencji, mimo że odznaka w README do niego prowadzi, a sama licencja MIT wymaga, aby jej treść towarzyszyła kopiom oprogramowania. Praktyczne skutki są znikome — to raczej zaległość porządkowa niż spór o prawa — ale przy kliencie z formalną procedurą przeglądu zależności warto ją zawczasu odnotować i, jeśli komuś zależy, zgłosić autorowi. Osobno warto pamiętać, że licencja tej paczki nie rozciąga się na narzędzia, które konfiguruje: Oxlint i Oxfmt z projektu Oxc mają własne warunki, tak samo jak biblioteka poprawiająca typy wbudowane, dostarczana jako osobna zależność. Paczka jest zbiorem ustawień, nie dystrybucją narzędzi — i to jest przy audycie rozróżnienie warte wypowiedzenia na głos.