eduardweb.
TypeScriptAvansat#performance#typescript#monorepo#tsconfig

Cum să nu-ți urăști viața când configurezi tsconfig în monorepo

De Elena Dumitrescu, 30 iun. 2026 · 17 vizualizări · 3 like-uri

Postat 30 iun. 2026
json
// packages/shared/tsconfig.json
{
  "compilerOptions": {
    "composite": true,
    "declaration": true,
    "declarationMap": true,
    "outDir": "./dist",
    "rootDir": "./src"
  }
}

// apps/api/tsconfig.json
{
  "compilerOptions": {
    "outDir": "./dist",
    "rootDir": "./src"
  },
  "references": [
    { "path": "../../packages/shared" }
  ]
}

Am văzut zeci de echipe care trec la monorepo și primul lucru pe care-l fac e să trântească niște paths în root-ul tsconfig.json ca să rezolve importurile dintre pachete. Pare că merge, IDE-ul e fericit, dar la primul build de CI începe coșmarul. TSC compilează totul de la zero de fiecare dată, iar timpul de build explodează.

La un proiect cu vreo 15 pachete interne (microservicii și librării de utilitare), am reușit să scădem timpul de build pe CI cu aproape 65% (de la 4 minute la vreo 80 de secunde) doar organizând corect referințele de proiect. TypeScript nu e doar un linter mai deștept, e un compilator care știe să facă build incremental dacă îi dai instrucțiunile corecte.

Capcana din "paths"

Când folosești doar compilerOptions.paths pentru a face legătura între pachete, păcălești compilatorul. Îi spui practic: „când vezi importul ăsta, caută direct în sursele celuilalt pachet”.

Sună bine în teorie, dar asta înseamnă că TypeScript va recompila librăria shared de fiecare dată când compilezi proiectul principal. Dacă ai 5 aplicații care depind de acea librărie, o vei compila de 5 ori. Plus că pierzi complet delimitarea fizică a modulelor; dacă modifici ceva în librărie, TSC nu știe să reconstruiască doar diferențele.

Salvarea: "composite": true și Project References

Soluția corectă, dar ignorată pentru că necesită puțin boiler-plate, este mecanismul de Project References introdus în TS 3.0.

Fiecare sub-proiect (pachet) din monorepo trebuie să aibă propriul său tsconfig.json cu opțiunea "composite": true activată. Această opțiune îi spune compilatorului trei lucruri esențiale:

  1. Produce fișiere .d.ts (declarații) și sourcemaps pentru ele.
  2. Generează un fișier .tsbuildinfo care ține minte graful de dependințe și ce s-a compilat deja.
  3. Forțează proiectul să fie un modul de sine stătător, interzicând importurile dubioase din afara rădăcinii lui fără referință explicită.

Apoi, în proiectul consumator, în loc de paths, adaugi un array de references care pointează spre folderele dependințelor.

Trade-off-ul sincer

Nimic nu e gratis. Setup-ul ăsta vine cu un cost de mentenanță destul de enervant. Trebuie să declari dependințele în trei locuri diferite: în package.json (pentru managerul de pachete), în tsconfig.json la references (pentru compiler) și uneori în bundler (Webpack/Vite/esbuild). Dacă uiți să adaugi o referință în tsconfig, te trezești cu erori ciudate de tipul "is not under 'rootDir'".

De asemenea, rularea build-ului nu se mai face cu un simplu tsc, ci cu tsc --build (sau tsc -b). Acest flag îi spune lui TypeScript să analizeze graful de referințe, să verifice ce s-a schimbat de la ultimul build (folosind fișierele .tsbuildinfo) și să compileze doar ce este strict necesar, în ordinea corectă a dependențelor.

Pentru proiecte mici, cu 2-3 pachete, s-ar putea ca overhead-ul de configurare să nu merite. Dar de la 5 pachete în sus, devine diferența dintre a livra în producție în 2 minute sau în 15 minute.

Voi cum gestionați asta? Vă bateți cu configurarea nativă din TS sau ați delegat totul către tool-uri externe gen Turborepo ori Nx?

Răspunsuri 0

Se încarcă răspunsurile…

Loghează-te pentru a răspunde

Doar membrii comunității pot lăsa comentarii.