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:
| Scenariusz | Cold (bez cache) | Warm (remote cache hit) |
|---|---|---|
| Lint wszystkich pakietów | 2m 40s | 8s |
| Typecheck wszystkich pakietów | 4m 10s | 12s |
| Build wszystkich aplikacji | 11m 20s | 35s |
| Pełny pipeline (lint + type + build + test) | 22m | 1m 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