Ein Workspace mit sechs Projekten und je drei Aufgaben hat 13 Aufgaben (bei uns). Bei jeder Änderung alle zu laufen, wäre in der CI bald zu langsam. Nx löst das mit zwei Mitteln: dem Cache (was schon berechnet wurde, wird nicht neu berechnet) und affected (was von der Änderung nicht berührt ist, wird gar nicht erst angefasst). Die Lektion zeigt beides an dem Workspace aus 0101 und 0102 und skizziert, wie eine Pipeline es nutzt.
Stand der Prüfung. Ausgeführt (Node 24.22.3, Nx 24.0.6, Angular 22.2, Vitest 4.1.12, Wegwerf-Workspace außerhalb des Repositorys):
nx run-many -t lint test buildohne und mit Cache,nx affected -t lint test build --base=HEAD~1 --head=HEADmit--explainund ohne; die Ausgaben stehen so da, wie sie gedruckt wurden (Pfade und Dauer gekürzt). Gelesen: die Nx-Seiten „Cache task results“, „Run only tasks affected by a PR“, „Remote cache“ und das Beispiel für Azure Pipelines (nur als Zusammenfassung des Seitentextes; den YAML-Wortlaut haben wir nicht gesehen). Nicht ausgeführt: jede Pipeline (GitHub Actions, Azure DevOps), Nx Cloud, ein selbst betriebener Cache,nx migrate. Offen: Preise und Bedingungen von Nx Cloud (die Seite nennt keine), die Bedienung eines selbst betriebenen Caches (nur die Seite „Self-hosted remote cache“ ist genannt, nicht gelesen).
Die Aufgabenpipeline
Eine Aufgabe (build, test, lint) hat bei Nx drei Eigenschaften, die für Cache und Affected zählen:
inputs: die Dateien und Werte, von denen das Ergebnis abhängt. Sie bilden den Hash. Benannte Eingaben (namedInputsinnx.json) fassen sie zusammen:defaultmeint alles im Projekt,productionlässt Testdateien und die Lint-Konfiguration weg.outputs: was die Aufgabe erzeugt und der Cache wiederherstellt (beibuildder Ausgabeordner).dependsOn: Aufgaben, die vorher laufen müssen."^build"heißt: erstbuildder Projekte, von denen dieses abhängt.
Wie Nx sie für unser Projekt kontakte-ui sieht, zeigt nx show project kontakte-ui --json. Bei uns waren lint und test zwischenspeicherbar (cache: true); die Eingabe von test enthält default, ^production (die Produktionsdateien der Abhängigkeiten), die Datei tsconfig.spec.json, die externe Abhängigkeit vitest und den Wert der Umgebungsvariable CI.
Das letzte ist lehrreich: Ein Test, den Sie lokal laufen lassen, hat einen anderen Hash als derselbe Test mit gesetztem CI, und die Ergebnisse teilen sich nicht.
Nach der Seite zum Cache gehören zum Hash, was ein Projekt an Quelldateien und Abhängigkeiten hat, relevante Workspace-Konfiguration, Versionen externer Pakete, Laufzeitwerte wie Betriebssystem und CPU-Architektur und die Argumente des Befehls. Quelltexte selbst speichert Nx nicht, nur ihre Hashes; ein Eintrag enthält die Terminalausgabe, die Dateien aus outputs und den Hash.
Ein Cache setzt eine Aufgabe ohne Nebenwirkungen voraus: Ein E2E-Test gegen einen laufenden Server gehört laut Seite nicht hinein.
Den Cache beweisen
Lassen Sie alles einmal laufen und dann ein zweites Mal, ohne etwas zu ändern:
npx nx run-many -t lint test build
npx nx run-many -t lint test build
Der erste Lauf endete bei uns mit Exit 0 und der Zeile „Cache: 0/13 hit (0%)“ (nach einer Änderung von tsconfig.base.json; ein Lauf mit teilweise warmem Cache zeigte vorher „4/13 hit (31%)“). Der zweite endete mit Exit 0 in 61 Millisekunden:
√ nx run kontakte-ui:test [local cache]
√ nx run kladde-buero:build:production [local cache]
√ nx run kontakte-ui:lint [existing outputs match the cache, left as is]
NX Successfully ran targets lint, test, build for 6 projects
Nx read the output from the cache instead of running the command for 13 out of 13 tasks.
Run duration: 61ms
Cache: 13/13 hit (100%)
Zwei Marken beweisen den Treffer: der Zusatz [local cache] oder [existing outputs match the cache, left as is] an der Aufgabe und die Zusammenfassung „13/13 hit“. Die Nx-Seite beschreibt keine feste Marke; wir lesen sie aus der Ausgabe dieser Fassung. Ein Cache-Treffer ist keine Garantie, dass der Test grün ist, sondern dass er mit denselben Eingaben grün war.
Das ist der Sinn, aber auch die Grenze: Was Ihr Test außerhalb der Eingaben liest (Uhrzeit, Netz, Datei außerhalb des Projekts), ändert den Hash nicht.
Wann der Cache fehlt. Wir änderten strict in tsconfig.base.json: danach 0 von 13 Treffern, denn die Wurzelkonfiguration ist Eingabe aller Aufgaben. Wir änderten einen Kommentar in shared-util-datum: nur dessen lint und test liefen neu (Abschnitt unten). nx reset leert Cache und Daten des Daemons; den Ort des lokalen Caches nennt die gelesene Seite nicht, das haben wir nicht ermittelt.
Affected
nx affected -t <Aufgaben> führt nur aus, was eine Änderung berühren kann. Nach der Nx-Seite: Nx fragt Git nach den geänderten Dateien, wählt jede Aufgabe, deren Eingaben eine geänderte Datei enthalten, wählt Aufgaben, die die Ausgaben einer gewählten Aufgabe lesen, und führt sie samt Abhängigkeiten aus (unveränderte Abhängigkeiten kommen aus dem Cache).
Die Auswahl hängt von Eingaben ab, nicht vom Aufgabengraphen: Ein dependsOn allein macht eine Aufgabe nicht betroffen. Deshalb sind richtige inputs und outputs auch bei Aufgaben nötig, die nicht gecacht werden.
Ab Nx 24 aufgabenbasiert. Bis Nx 23 löste eine Änderung an einem Projekt alle Aufgaben dieses Projekts und aller seiner Abhängigen aus (projektbasiert). Nx 24 wählt einzelne Aufgaben. Ein Workspace, der mit nx migrate auf 24 gehoben wird, behält laut Seite das alte Verhalten über "affected": { "legacy": true } in nx.json; wer die Einstellung entfernt oder auf false setzt, bekommt das neue. Unser Workspace entstand mit Nx 24 und hatte keine solche Einstellung.
Die Probe. Wir committeten eine kleine Änderung in libs/kontakte/ui (der Name der Karte bekam eine Klasse) und fragten mit --explain, was betroffen wäre; --explain führt nichts aus:
npx nx affected -t lint test build --base=HEAD~1 --head=HEAD --explain
NX 7 out of 13 lint, test and build tasks are affected
Changing 1 file touches 3 lint tasks, 3 test tasks and 1 build task. Pass --verbose to list each with its reasons.
libs/kontakte/ui/src/lib/kontakt-karte.ts:
- kladde-buero:build:production
- kladde-buero:lint
- kladde-buero:test
- kontakte-feature-liste:lint
- kontakte-feature-liste:test
- and 1 more lint task and 1 more test task
Ohne --explain liefen genau diese sieben Aufgaben (Exit 0, „Cache: 0/7 hit“, 18,6 Sekunden): kontakte-ui selbst, die Feature-Bibliothek und die Anwendung. Die drei anderen Bibliotheken liefen nicht, termine-data-access, kontakte-data-access und shared-util-datum haben kontakte-ui nicht importiert. Das ist der Gewinn: Der Graph aus Lektion 0102 entscheidet, wer neu läuft.
Zwei weitere Commits: Ein Kommentar in shared-util-datum betraf mit --explain „2 out of 13“ (dessen lint und test; keine Anwendung importiert es bisher). Eine Zeile in der README.md im Wurzelordner ergab „Nothing affected“. Das liegt an den Eingaben: Die README ist in keiner inputs-Liste.
Gegenstück: Eine Änderung der Sperrdatei betrifft nach der Seite standardmäßig alle Aufgaben; mit projectsAffectedByDependencyUpdates auf "auto" nur die, deren aufgelöste Abhängigkeiten sich ändern (nicht ausgeführt).
Der Lauf in der CI
In der CI gibt es keinen „aktuellen Zustand der Platte gegen den letzten Commit“, sondern einen Vergleich zweier Commits. Die Seite sagt: Standard für --base ist der Hauptzweig, für --head der Stand des Dateisystems; in der CI geben Sie beides ausdrücklich an (--base=origin/main --head=<Zweig>) oder setzen NX_BASE und NX_HEAD. Empfohlen ist als Basis der letzte erfolgreiche Commit auf dem Hauptzweig, damit alles seit dem letzten grünen Lauf eingeschlossen ist.
Voraussetzung ist die ganze Historie: Ohne fetch-depth: 0 (GitHub) beziehungsweise fetchDepth: '0' (Azure DevOps) gibt es keinen Basis-Commit zum Vergleichen.
GitHub Actions (Skizze, nicht ausgeführt). Für GitHub nennt die Seite die Aktion nrwl/nx-set-shas, die NX_BASE und NX_HEAD aus dem letzten erfolgreichen Lauf des Hauptzweigs setzt. Fassungen der Aktionen siehe Seite; welche Eingabe sie für Ihren Hauptzweig braucht, haben wir nicht gelesen: offen.
steps:
- uses: actions/checkout@<Fassung>
with:
fetch-depth: 0
- uses: nrwl/nx-set-shas@<Fassung>
- run: npm ci
- run: node tools/boundary-probe.mts
- run: npx nx affected -t lint test build
Azure DevOps (Skizze, nicht ausgeführt). Nach unserer Zusammenfassung des Nx-Beispiels checkt die Pipeline mit fetchDepth: '0' aus, rechnet BASE_SHA und HEAD_SHA aus (bei Pull Requests der Merge-Base mit dem Zielzweig, sonst der letzte erfolgreiche Build, Rückfall HEAD~1) und ruft npx nx affected --base=$(BASE_SHA) --head=$(HEAD_SHA) -t lint test build auf. Eigene Skizze dazu, ohne die Berechnung der Basis:
steps:
- checkout: self
fetchDepth: '0'
- script: npm ci
- script: node tools/boundary-probe.mts
- script: npx nx affected --base=$(BASE_SHA) --head=$(HEAD_SHA) -t lint test build
Zwei Dinge gehören in jede Pipeline: der Exit-Code zählt (ein roter Lint-Lauf muss die Pipeline rot machen, sonst sind die Grenzen aus 0102 Wünsche), und die Probe boundary-probe.mts läuft als eigener Schritt. npm ci statt npm install installiert genau das, was die Sperrdatei sagt.
Entfernter Cache
Der lokale Cache liegt auf Ihrer Maschine. In der CI hat jeder Lauf eine frische Maschine, also ist er leer, es sei denn, jemand teilt ihn. Die Seite „Remote cache“ nennt zwei Wege: Nx Cloud (Verbindung mit npx nx@latest connect; teilt Ergebnisse zwischen Entwicklungsrechnern und CI) und einen selbst betriebenen Cache-Server gegen eine OpenAPI-Beschreibung (Seite „Self-hosted remote cache“, nicht gelesen).
Welche Speicher dafür möglich sind, nennt die Seite nicht. Preise und Lizenzbedingungen von Nx Cloud stehen dort nicht: offen, vor dem Einsatz bei Nx nachlesen. Beachten Sie, was ein geteilter Cache bedeutet: Ergebnisse, die jemand anderes erzeugt hat, laufen auf Ihrer Maschine ungeprüft; Zugriffsrechte und Vertrauen gehören zur Entscheidung (ein ADR, K4 4.1.3).
Konventionen für Commits
Ein Monorepo mit vielen Projekten profitiert von lesbaren Commits, denn aus ihnen entstehen später Versionen und Änderungsprotokolle (Lektion 0302). Conventional Commits schreiben vor, dass jede Nachricht mit einem Typ beginnt, optional mit Geltungsbereich: feat(kontakte-ui): add card class, fix(shared-util-datum): ..., chore(deps): ..., docs: ..., test: ..., ci: .... Als Bereich bietet sich der Projektname an, den Nx kennt. In den Lektionen K1 bis K3 steht das Thema nicht (der Plan verwies auf eine K2-Lektion, die es nicht gibt); wir erklären es hier nur in diesem Umfang. Die Spezifikation haben wir nicht gelesen: offen.
Übung 1
Einen Cache-Treffer erzeugen und beweisen.
- Führen Sie in Ihrem Workspace
npx nx run-many -t lint test buildaus; Exit-Code lesen. - Führen Sie denselben Befehl noch einmal aus, ohne etwas zu ändern.
- Ändern Sie einen Wert in
tsconfig.base.json(und lassen Sie ihn geändert; ein Zurücksetzen ergäbe wieder den alten Hash) und führen Sie den Befehl erneut aus. - Notieren Sie je Lauf die Zeile mit „Cache:“ und die Dauer in
docs/cache.md.
So prüfen Sie: Lauf 1 hat 0 Treffer oder nur teilweise, Lauf 2 „13/13 hit“ (bei Ihnen die Zahl Ihrer Aufgaben) mit „Nx read the output from the cache“, Lauf 3 wieder 0 Treffer. Alle drei enden mit exit=0.
Lösung 1
- Lauf 2 hat keine Treffer: Etwas ändert sich zwischen den Läufen (eine Datei wird erzeugt, die
inputseiner Aufgabe enthalten sie).nx show project <name> --jsonzeigt dieinputs. - Lauf 2 hat Treffer, aber der Test lief nicht „wirklich“: erwartet; er war mit denselben Eingaben schon grün.
testmeldet beim ersten Lauf Exit 1 „No test files found“: Siehe Lektion 0102 (passWithNoTests).- Lauf 3 hat weiter Treffer: Die geänderte Datei ist in keiner Eingabe.
tsconfig.base.jsonist es bei uns gewesen (0 von 13).
Übung 2
Affected: ein Commit in einer Bibliothek.
- Committen Sie den Stand. Ändern Sie dann eine Datei in
libs/kontakte/ui/src(zum Beispiel eine Klasse im Template) und committen Sie mitfeat(kontakte-ui): add name class. - Führen Sie
npx nx affected -t lint test build --base=HEAD~1 --head=HEAD --explainaus und notieren Sie, wie viele von wie vielen Aufgaben betroffen sind und welche Projekte darunter sind. - Führen Sie ohne
--explainaus und lesen Sie den Exit-Code. - Ändern Sie danach nur die
README.md, committen Sie mitdocs: update readmeund fragen Sie erneut mit--explain.
So prüfen Sie: Bei Schritt 2 steht kontakte-ui selbst, die Feature-Bibliothek und die Anwendung darunter, aber nicht termine-data-access; Schritt 3 endet mit exit=0; Schritt 4 meldet „Nothing affected“.
Lösung 2
- Alles ist betroffen: Sie haben eine Datei der Wurzel geändert (zum Beispiel
tsconfig.base.jsonoder die Sperrdatei); die gelten für alle Aufgaben. - Nichts ist betroffen, obwohl Sie
libs/änderten: Die Änderung ist nicht committet (--head=HEADliest den Verlauf), oder--basezeigt auf denselben Commit. - Nx meldet Git-Fehler: kein Repository oder kein Commit; in der CI eine zu flache Kopie (
fetch-depth: 0). - Die Zahlen weichen ab: Sie haben andere Projekte; maßgeblich ist, dass Projekte ohne Weg zur Änderung fehlen.
Was Sie jetzt können
- Sie lesen
inputs,outputsunddependsOneiner Aufgabe und wissen, was den Hash bestimmt (darunterCIbei Tests). - Sie beweisen einen Cache-Treffer an der Ausgabe und erklären, wann er ausbleibt.
- Sie lassen
nx affected -tmit--explainzeigen, welche Aufgaben ein Commit auslöst, und kennen den Unterschied zwischen projekt- und aufgabenbasiert (Nx 24). - Sie skizzieren eine Pipeline mit vollem Verlauf, expliziter Basis,
npm ciund Grenzprobe und wissen, was dort nicht geprüft wurde. - Sie ordnen den entfernten Cache ein, benennen die offenen Punkte und schreiben Commits nach Conventional Commits.
Weiterlesen
- Nx: Cache task results: Hash,
inputs,outputs,targetDefaults. - Nx: Run only tasks affected by a PR:
--base,--head,--explain, Änderungen in Nx 24, Hinweise für GitHub, CircleCI, GitLab. - Nx: Remote cache: Nx Cloud und Selbstbetrieb.
- Nx: CI workflow examples, Azure: das Beispiel für Azure Pipelines.
Die Kladde: nx.json mit ihren namedInputs und eine Pipelineskizze mit vollem Verlauf, affected und der Probe. Damit ist das Modul abgeschlossen. Als Nächstes folgt Lektion 0201 im Modul W10.2: Strategisches DDD und die Kontexte der Kladde.