Zum Inhalt springen

2.2 npm: Versionen und fremder Code

Zu Zen-Modus wechseln

Nach dieser Einheit kannst du:

  • Semantic Versioning erklären und Versionsbereiche mit ^ und ~ lesen.
  • die Aufgabe der Sperrdatei erklären und eine Projektumgebung mit npm install beziehungsweise npm ci reproduzierbar wiederherstellen.
  • vor der Aufnahme einer Abhängigkeit beurteilen, ob sie nötig, gepflegt und vertrauenswürdig ist.

Bei dir läuft das buffet-app einwandfrei. Deine Sitznachbarin klont dasselbe Repository, tippt npm install, und womöglich verhält sich das Programm bei ihr anders. Dieselbe package.json, derselbe Befehl, trotzdem ein anderes Ergebnis. Die Ursache steckt in einem einzigen Zeichen: dem ^ vor der Versionsnummer.

flowchart LR
    accTitle: Dieselbe Einkaufsliste, zwei verschiedene Lieferungen
    list["package.json:<br/>zod ^4.4.3"] --> mine["dein Rechner, damals:<br/>npm install → 4.4.3"]
    list --> hers["anderer Rechner, Wochen später:<br/>npm install → 4.6.5"]
    mine --> same{"dasselbe<br/>Programm?"}
    hers --> same

Abb. 2.6: Ohne Lieferschein liefert dieselbe Einkaufsliste an verschiedenen Tagen verschiedene Versionen (eigene Darstellung).

In Einheit 2.1 ging es um die Einkaufsliste, die package.json. Diese Einheit kümmert sich um den Lieferschein: welche Version genau geliefert wird, wie du ein Projekt auf jedem Rechner gleich herstellst und wie du beurteilst, ob ein Paket überhaupt ins Projekt darf.

  • Semantic Versioning (SemVer): eine Versionsnummer aus drei Teilen, major.minor.patch, mit fester Bedeutung für jeden Teil.
  • Versionsbereich: ein Eintrag wie ^4.4.3 in der package.json, der nicht eine, sondern mehrere Versionen erlaubt.
  • Sperrdatei (lock file): hält fest, welche Version jedes Pakets tatsächlich installiert wurde (package-lock.json bei npm, pnpm-lock.yaml bei pnpm).
  • Lieferkette (supply chain): alle fremden Pakete, von denen dein Projekt direkt oder über andere Pakete abhängt. Wer ein Glied angreift, trifft alle, die es verwenden.

Im Möbelhaus-Bild aus Einheit 2.1: Die package.json ist die Einkaufsliste, die Sperrdatei der Lieferschein mit der genauen Artikelnummer jedes gelieferten Teils.

An dieser Stelle entscheidet sich, ob ein Projekt in zwei Jahren noch baut. Pakete verwenden Semantic Versioning (SemVer), eine dreiteilige Nummer mit fester Bedeutung:

4 . 4 . 3
major minor patch
TeilWird erhöht beiFür dich bedeutet das
patchFehlerbehebung, keine Schnittstellenänderunggefahrlos aktualisieren
minorneue Funktionen, bestehende bleiben gültiggefahrlos aktualisieren
majorÄnderung, die bestehenden Code brechen kannerst lesen, dann aktualisieren

Diese Bedeutung ist ein Versprechen der Paketautoren, keine technische Garantie. Es wird meistens gehalten, aber nicht immer. Ein major-Sprung ist deshalb immer eine bewusste Entscheidung, nie ein nebenbei ausgeführtes Kommando.

Wo begegnet dir das? Bei Node.js selbst: node --version aus Kapitel 1 zeigt etwa v24.11.1. Die 24 ist die major-Version, und genau an ihr hängt, ob eine Fassung LTS ist oder nicht. Auch viele Handy-Apps zeigen in den Einstellungen eine dreiteilige Versionsnummer, nur hält sich nicht jede App an die SemVer-Bedeutung.

In der package.json steht deshalb selten eine exakte Version, sondern ein Bereich:

EintragBedeutung
"^4.4.3"alles ab 4.4.3 bis unter 5.0.0 (Standard bei npm install)
"~4.4.3"alles ab 4.4.3 bis unter 4.5.0 (nur Patches)
"4.4.3"exakt diese Version (so trägt --save-exact ein, siehe Biome oben)

Konkret heißt das für "zod": "^4.4.3": Im Oktober 2026 ist 4.6.5 die neueste 4er-Version, also installiert npm install auf einem frischen Rechner 4.6.5. Erscheint morgen 5.0.0, bleibt es bei der neuesten 4er-Version, denn der Bereich endet unter 5.0.0. Ausnahme: Bei Versionen unter 1.0.0 ist schon der minor-Teil ein Bruch-Signal, deshalb bedeutet ^0.4.2 nur “ab 0.4.2 bis unter 0.5.0”.

Die Versionsnummer 4.4.3 mit ihren Teilen major, minor und patch. Darunter ein Zahlenstrahl von 4.4.2 bis 5.0.0. ~4.4.3 reicht von 4.4.3 bis unter 4.5.0, ^4.4.3 von 4.4.3 bis unter 5.0.0. 4.4.2 ist zu alt, 5.0.0 ein major-Sprung
Abb. 2.7: Welche Versionen ^4.4.3 und ~4.4.3 erlauben (eigene Darstellung).

Damit ergibt sich das Problem, das jeden Anfänger einmal erwischt: "^4.4.3" bedeutet, dass zwei Installationen an verschiedenen Tagen verschiedene Versionen ergeben können. Genau dafür gibt es die package-lock.json (pnpm: pnpm-lock.yaml). Sie hält fest, welche Version jedes einzelnen Pakets tatsächlich installiert wurde, auch jeder transitiven Abhängigkeit.

Im Möbelhaus-Bild: Die Einkaufsliste sagt “ein Kasten dieses Modells”, die Sperrdatei ist der Lieferschein mit der genauen Artikelnummer jedes gelieferten Teils. Wer nach diesem Lieferschein nachbestellt, bekommt exakt dasselbe. Die Grenze des Bildes: Ein Paket nutzt sich nicht ab und lässt sich beliebig oft identisch nachladen. Deshalb kommt nicht die Lieferung ins Repository, sondern nur der Lieferschein.

npm install liest den erlaubten Bereich ^4.4.3 aus der package.json, schreibt die tatsächlich installierte Version 4.4.3 in die package-lock.json und füllt node_modules. npm ci liest nur die package-lock.json und baut node_modules exakt danach neu auf
Abb. 2.8: Die package.json sagt, was erlaubt ist, die Sperrdatei, was installiert wurde (eigene Darstellung).

Für automatisierte Umgebungen (die CI-Pipeline aus Kapitel 13) gibt es den strengeren Befehl:

Terminal-Fenster
npm ci # installs exactly what the lock file says; fails if it is missing or inconsistent
pnpm install --frozen-lockfile # pnpm: same guarantee, fails if the lock file is out of date

npm ci löscht node_modules und baut die Umgebung ausschließlich aus der Sperrdatei auf. Er ist damit reproduzierbar und schneller, und er ist der Grund, warum die Sperrdatei ins Repository gehört. pnpm hat dafür keinen eigenen Befehl, sondern die Option --frozen-lockfile; sie ist in CI-Umgebungen ohnehin der Standard.

Terminal-Fenster
npm outdated # what is out of date?
npm update # update within the allowed range (no major jumps)
npm install zod@5 # deliberate major jump
pnpm outdated
pnpm update # update within the allowed range (no major jumps)
pnpm add zod@5 # deliberate major jump

npm update (pnpm: pnpm update) bleibt innerhalb der Bereiche aus der package.json und ist deshalb der ungefährliche Befehl. Ein major-Sprung wird einzeln gemacht, mit einem Blick in die Änderungsliste des Pakets und einem Testlauf danach. Nie für mehrere Pakete gleichzeitig, weil sonst unklar bleibt, welcher Sprung etwas gebrochen hat.

Auflösung

Bei ^4.4.3 sind 4.4.9 und 4.6.0 erlaubt: alles ab 4.4.3 bis unter 5.0.0. Bei ~4.4.3 bleibt nur 4.4.9, weil der Bereich unter 4.5.0 endet. 4.4.2 ist in beiden Fällen zu alt, 5.0.0 in beiden Fällen ein major-Sprung. Nachprüfen kannst du das im npm SemVer Calculator.

Manche Werkzeuge braucht man genau einmal, etwa den Projektgenerator eines Frameworks. npx lädt ein Paket, führt es aus und installiert es nicht dauerhaft:

Terminal-Fenster
npx create-next-app@latest my-project # used again in chapter 8
pnpm dlx create-next-app@latest my-project

Für Werkzeuge, die im Projekt stehen, ruft npx die lokale Version auf: npx tsc nimmt den TypeScript-Compiler aus node_modules, nicht irgendeinen global installierten. Bei pnpm heißt das Gegenstück pnpm exec tsc, und pnpm dlx ist das Gegenstück zum einmaligen Ausführen.

Das ist der Grund, warum Projektwerkzeuge lokal und nicht global installiert werden: Zwei Projekte können unterschiedliche Compilerversionen brauchen, und global geht immer nur eine.

Links: npx create-next-app lädt das Paket aus der Registry, führt es einmal aus und installiert es nicht dauerhaft. Rechts: npx tsc nimmt den Compiler aus dem node_modules-Ordner des jeweiligen Projekts, Projekt A und Projekt B können verschiedene Versionen verwenden; global installiert gäbe es nur eine
Abb. 2.9: npx führt einmal aus oder nimmt das Werkzeug aus dem eigenen Projekt (eigene Darstellung, Versionsnummern als Beispiel).

Wo begegnet dir das? In Kapitel 7 legst du mit npm create vite@latest ein React-Projekt an. Dahinter steckt derselbe Mechanismus: npm create vite führt das Paket create-vite einmal aus, ohne es dauerhaft zu installieren.

Ein Paket zu installieren ist eine Entscheidung mit Folgen: Du übernimmst Code, den du nicht geschrieben hast, in dein Produkt, samt dessen Abhängigkeiten und dessen Sicherheitslücken.

Ein weit verbreitetes Paket, von dem eine schadhafte Version veröffentlicht wurde. Pfeile führen zu sechs Projekten, die es verwenden. Eine Veröffentlichung erreicht auf einen Schlag alle diese Projekte
Abb. 2.10: Eine schadhafte Version eines beliebten Pakets erreicht alle Projekte, die es verwenden (eigene Darstellung).

Prüffragen vor jedem npm install:

  1. Brauche ich das wirklich? Für drei Zeilen Datumsformatierung braucht es kein Paket. Jede Abhängigkeit ist dauerhafte Wartungslast.
  2. Wird es gepflegt? Wann kam die letzte Veröffentlichung? Wie viele offene Fehlermeldungen liegen unbeantwortet?
  3. Wie verbreitet ist es? Wöchentliche Downloadzahlen sind ein grober, aber brauchbarer Hinweis: Bei vielen Benutzern fallen Probleme meist auf, bevor sie dich treffen.
  4. Was schleppt es mit? Ein Paket mit fünfzig eigenen Abhängigkeiten ist eine andere Entscheidung als eines ohne.
  5. Passt die Lizenz? Bei schulischen Projekten selten kritisch, im Berufsleben regelmäßig.
Terminal-Fenster
npm audit # known vulnerabilities in the dependency tree
npm audit fix # update what can be updated safely
npm ls zod # who actually pulls in this package?
pnpm audit
pnpm audit --fix # pnpm: adds "overrides" to pnpm-workspace.yaml instead of updating packages
pnpm why zod # who actually pulls in this package?

npm audit fix und pnpm audit --fix gehen unterschiedlich vor: npm aktualisiert die betroffenen Pakete, pnpm erzwingt stattdessen eine sichere Version per Override. Was das genau heißt, steht im optionalen Teil Vertiefung: Overrides bei pnpm audit --fix bei den Zusatzmaterialien.

Wo begegnet dir das? Schon bei jedem npm install: Am Ende steht eine Zeile wie found 0 vulnerabilities. Das ist eine automatische Sicherheitsprüfung. Steht dort eine Zahl größer null, zeigt npm audit die Details.

Auflösung

Schon die erste Frage: tableNumber % 2 === 0 ist eine einzige Zeile, dafür braucht es kein Paket. Jede Abhängigkeit bleibt dauerhafte Wartungslast, und sie kann eigene Abhängigkeiten mitbringen (Frage 4). Wie wenig Code genügt, um viele Projekte lahmzulegen, hat left-pad gezeigt: Elf Zeilen, die jeder selbst hätte schreiben können.

npm ist nicht das einzige Werkzeug seiner Art. Du wirst in fremden Projekten auf yarn, pnpm und bun stoßen. Alle lesen dieselbe package.json, unterscheiden sich aber in Geschwindigkeit, Plattenverbrauch und eigener Sperrdatei (yarn.lock, pnpm-lock.yaml, bun.lock).

Zwei Regeln genügen fürs Erste. In einem Projekt wird ein Paketmanager verwendet; zwei Sperrdateien nebeneinander sind ein handfester Fehler. Und welcher es ist, erkennst du an der vorhandenen Sperrdatei, nicht an der Gewohnheit. Im Unterricht zeigen wir npm, weil es mit Node.js mitkommt und keine Zusatzinstallation braucht; pnpm ist jederzeit gleichwertig verwendbar.

Vier Sperrdateien und der Paketmanager, zu dem sie gehören: package-lock.json zu npm, pnpm-lock.yaml zu pnpm, yarn.lock zu yarn, bun.lock zu bun. Daneben ein Projekt mit package-lock.json und pnpm-lock.yaml nebeneinander, markiert als Fehler
Abb. 2.11: Die Sperrdatei verrät den Paketmanager; zwei Sperrdateien sind ein Fehler (eigene Darstellung).

Wo begegnet dir das? In vielen Installationsanleitungen, etwa bei Vite oder Next.js, stehen die Befehle für npm, pnpm, yarn und bun nebeneinander. Du nimmst die Zeile, die zur Sperrdatei im Projekt passt.

Aufgabenpmpnpm
Projekt anlegennpm init -ypnpm init
Skript ausführennpm run devpnpm run dev (oder pnpm dev)
Paket installierennpm install zodpnpm add zod
Entwicklungspaketnpm install -D vitestpnpm add -D vitest
Alles wiederherstellennpm installpnpm install
Exakt nach Sperrdateinpm cipnpm install --frozen-lockfile
Paket entfernennpm uninstall zodpnpm remove zod
Veraltetes anzeigennpm outdatedpnpm outdated
Im erlaubten Bereich aktualisierennpm updatepnpm update
Einmal ausführennpx <paket>pnpm dlx <paket>
Lokales Werkzeug aufrufennpx tscpnpm exec tsc
Sicherheitsprüfungnpm auditpnpm audit
Wer holt dieses Paket?npm ls zodpnpm why zod
Sperrdateipackage-lock.jsonpnpm-lock.yaml

pnpm ist nicht Teil von Node.js und muss einmalig installiert werden, etwa mit npm install --global pnpm; weitere Wege beschreibt die pnpm-Installationsanleitung. Das ist eine der wenigen Ausnahmen von der Regel “nichts global”: Der Paketmanager selbst ist ein Werkzeug des Rechners, keines des Projekts. Damit im Team alle dieselbe Version verwenden, trägt pnpm init das Feld packageManager in die package.json ein.

Auflösung

Die Sperrdatei verrät pnpm, also pnpm install (in einer CI-Pipeline pnpm install --frozen-lockfile). npm install liest die pnpm-lock.yaml nicht, löst die Versionsbereiche neu auf und legt eine zusätzliche package-lock.json an. Danach liegen zwei Sperrdateien im Projekt, und im Team installiert womöglich jede:r etwas anderes.

  1. package-lock.json ignoriert oder gelöscht: die Ursache hinter den meisten “bei mir läuft es aber”-Fällen.
  2. Global statt lokal installiert: funktioniert auf dem eigenen Rechner und nirgends sonst; das Projekt ist ohne die Dokumentation dieser Handgriffe nicht lauffähig.
  3. npm audit fix --force blind ausgeführt: Das --force erlaubt major-Sprünge und bricht dabei gern das Projekt. Erst lesen, dann entscheiden.
  4. Zwei Sperrdateien nebeneinander: package-lock.json und pnpm-lock.yaml (oder bun.lock) im selben Projekt, und jede:r im Team installiert etwas anderes. Eine löschen, einen Paketmanager festlegen.
  • npm-Dokumentation zu npm ci - erklärt genau, wann npm ci abbricht und warum das gewollt ist.
  • semver.org - kurz und präzise: was major, minor und patch versprechen.
  • npm SemVer Calculator - zeigt für ein beliebiges Paket, welche Versionen ein Bereich trifft; hilfreich, um ^ und ~ wirklich zu verstehen.
  • npmjs.com - die Paketsuche selbst: Hier stehen Downloadzahlen, letzte Veröffentlichung und Abhängigkeiten, also alles für die Prüffragen oben.
  • pnpm-Dokumentation - Befehlsreferenz und Begründung für pnpm; hilfreich, wenn du statt npm damit arbeitest.

Dieser Teil ist optional und kommt im Unterricht nicht vor. Er ist für dich, wenn du mit pnpm arbeitest.

npm audit fix repariert eine Sicherheitslücke, indem es die betroffenen Pakete auf eine neuere Version hebt, soweit die Versionsbereiche in der package.json das erlauben. pnpm audit --fix aktualisiert dagegen kein Paket. Es trägt einen Override ein, also eine Zwangsversion: Egal welches Paket im Abhängigkeitsbaum die verwundbare Version verlangt, pnpm installiert dort die reparierte.

Ein Beispiel: Das alte Paket [email protected] zieht [email protected] mit, für das zwei bekannte Lücken gemeldet sind. pnpm audit --fix schreibt daraufhin in die pnpm-workspace.yaml:

overrides:
minimist@<0.2.1: ^0.2.1
minimist@<0.2.4: ^0.2.4

Erst ein anschließendes pnpm install wendet die Overrides an. Danach hängt unter [email protected] die reparierte Version [email protected], und pnpm audit meldet keine Lücke mehr. mkdirp selbst bleibt auf 0.5.1.

Vorteil und Preis liegen nah beieinander:

  • Vorteil: Auch Lücken tief im Abhängigkeitsbaum lassen sich sofort schließen, selbst wenn das Paket dazwischen (hier mkdirp) noch keine neue Version veröffentlicht hat.
  • Preis: Das Paket dazwischen bekommt eine Version, mit der es nie getestet wurde. Bei einem Sprung über eine Major-Version kann es dadurch brechen. Nach einem --fix also immer testen.
  • Aufräumen: Overrides bleiben stehen, bis du sie löschst, auch wenn das Paket dazwischen die Lücke längst selbst behoben hat. Schau sie dir bei jedem größeren Update an und entferne, was nicht mehr nötig ist.
  • Für alle gültig: Die pnpm-workspace.yaml wird eingecheckt. Ein Override gilt also für das ganze Team, nicht nur auf deinem Rechner.

Seit pnpm 11 gibt es auch pnpm audit --fix=update: Es hebt die verwundbaren Pakete in der Sperrdatei an, so wie npm. Im Beispiel hilft das aber nicht, denn [email protected] verlangt genau [email protected]. --fix=update meldet dann 0 vulnerabilities were fixed. Genau für solche Fälle gibt es Overrides.

In älteren Projekten findest du Overrides manchmal noch im Feld pnpm.overrides der package.json. Seit pnpm 11 liest pnpm Einstellungen aus diesem Feld nicht mehr, sie gehören in die pnpm-workspace.yaml.

Du kannst dir diese Einheit kostenlos mit der Vorlesefunktion deines Browsers oder Betriebssystems vorlesen lassen (etwa „Laut vorlesen” in Edge), ohne Konto und ohne Upload. Freiwillig kannst du mit Google NotebookLM (Google-Konto nötig) aus diesem öffentlichen Text eine gesprochene Zusammenfassung erzeugen, auch auf Deutsch.

Du kannst jetzt:

  • Semantic Versioning erklären und Versionsbereiche mit ^ und ~ lesen.
  • die Aufgabe der Sperrdatei erklären und eine Projektumgebung mit npm install beziehungsweise npm ci (pnpm: pnpm install beziehungsweise pnpm install --frozen-lockfile) reproduzierbar wiederherstellen.
  • vor der Aufnahme einer Abhängigkeit beurteilen, ob sie nötig, gepflegt und vertrauenswürdig ist.
  • Aufgabe 01 - TypeScript-Spickzettel, Stufe Wesentlich_überwiegend fertigstellen (Sperrdatei, node_modules/ löschen und mit npm ci wiederherstellen)
  • npm ci: Verhalten und Abgrenzung zu npm install → npm-Dokumentation: npm ci
  • npm create führt das Paket create-<name> aus → npm-Dokumentation: npm init
  • Semantic Versioning, Bedeutung von ^ und ~ → semver.org
  • Welche Version ein Versionsbereich wie ^4.4.3 tatsächlich trifft → npm SemVer Calculator
  • npm-Befehle allgemein (install, audit, outdated, update) → npm Docs
  • npm-Registry, Paketsuche, Downloadzahlen → npmjs.com
  • pnpm-Befehle, packageManager-Feld → pnpm-Dokumentation
  • pnpm-Installation → pnpm-Installationsanleitung
  • pnpm audit --fix trägt Overrides in die pnpm-workspace.yaml ein, --fix=update seit pnpm 11 → pnpm-Dokumentation: pnpm audit
  • Aufbau von overrides → pnpm-Dokumentation: overrides
  • Seit pnpm 11 keine Einstellungen mehr aus dem Feld pnpm der package.json → pnpm-Dokumentation: package.json
  • Beispiel [email protected] / [email protected], Ausgaben von pnpm audit --fix und --fix=update → eigener Test mit pnpm 12.5.1 und Node.js 24.11.1 (Windows, Oktober 2026)
  • Abb. 2.6 (dieselbe Einkaufsliste, zwei Lieferungen, Mermaid-Diagramm), Abb. 2.7 (SemVer-Bereiche), Abb. 2.8 (Sperrdatei und npm ci), Abb. 2.9 (npx), Abb. 2.10 (Lieferkette), Abb. 2.11 (Sperrdateien und Paketmanager) → eigene Darstellung
  • Vorfall rund um das Paket event-stream (2018) → npm-Blog: Details about the event-stream incident
  • Vorfall rund um das Paket left-pad (2016) → Wikipedia: Npm left-pad incident
  • Oliver Maurhart (MAO)
  • Michael Gabriel (GAM)

CC-BY-SA

https://qiz.at/rules