Kas gi tas Turborepo ir kodėl turėtum susirūpinti?
Jei dirbi su JavaScript ekosistema ir tavo projektas augo greičiau nei planuojai, tikriausiai jau susidūrei su monorepo problema. Turiu omeny tą akimirką, kai paprasta aplikacija virsta keliais paketais, bibliotekų rinkiniu ir daugybe tarpusavyje priklausomų dalių. Ir štai čia prasideda tikrasis linksmas gyvenimas – build’ai, kurie trunka amžinybę, cache’inimas, kuris neveikia taip, kaip tikėjaisi, ir CI/CD pipeline’ai, kurie ryja tavo biudžetą greičiau nei galėtum pasakyti „npm install”.
Turborepo atsirado kaip atsakas į šias problemas. Tai ne tiesiog dar vienas įrankis monorepo valdymui – tai build sistema, kuri iš tikrųjų supranta, kaip veikia tavo projektas. Sukūrė ją Vercel komanda (taip, tie patys žmonės, kurie davė mums Next.js), ir ji greitai tapo vienu populiariausių sprendimų šioje srityje.
Esmė paprasta: Turborepo žino, kurie paketai priklauso nuo kitų, ir gali paralelizuoti build procesus bei cache’inti rezultatus taip, kad kartojantys build’ai būtų ne tik greiti, bet ir žaibiškai greiti. Kalbame apie 85-95% laiko sutaupymą kai kuriose situacijose.
Kaip Turborepo veikia po gaubtu
Turborepo pagrindas yra task orkestravimas ir dependency graph’as. Kai paleidžiate turbo run build, sistema pirmiausiai išanalizuoja visą jūsų monorepo struktūrą. Ji peržiūri kiekvieno paketo package.json failus, supranta priklausomybes tarp jų ir sukuria grafiką, kuris parodo, kas nuo ko priklauso.
Tada prasideda magija. Vietoj to, kad build’intų viską nuosekliai (kaip daro tradiciniai įrankiai), Turborepo nustato, kurie paketai gali būti build’inami vienu metu. Pavyzdžiui, jei turite tris paketus A, B ir C, kur A ir B nepriklauso vienas nuo kito, bet abu reikalingi C – sistema build’ins A ir B paraleliai, o tada C.
Bet tikrasis žaidimo keitėjas yra cache’inimas. Turborepo seka ne tik failus, bet ir visą kontekstą: aplinkos kintamuosius, priklausomybių versijas, net git commit hash’us. Jei nieko nepasikeitė – kodėl build’inti iš naujo? Sistema tiesiog grąžina cache’intą rezultatą. Ir čia ne koks nors primityvus file-based cache – tai content-addressable storage, kuris veikia lokaliai IR gali būti dalijamasis su komanda per remote cache.
Praktinis setup žingsnis po žingsnio
Gerai, gana teorijos. Pažiūrėkim, kaip tai veikia praktikoje. Tarkime, turite egzistuojantį monorepo su Yarn workspaces ar npm workspaces (Turborepo puikiai veikia su abiem).
Pirmas žingsnis – įdiegti Turborepo:
npm install turbo --global
Arba, jei nenorite globaliai:
npm install turbo --save-dev
Dabar reikia sukurti turbo.json konfigūracijos failą projekto šakniniame kataloge. Štai paprastas pavyzdys:
{
"$schema": "https://turbo.build/schema.json",
"pipeline": {
"build": {
"dependsOn": ["^build"],
"outputs": ["dist/**", ".next/**"]
},
"test": {
"dependsOn": ["build"],
"outputs": []
},
"lint": {
"outputs": []
},
"dev": {
"cache": false
}
}
}
Čia vyksta keletas įdomių dalykų. "dependsOn": ["^build"] reiškia „pirma build’ink visas priklausomybes”. Simbolis ^ nurodo, kad tai taikoma dependencies, o ne task’ams tame pačiame pakete. outputs masyvas nurodo, kokius failus Turborepo turėtų cache’inti.
Pastebėjote "cache": false prie dev? Tai svarbu – development režimas neturėtų būti cache’inamas, nes norite matyti pakeitimus realiu laiku.
Pipeline konfigūracija ir dependency management
Pipeline konfigūracija – tai širdis ir siela Turborepo. Čia apibrėžiate, kaip jūsų task’ai sąveikauja tarpusavyje. Ir tai nėra taip paprasta, kaip gali atrodyti iš pirmo žvilgsnio.
Pavyzdžiui, tarkime turite tokią struktūrą:
– packages/ui – komponentų biblioteka
– packages/utils – utility funkcijos
– apps/web – Next.js aplikacija, kuri naudoja abu paketus
Jūsų turbo.json galėtų atrodyti taip:
{
"pipeline": {
"build": {
"dependsOn": ["^build"],
"outputs": ["dist/**", ".next/**", "build/**"]
},
"web#build": {
"dependsOn": ["^build"],
"env": ["NEXT_PUBLIC_API_URL"],
"outputs": [".next/**"]
},
"test": {
"dependsOn": ["build"],
"inputs": ["src/**/*.tsx", "src/**/*.ts", "test/**/*.ts"]
}
}
}
Atkreipkite dėmesį į web#build – tai specifinė konfigūracija tik web aplikacijai. Galite override’inti bendrus nustatymus konkretiems paketams. env laukas svarbus cache invalidation – jei pasikeičia NEXT_PUBLIC_API_URL, cache bus invalidated.
inputs laukas leidžia tiksliai nurodyti, kuriuos failus Turborepo turėtų stebėti. Jei pakeitimai įvyko tik README.md, o jūs nurodėte stebėti tik src katalogą – cache išliks validus.
Remote cache ir komandinis darbas
Vienas didžiausių Turborepo privalumų – remote cache galimybė. Įsivaizduokite: jūsų kolega jau build’ino tą patį kodą vakar. Kodėl jūs turėtumėte build’inti iš naujo? Su remote cache – neturite.
Vercel siūlo savo Turborepo Remote Cache sprendimą, bet galite naudoti ir kitus – AWS S3, Google Cloud Storage, ar net savo custom sprendimą. Setup su Vercel yra paprasčiausias:
npx turbo login
Tai atvers naršyklę ir paprašys autentifikuotis. Po to:
npx turbo link
Ir viskas – dabar jūsų cache dalijamasis su visa komanda. Kai kas nors push’ina kodą į CI/CD, build rezultatai cache’inami. Kai kitas developeris pull’ina tą kodą – jis gauna cache’intus rezultatus akimirksniu.
Bet atsargiai su sensityviais duomenimis! Jei jūsų build’ai generuoja failus su API raktais ar kitais secrets – įsitikinkite, kad jie nėra įtraukti į outputs. Arba dar geriau – naudokite aplinkos kintamuosius runtime, o ne build time.
Optimizavimas ir performance tuning
Gerai, turite veikiantį Turborepo setup. Bet kaip išspausti maksimalią performancą? Čia keletas trikų, kuriuos išmokau sunkiu būdu.
Pirma, --concurrency flag’as. Pagal nutylėjimą Turborepo naudoja visus CPU cores. Bet kartais tai ne optimaliausia. Jei turite daug I/O operacijų (pavyzdžiui, TypeScript kompiliacija), galite nustatyti didesnį concurrency:
turbo run build --concurrency=20
Taip, daugiau nei CPU cores. Skamba keistai, bet veikia, nes daug laiko praleidžiama laukiant I/O.
Antra, --filter flag’as. Kai dirbate tik su vienu paketu, nėra prasmės build’inti visko:
turbo run build --filter=web
Tai build’ins tik web paketą ir jo priklausomybes. Galite net naudoti wildcard’us:
turbo run build --filter=...web
Trys taškai reiškia „šis paketas ir visi, nuo kurių jis priklauso”. Vienas taškas kita kryptimi:
turbo run build --filter=ui...
Tai build’ins ui paketą ir visus, kurie nuo jo priklauso.
Trečia, .turbo katalogo valdymas. Lokalus cache gali užimti nemažai vietos. Galite periodiškai jį išvalyti:
rm -rf .turbo
Arba naudoti turbo prune komandą, kuri išvalo tik senus cache įrašus.
Integracijos su CI/CD sistemomis
Turborepo tikroji vertė atsiskleidžia CI/CD aplinkoje. Čia build’ai vyksta dažnai, ir kiekviena sutaupyta minutė reiškia sutaupytus pinigus ir greitesnį deployment.
GitHub Actions pavyzdys:
name: CI
on:
push:
branches: [main]
pull_request:
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/setup-node@v3
with:
node-version: 18
- name: Install dependencies
run: npm install
- name: Build
run: npx turbo run build
env:
TURBO_TOKEN: ${{ secrets.TURBO_TOKEN }}
TURBO_TEAM: ${{ secrets.TURBO_TEAM }}
TURBO_TOKEN ir TURBO_TEAM reikalingi remote cache. Juos gausite po turbo login.
Svarbus niuansas – cache hit rate. Galite pridėti step’ą, kuris reportuoja, kiek cache buvo panaudota:
npx turbo run build --summarize
Tai sugeneruos JSON failą su detaliais statistika. Galite jį parse’inti ir siųsti į monitoring sistemą.
GitLab CI setup panašus, tik sintaksė kita. Svarbu įsitikinti, kad node_modules ir .turbo katalogai yra cache’inami tarp job’ų – tai dar labiau paspartins procesą.
Tipinės problemos ir jų sprendimai
Nė viena technologija nėra tobula, ir Turborepo turi savo quirk’ų. Štai keletas problemų, su kuriomis greičiausiai susidursite.
**Cache invalidation problemos.** Kartais Turborepo nenustato, kad kažkas pasikeitė. Paprastai tai nutinka, kai naudojate external failus, kurių Turborepo neseka. Sprendimas – pridėti juos į inputs arba globalDependencies:
{
"globalDependencies": [".env", "tsconfig.json"]
}
**Lėti build’ai nepaisant cache.** Jei cache veikia, bet build’ai vis tiek lėti, problema greičiausiai ne Turborepo. Patikrinkite:
– Ar TypeScript incremental compilation įjungtas?
– Ar naudojate SWC vietoj Babel (jei įmanoma)?
– Ar jūsų dependency graph’as nėra per daug susipynęs?
**Remote cache neveikia.** Pirma, patikrinkite, ar TURBO_TOKEN validus. Antra, įsitikinkite, kad jūsų firewall neleidžia prisijungti prie Vercel serverių. Trečia, patikrinkite outputs konfigūraciją – jei ji neteisingai nustatyta, cache bus tuščias.
**Dependency cycles.** Jei turite circular dependencies tarp paketų, Turborepo gali nesugebėti sukurti validaus build order. Sprendimas – refaktorinti kodą ir pašalinti cycles. Naudokite madge ar panašius įrankius circular dependencies aptikimui.
Kai viskas sudėliojama į vietas
Turborepo nėra magic bullet, kuris išspręs visas jūsų monorepo problemas. Bet tai tikrai vienas geriausių įrankių, kurie šiuo metu egzistuoja šiai problemai spręsti. Jei jūsų projektas auga, build’ai lėtėja, ir CI/CD pradeda kainuoti daugiau nei norėtumėte – tikrai verta išbandyti.
Svarbiausias patarimas – pradėkite paprastai. Nereikia iš karto konfigūruoti remote cache, sudėtingų pipeline’ų ir custom task’ų. Pradėkite nuo bazinio setup’o, leiskite Turborepo išanalizuoti jūsų projektą ir pamatysite pirmųjų rezultatų. Paskui galite optimizuoti, pridėti remote cache, fine-tune’inti concurrency settings.
Ir nepamirškite – Turborepo dokumentacija yra tikrai gera. Jei kažkas neaišku, ten rasite atsakymus. Community taip pat aktyvi, ypač Discord serveryje. Monorepo valdymas niekada nebuvo paprastas, bet su tinkamais įrankiais jis tampa bent jau valdomas. O Turborepo tikrai yra vienas iš tų tinkamų įrankių.
