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ólnytsconfig.jsondo 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
FIXMEsą błędem, aTODOnie. Reguła ostrzegająca przed komentarzami sygnalizującymi problemy jest ustawiona na poziom błędu wyłącznie dla słowaFIXME, 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-hooksireact-hooks/exhaustive-deps,import/order,testing-library/*ijest-dom/*,- większość reguł
vitest/*orazplaywright/*.
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
noUncheckedIndexedAccessjako 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, FIXMEjest błędem,TODOnie, 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.