ADR-004: Monorepo mit Bun Workspaces
Warum Owlat ein Monorepo mit Bun Workspaces und Turborepo einsetzt — für schnelle Installationen, atomare paketübergreifende Änderungen und gecachte CI.
- Status: Angenommen
- Datum: 2024-06-15
Kontext
Owlat besteht aus mehreren miteinander verbundenen Teilen: einem Nuxt-Frontend, einem Convex-Backend, einer Dokumentationsseite, einer E-Mail-Builder-Komponente, einer E-Mail-Renderer-Bibliothek, gemeinsamen Typen und veröffentlichbaren SDKs. Diese Packages teilen sich Typen und Utilities in großem Umfang — so hängen etwa sowohl der E-Mail-Builder als auch der E-Mail-Renderer für die Block-Typdefinitionen von @owlat/shared ab.
Die wesentlichen Optionen:
- Getrennte Repositories — jedes Package in einem eigenen Repo, gemeinsamer Code über veröffentlichte npm-Packages. Saubere Grenzen, aber langsame Iteration: Die Änderung eines gemeinsamen Typs erfordert Veröffentlichen, Versionsanhebungen und das Aktualisieren der Consumer.
- Monorepo mit npm-/pnpm-Workspaces — Code an einem Ort, Abhängigkeiten per Symlink. Gut etabliert, aber langsamere Installationen und komplexeres Tooling.
- Monorepo mit Bun Workspaces — dasselbe Workspace-Modell, aber mit Buns schneller Installation, nativer TypeScript-Ausführung und Turbo für die Task-Orchestrierung.
Entscheidung
Ein Monorepo mit Bun Workspaces und Turborepo für die Task-Orchestrierung einsetzen. Alle Apps und Packages liegen in einem einzigen Repository unter apps/ und packages/.
Struktur:
apps/web — @owlat/web (Nuxt frontend)
apps/api — @owlat/api (Convex backend)
apps/docs — @owlat/docs (Nuxt + @nuxt/content)
packages/shared — @owlat/shared (types, utilities)
packages/email-builder — @owlat/email-builder
packages/email-renderer — @owlat/email-renderer
packages/sdk-js — @owlat/sdk-js
packages/ui — @owlat/ui
Dies ist ein repräsentativer Ausschnitt; das Repo enthält darüber hinaus weitere Apps (apps/mta, apps/marketing, apps/imap, apps/updater, apps/setup-cli und mehr) sowie weitere Packages. Zu beachten: packages/sdk-java ist ein Java-SDK und wird explizit aus den Bun Workspaces ausgenommen (!packages/sdk-java in workspaces.packages der Root-package.json).
Konsequenzen
Ermöglicht:
- Atomare paketübergreifende Änderungen — eine Typänderung in
sharedund ihre Consumer gehen in einem einzigen PR raus - Schnelle Installationen — Buns Linker ist bei der Workspace-Auflösung deutlich schneller als npm/pnpm
- Turbo-Caching — Build-Ausgaben werden gecacht (der Task
buildinturbo.jsondeklariertoutputs), sodassbun run buildunveränderte Packages überspringt. Tests laufen ungecacht: Der Tasktestsetzt"cache": false. - Eine einzige CI-Pipeline —
.github/workflows/test.yml(Workflow-Name „CI“) führt alle Workspace-Tests aus, über eine Matrix pro Workspace, diebun run --cwd <workspace> test:coveragefür jeden Workspace unabhängig aufruft - Gemeinsames Tooling — eine
oxlintrc.json, eineoxfmtrc.json, eine Turbo-Konfiguration - Changesets für die SDK-Veröffentlichung —
@changesets/cliverwaltet die Versionierung der veröffentlichbaren Packages
Abwägungen:
- Alle Mitwirkenden benötigen eine Bun-Installation (nicht nur Node)
- Große
bun.lock-Datei, die bei Abhängigkeitsänderungen Merge-Konflikte verursachen kann - Turbo fügt eine Build-Orchestrierungsschicht hinzu, die neue Mitwirkende verstehen müssen
- Die CI führt alle Workspace-Prüfungen aus, selbst bei Änderungen an einem einzelnen Package. Build-Caching hilft, doch Tests sind ungecacht; der Test-Job dämpft die Kosten mit einer Matrix pro Workspace in
.github/workflows/test.yml, sodass Workspaces parallel statt sequenziell laufen