Skip to content
Sprinx
Powrót do Bloga
TypeScriptMonorepoTurborepoDevOps

Monorepa TypeScript z Turborepo: Lekcje z Prawdziwych Projektów

P
Patryk Jankowiak
Founder & Engineer, Sprinx
7 min czytania
Udostępnij ten artykuł
Monorepa TypeScript z Turborepo: Lekcje z Prawdziwych Projektów

W ciągu ostatniego roku przeniosłem trzy projekty klienckie z osobnych repo do monorepa Turborepo. Dwa poszły dobrze. Jeden nauczył mnie drogich lekcji. Ten artykuł to skondensowana wersja tego, czego się nauczyłem — konkretnie tych części, które łatwo przeoczyć, aż ugryzą Cię w CI o 2 w nocy.

Samo Turborepo jest świetne. Pułapką jest założenie, że domyślne ustawienia zrobią właściwą rzecz w realnym projekcie. Nie zrobią. Musisz świadomie zdecydować się na poprawność.

Dlaczego przestałem używać Nx

Nx jest potężniejsze, ale też bardziej opiniotwórcze. Generatory, system pluginów, graf projektu — świetne, kiedy żyjesz w świecie Nx, męczące, kiedy musisz zrobić coś niestandardowego. Turborepo jest dokładnie odwrotne: małe, szybkie i nie przeszkadza. Dla zespołów, które mają już lubiany setup buildów (Vite, tsup, Next.js), Turborepo dodaje cache i orchestrację tasków bez wymuszania przepisywania. To pasowało do mojej sytuacji, więc się przesiadłem.

Trzy zasady, których żałuję, że nie znałem wcześniej

  • Zdefiniuj globy inputs jawnie dla każdego taska. Domyślne "hashuj wszystko w pakiecie" jest jednocześnie za szerokie (łamie cache, kiedy edytujesz README) i niebezpiecznie wąskie dla tasków zależnych od configów w roocie.
  • Włącz remote caching od pierwszego dnia, ale schowaj go za sprawdzeniem środowiska. Współdzielony cache z niepoprawnymi inputs jest gorszy niż brak cache — serwuje stare buildy każdemu deweloperowi w zespole.
  • Trzymaj współdzielone pakiety małe i skupione. Pakiet shared-ui, który re-eksportuje wszystko, jest wzmacniaczem rebuildów: jedna zmiana unieważnia każdą aplikację, która go importuje, nawet te, które nie używają zmienionego komponentu.

Cold vs warm CI

Na ostatnim projekcie z sześcioma aplikacjami i dwunastoma współdzielonymi pakietami tak wyglądały realne czasy po poprawnej konfiguracji cache:

ScenariuszCold (bez cache)Warm (remote cache hit)
Lint wszystkich pakietów2m 40s8s
Typecheck wszystkich pakietów4m 10s12s
Build wszystkich aplikacji11m 20s35s
Pełny pipeline (lint + type + build + test)22m1m 45s

Ten wskaźnik trafień w cache to cały sens. PR ruszający jeden komponent przebudowuje się teraz w mniej niż dwie minuty zamiast blokować deweloperów na dwadzieścia. W sześcioosobowym zespole, w którym każdy pushuje kilka razy dziennie, zwraca się w pierwszym tygodniu.

Monorepo ze zepsutym cache to po prostu wolniejsze polyrepo. Cache nie jest optymalizacją — cache jest produktem.

Co zrobiłbym inaczej

  • Skonfigurowałbym globy inputs zanim napiszę pierwszy współdzielony pakiet. Naprawianie tego później to audyt każdej granicy hashu w grafie.
  • Napisałbym ADR dokumentujący, które pakiety żyją w roocie, a które pod apps. Ta granica jest najtrudniejsza do zmiany po sześciu miesiącach.
  • Przypiąłbym wersję Turborepo w package.json i w obrazie CI. Zachowanie między minor wersjami zmieniało mi się w zaskakujący sposób.
  • Trzymałbym jedno źródło prawdy dla tsconfig i eslint w roocie, rozszerzane przez każdy pakiet. Kopiowane configi jako pierwsze się rozjeżdżają.

Krótko: Turborepo to świetne narzędzie, jeśli rozumiesz, co tak naprawdę cache'uje i dlaczego. Jeśli migrujesz do monorepa i chcesz drugiego spojrzenia przed zobowiązaniem się do struktury — robiłem to trzy razy i chętnie podzielę się listą pułapek na rozmowie.

Więcej kontekstu o szerszych tradeoffach architektonicznych w moim wpisie o mikroserwisach vs monolicie, a jeśli chcesz omówić konkretną migrację — napisz do mnie.

Potrzebujesz pomocy z Twoim projektem?

Porozmawiajmy o Twoich wymaganiach technicznych. Oferuję bezpłatną konsultację, podczas której omówimy architekturę, stos technologiczny i harmonogram.

Zobacz moje usługi