eduardweb.
TypeScriptAvansat#performance#typescript#monorepo#tsconfig

De ce îți crapă TypeScript în monorepo și cum îl repari cu Project References

De Ioana Marinescu, 9 iul. 2026 · 15 vizualizări · 2 like-uri

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

// apps/web-app/tsconfig.json
{
  "compilerOptions": {
    "target": "ES2022",
    "module": "NodeNext"
  },
  "references": [
    { "path": "../../packages/shared" }
  ]
}

Am avut anul trecut un proiect cu vreo 12 pachete interne și în jur de 80k linii de cod în TypeScript. La început, un build complet pe CI dura aproape 3 minute, iar VS Code-ul își dădea duhul când încercam să navighez prin tipurile definite în alte pachete din monorepo. Pur și simplu îngheța editorul sau îmi arăta erori de tipul "Any" deși tipul exista bine mersi. Atunci mi-am băgat picioarele și am stat o duminică întreagă să înțeleg cum funcționează de fapt arhitectura de TS în monorepo.

Capcana din paths

Cea mai comună greșeală pe care o văd (și pe care am făcut-o și eu) este să folosești doar proprietatea paths din tsconfig.json ca să legi pachetele între ele.

Sună simplu: pui @my-repo/shared/*: ["packages/shared/src/*"] și gata.

Dar asta e doar o iluzie. paths este doar un alias pentru compiler și bundler. Nu îi spune lui TypeScript că există o relație de dependență reală între pachete. Când rulezi tsc în aplicația principală, compilerul va încerca să compileze și fișierele din shared direct în contextul aplicației. Asta înseamnă că nu beneficiezi de cache, iar timpii de build cresc exponențial cu fiecare pachet adăugat.

Soluția reală: composite și references

Ca să facem lucrurile corect, fiecare pachet trebuie să fie un proiect TypeScript de sine stătător, iar proiectul principal doar să le „ofere credit” prin references.

Pentru asta, în pachetul partajat (packages/shared/tsconfig.json), trebuie să activăm modul composite. Acest flag obligă TypeScript să genereze fișiere .d.ts de definire și fișiere .tsbuildinfo pentru build-ul incremental. Practic, îi spune compilerului: „Tratează-mă ca pe o librărie pre-compilată”.

În proiectul consumator (de exemplu, o aplicație Next.js sau un API de Express), în loc de alias-uri chioare, adăugăm o referință directă către folderul pachetului partajat.

Compromisul sincer: merită efortul?

Nimic nu e gratis în backend/frontend architecture.

Pe de o parte, am economisit în jur de 65% din timpul de build pe CI (am scăzut de la acele 3 minute la vreo 60 de secunde) pentru că TS nu mai recompila pachetul shared dacă nu modificasem nimic în el. VS Code-ul a început să zboare instantaneu la definiții.

Pe de altă parte, mentenanța devine mai grea. Dacă ai 15 pachete, trebuie să scrii manual aceste referințe în fiecare tsconfig.json implicat. Dacă uiți o singură referință, compilerul o să-ți dea niște erori absolut criptice despre fișiere care nu fac parte din structura de proiect. De asemenea, ești obligat să folosești tsc --build (sau tsc -b) în loc de tsc simplu la rădăcină, altfel TypeScript va ignora referințele.

Pentru un proiect mic cu două pachete, sincer, nu merită bătaia de cap. Rămâi pe alias-uri simple. Dar dacă ai trecut de 3-4 pachete și echipa crește, Project References devine singura cale prin care îți păstrezi mințile întregi.

Voi cum gestionați asta? Ați trecut pe bundlere gen Turborepo/Nx care se ocupă singure de orchestrat build-ul, sau mai folosiți direct tsc -b?

Răspunsuri 0

Se încarcă răspunsurile…

Loghează-te pentru a răspunde

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