Zum Inhalt springen
aviral gupta

// B2.3 · ca. 32 Min. · Einstieg

semver-Bereiche und Updates

Nach dieser Lektion können Sie eine Versionsnummer lesen, in package.json den Bereich schreiben, den Sie meinen, und Abhängigkeiten aktualisieren, ohne von den Änderungen überrascht zu werden.

Lektion 3 von 5 in B2 npm und Pakete

Danach können Sie

  • major.minor.patch lesen: welche Zahl ein Fix, ein Feature oder eine inkompatible Änderung erhöht und wie Prereleases sortieren
  • Vorhersagen, was exakte Versionen, ^, ~, x-Bereiche und Prerelease-Bereiche zulassen, auch unter 1.0.0
  • npm outdated und npm update verwenden und sagen, was jeder Befehl ändert
  1. Aufwärmen · Aufgabe 1 von 7

    Aufwärmen: Eine Bibliothek in Version 2.3.1 bekommt eine neue Funktion, und alles, was vorher funktionierte, funktioniert weiter. Welche Version sollte das Release haben?

  2. Vorhersagen · Aufgabe 2 von 7

    Sagen Sie es vorher, bevor Sie weiterlesen. dep1 1.1.1 ist mit der package.json unten installiert. Seitdem sind dep1 1.1.2, 1.2.0 und 1.2.2 erschienen, und 1.2.2 ist als latest markiert. Sie führen npm update aus. Welche Version von dep1 ist danach installiert?

    {
      "dependencies": {
        "dep1": "~1.1.1"
      }
    }
  3. Üben · Aufgabe 3 von 7

    Setzen Sie den Operator ein, damit der Bereich 1.4.2 und 1.9.0 zulässt, aber nicht 2.0.0.

    "dep1": "____1.4.2"
    "dep1": "1.4.2"
  4. Üben · Aufgabe 4 von 7

    Ordnen Sie jedem Bereich in package.json die Versionen zu, die er zulässt.

  5. Üben · Aufgabe 5 von 7

    Welche dieser Versionen lässt dieser Bereich zu?

    "dep1": "^0.2.3"
  6. Denksport · Aufgabe 6 von 7

    Knobelaufgabe. Die Registry hat dep1 1.4.0-beta.1, 1.4.0-beta.3, 1.4.0, 1.4.7 und 1.5.0-beta.1. Welche Version wählt npm install für diesen Bereich?

    "dep1": "^1.4.0-beta.2"
  7. Anwenden · Aufgabe 7 von 7

    Mini-Aufgabe. Führen Sie in einem neuen Ordner npm init -y aus. Wählen Sie ein kleines Paket und listen Sie seine Versionen mit npm view <package> versions. Installieren Sie eine ältere mit npm install <package>@<version> und sehen Sie nach, welchen Bereich npm in package.json gespeichert hat. Führen Sie npm outdated aus und lesen Sie Current, Wanted und Latest. Führen Sie npm update aus und vergleichen Sie package.json und package-lock.json mit vorher. Wechseln Sie zum Schluss mit npm install <package>@latest auf den neuesten Major.

    Prüfen Sie Ihr Ergebnis anhand dieser Liste

Selbst programmieren

Lesen Sie das ausgearbeitete Beispiel und lösen Sie dann die Übungen. Ihr Code läuft in Ihrem Browser oder auf Ihrem Computer und wird nie hochgeladen.

Ausgearbeitetes Beispiel

Versionen werden als Zahlen verglichen

Bevor npm die höchste Version wählen kann, die ein Bereich erlaubt, muss es wissen, welche Version höher ist. Dieses Programm sortiert vier Versionen zweimal: als Text, wie sort() es standardmäßig tut, und Teil für Teil als Zahlen, so wie semver sie vergleicht. Als Text kommt 1.10.0 zuerst, weil das Zeichen 1 vor 9 sortiert. Es läuft auch im Browser.

main.js

// Versions are compared number by number, not letter by letter.
const versions = ["1.9.0", "1.10.0", "1.2.10", "1.2.9"];

function compare(a, b) {
  const x = a.split(".").map(Number);
  const y = b.split(".").map(Number);
  for (let i = 0; i < 3; i++) {
    if (x[i] !== y[i]) return x[i] - y[i];
  }
  return 0;
}

console.log("as text:   ", [...versions].sort().join(" < "));
console.log("as numbers:", [...versions].sort(compare).join(" < "));
console.log("newest:", [...versions].sort(compare).at(-1));
console.log('"1.10.0" > "1.9.0" is', "1.10.0" > "1.9.0");

Ausführen mit

node main.js

Ausgabe

as text:    1.10.0 < 1.2.10 < 1.2.9 < 1.9.0
as numbers: 1.2.9 < 1.2.10 < 1.9.0 < 1.10.0
newest: 1.10.0
"1.10.0" > "1.9.0" is false
  • sort() ohne Funktion vergleicht Strings Zeichen für Zeichen, darum landet "1.10.0" vor "1.2.10".
  • compare() prüft major, dann minor, dann patch und geht nur weiter, solange sie gleich sind.
  • Dieses compare() kennt keine Prereleases: "0-beta" ist keine Zahl. Das semver-Paket von npm kann sie.
Ändern und ausführen

Tab rückt ein, Umschalt+Tab rückt aus. Um den Editor mit der Tastatur zu verlassen, drücken Sie Esc und dann Tab.

Beim ersten Ausführen lädt Ihr Browser den JavaScript-Runner herunter (bis zu 0.1 MB) und speichert ihn im Cache. Ihr Code läuft in der eigenen Engine Ihres Browsers und bleibt auf Ihrem Gerät.

Übungen

Übung 1 von 2

Eine Version lesen

Schreiben Sie parseVersion(text). Für eine Version aus drei ganzen Zahlen mit Punkten dazwischen, etwa "1.4.2" oder "10.20.30", liefert sie {major, minor, patch} als Zahlen. Für alles andere liefert sie null: einen Bereich wie "^1.4.2", eine unvollständige Version wie "1.4", ein führendes v wie in "v1.4.2", vier Teile oder ein Prerelease wie "2.0.0-beta.1" (dieser Parser lässt sie aus). Prüfen Sie die Tests mit node --test.

Tab rückt ein, Umschalt+Tab rückt aus. Um den Editor mit der Tastatur zu verlassen, drücken Sie Esc und dann Tab.

Beim ersten Ausführen lädt Ihr Browser den JavaScript-Runner herunter (bis zu 0.1 MB) und speichert ihn im Cache. Ihr Code läuft in der eigenen Engine Ihres Browsers und bleibt auf Ihrem Gerät.

Hinweise
  1. Hinweis 1

    Ein regulärer Ausdruck mit ^ und $ kann verlangen, dass der ganze Text aus Ziffern, Punkt, Ziffern, Punkt, Ziffern besteht: /^(\d+)\.(\d+)\.(\d+)$/.

  2. Hinweis 2

    exec() liefert null, wenn der Text nicht passt; geben Sie dann null zurück.

  3. Hinweis 3

    Die Gruppen sind Strings. Number(match[1]) macht aus der ersten eine Zahl.

Eine Lösung zeigen

Ein möglicher Lösungsweg. Ihrer kann anders aussehen und trotzdem alle Prüfungen bestehen.

// {major, minor, patch} for a version such as "1.4.2", or null.
export function parseVersion(text) {
  const match = /^(\d+)\.(\d+)\.(\d+)$/.exec(text);
  if (match === null) return null;
  return {major: Number(match[1]), minor: Number(match[2]), patch: Number(match[3])};
}
Auf dem eigenen Computer ausführen

Installieren Sie Node.js 24 LTS oder neuer. Speichern Sie diese Dateien in einem Ordner, öffnen Sie dort ein Terminal und führen Sie die Befehle unten aus.

main.js

// {major, minor, patch} for a version such as "1.4.2", or null.
export function parseVersion(text) {
  return null;
}

main.test.js

import {test} from 'node:test';
import assert from 'node:assert/strict';
import {parseVersion} from './main.js';

test('zerlegt 1.4.2 in drei Zahlen', () => {
  assert.deepEqual(parseVersion('1.4.2'), {major: 1, minor: 4, patch: 2}, `parseVersion('1.4.2') lieferte ${JSON.stringify(parseVersion('1.4.2'))}`);
});

test('liest Zahlen mit mehr als einer Stelle', () => {
  assert.deepEqual(parseVersion('10.20.30'), {major: 10, minor: 20, patch: 30}, `parseVersion('10.20.30') lieferte ${JSON.stringify(parseVersion('10.20.30'))}`);
});

test('liefert null für Bereiche und anderen Text, der keine einfache Version ist', () => {
  for (const text of ['^1.4.2', '1.4', 'v1.4.2', '1.4.2.0', '2.0.0-beta.1', '1.x.0', '']) {
    assert.equal(parseVersion(text), null, `parseVersion(${JSON.stringify(text)}) lieferte ${JSON.stringify(parseVersion(text))}, erwartet: null`);
  }
});

package.json

{
  "type": "module"
}

package.json sagt Node.js, dass die .js-Dateien Module sind; lassen Sie die Datei im Ordner.

Programm ausführen:

node main.js

Prüfungen ausführen (learnrun.js muss im selben Ordner liegen):

node --test
learnrun.js herunterladen

Übung 2 von 2

Ein Caret-Bereich, von Hand

parseVersion ist fertig. Schreiben Sie allowsCaret(range, version) für einen Caret-Bereich mit Major 1 oder höher, etwa "^1.4.2": Die Funktion liefert true, wenn version denselben Major hat und nicht kleiner ist als die Version des Bereichs. "^1.4.2" erlaubt also 1.4.2, 1.9.0 und 1.10.0, aber nicht 1.4.1, 1.3.9 oder 2.0.0. Vergleichen Sie die Teile als Zahlen. Bereiche unter 1.0.0 und Prereleases gehören nicht dazu; die übernimmt das semver-Paket von npm.

Tab rückt ein, Umschalt+Tab rückt aus. Um den Editor mit der Tastatur zu verlassen, drücken Sie Esc und dann Tab.

Beim ersten Ausführen lädt Ihr Browser den JavaScript-Runner herunter (bis zu 0.1 MB) und speichert ihn im Cache. Ihr Code läuft in der eigenen Engine Ihres Browsers und bleibt auf Ihrem Gerät.

Hinweise
  1. Hinweis 1

    range.slice(1) entfernt das ^, und parseVersion macht aus beiden Strings Zahlen.

  2. Hinweis 2

    Ein anderer Major ist nie erlaubt. Bei gleichem Major ist ein höherer Minor immer erlaubt.

  3. Hinweis 3

    Bei gleichem Major und Minor muss der Patch gleich oder höher sein.

Eine Lösung zeigen

Ein möglicher Lösungsweg. Ihrer kann anders aussehen und trotzdem alle Prüfungen bestehen.

// {major, minor, patch} for a version such as "1.4.2", or null.
export function parseVersion(text) {
  const match = /^(\d+)\.(\d+)\.(\d+)$/.exec(text);
  if (match === null) return null;
  return {major: Number(match[1]), minor: Number(match[2]), patch: Number(match[3])};
}

// true if version is allowed by a caret range such as "^1.4.2" (major 1 or more).
export function allowsCaret(range, version) {
  const base = parseVersion(range.slice(1));
  const v = parseVersion(version);
  if (v.major !== base.major) return false;
  if (v.minor !== base.minor) return v.minor > base.minor;
  return v.patch >= base.patch;
}
Auf dem eigenen Computer ausführen

Installieren Sie Node.js 24 LTS oder neuer. Speichern Sie diese Dateien in einem Ordner, öffnen Sie dort ein Terminal und führen Sie die Befehle unten aus.

main.js

// {major, minor, patch} for a version such as "1.4.2", or null.
export function parseVersion(text) {
  const match = /^(\d+)\.(\d+)\.(\d+)$/.exec(text);
  if (match === null) return null;
  return {major: Number(match[1]), minor: Number(match[2]), patch: Number(match[3])};
}

// true if version is allowed by a caret range such as "^1.4.2" (major 1 or more).
export function allowsCaret(range, version) {
  return false;
}

main.test.js

import {test} from 'node:test';
import assert from 'node:assert/strict';
import {allowsCaret} from './main.js';

test('erlaubt die Version selbst und neuere Minors und Patches', () => {
  for (const v of ['1.4.2', '1.4.9', '1.9.0']) {
    assert.equal(allowsCaret('^1.4.2', v), true, `allowsCaret('^1.4.2', '${v}') sollte true sein`);
  }
});

test('vergleicht Zahlen, keinen Text: 1.10.0 ist neuer als 1.4.2', () => {
  assert.equal(allowsCaret('^1.4.2', '1.10.0'), true, `allowsCaret('^1.4.2', '1.10.0') sollte true sein`);
});

test('lehnt kleinere Versionen ab', () => {
  for (const v of ['1.4.1', '1.3.9', '0.9.9']) {
    assert.equal(allowsCaret('^1.4.2', v), false, `allowsCaret('^1.4.2', '${v}') sollte false sein`);
  }
});

test('lehnt einen neuen Major ab', () => {
  assert.equal(allowsCaret('^1.4.2', '2.0.0'), false, `allowsCaret('^1.4.2', '2.0.0') sollte false sein`);
  assert.equal(allowsCaret('^2.0.0', '2.5.1'), true, `allowsCaret('^2.0.0', '2.5.1') sollte true sein`);
});

package.json

{
  "type": "module"
}

package.json sagt Node.js, dass die .js-Dateien Module sind; lassen Sie die Datei im Ordner.

Programm ausführen:

node main.js

Prüfungen ausführen (learnrun.js muss im selben Ordner liegen):

node --test
learnrun.js herunterladen

Häufige Fehler

Einen Bereich lesen, als wäre er eine Version

// {major, minor, patch} for a version such as "1.4.2", or null.
function parseVersion(text) {
  const match = /^(\d+)\.(\d+)\.(\d+)$/.exec(text);
  if (match === null) return null;
  return {major: Number(match[1]), minor: Number(match[2]), patch: Number(match[3])};
}

const dependencies = {dep1: "^1.4.2"};
console.log("dep1 major:", parseVersion(dependencies.dep1).major);

Was Node.js ausgibt

TypeError: Cannot read properties of null (reading 'major')

Warum, und die Lösung

"^1.4.2" in package.json ist ein Bereich, keine Version, darum liefert der Parser null, und .major scheitert. Die wirklich installierte Version steht in package-lock.json und in node_modules/dep1/package.json. Prüfen Sie ein Ergebnis auf null, bevor Sie es verwenden, und geben Sie einen Bereich nur an Code, der Bereiche versteht, etwa die Funktion satisfies() aus dem semver-Paket von npm.

semver importieren, ohne es zu installieren

import semver from "semver";

console.log(semver.satisfies("1.9.0", "^1.4.2"));

Was Node.js ausgibt

Error [ERR_MODULE_NOT_FOUND]: Cannot find package 'semver' imported from

Warum, und die Lösung

npm bringt eine eigene Kopie von semver mit, aber diese Kopie liegt in npm, nicht in Ihrem Projekt, also findet Node.js sie nicht. Installieren Sie das Paket zuerst ins Projekt: npm install semver. Das trägt es in "dependencies" und node_modules ein, dann funktioniert der Import und gibt true aus.

Ein Parser, der Prereleases vergisst

// The three numbers of a version such as 1.4.2.
function parts(version) {
  return version.match(/^(\d+)\.(\d+)\.(\d+)$/).slice(1).map(Number);
}

console.log(parts("1.4.2"));
console.log(parts("2.0.0-beta.1"));

Was Node.js ausgibt

TypeError: Cannot read properties of null (reading 'slice')

Warum, und die Lösung

2.0.0-beta.1 ist eine gültige Version: ein Prerelease mit dem Tag beta.1. Das Muster erlaubt nur drei Zahlen, also liefert match() null, und .slice scheitert. Erlauben Sie entweder ein Tag nach einem Bindestrich, wie in /^(\d+)\.(\d+)\.(\d+)(-[0-9A-Za-z.-]+)?$/, oder prüfen Sie auf null und melden Sie die Version, die Sie nicht lesen können. Denken Sie daran: 2.0.0-beta.1 sortiert vor 2.0.0.

JavaScript im Browser: die eigene Engine Ihres Browsers in einem abgeschotteten Worker. Syntaxfehler findet acorn 8.18.0, MIT. Lizenz und Quellcode

Abschlussquiz

5 Fragen, ohne Hinweise. Ab 80 % ist die Lektion abgeschlossen.

Erledigen Sie zuerst alle Aufgaben oben, um das Abschlussquiz freizuschalten.

Problem melden

Etwas ist falsch oder unklar? Beschreiben Sie es kurz, dann wird es geprüft und korrigiert.

#

Mindestens 20 Zeichen.

Nur, wenn Sie eine Antwort wünschen.

Kernideen

major.minor.patch sagt, was sich geändert hat

Eine Version wie 1.4.2 hat drei Zahlen: major, minor und patch. npm bittet Autorinnen und Autoren, für einen abwärtskompatiblen Bugfix den Patch zu erhöhen (1.4.3), für ein abwärtskompatibles neues Feature den Minor, mit Patch auf null (1.5.0), und für eine inkompatible Änderung den Major, mit beiden auf null (2.0.0). npm empfiehlt, mit 1.0.0 zu beginnen. Die Teile werden als Zahlen verglichen, also ist 1.10.0 neuer als 1.9.0, obwohl "1.10.0" < "1.9.0" als Strings gilt. Ein Prerelease hängt nach einem Bindestrich ein Tag an, etwa 2.0.0-beta.1, und sortiert vor 2.0.0. In package.json ordnet "dependencies" jedem Paketnamen einen Versionsbereich zu, nicht eine einzelne Version.

Was ein Bereich zulässt

Eine exakte Version, "1.4.2", erlaubt nur 1.4.2. Ein Caret, "^1.4.2", erlaubt 1.4.2 bis unter 2.0.0: neue Minors und Patches, nie einen neuen Major. Eine Tilde, "~1.4.2", erlaubt nur Patches: bis unter 1.5.0. Ein x-Bereich füllt aus, was fehlt: "1.4.x" und "1.4" bedeuten 1.4.0 bis unter 1.5.0, "1.x" und "1" bis unter 2.0.0, und "*" jede Version. Unter 1.0.0 hält das Caret die am weitesten links stehende Zahl ungleich null fest: "^0.2.3" erlaubt nur 0.2.x, "^0.0.3" nur 0.0.3. Prereleases bleiben draußen, außer der Bereich nennt ein Prerelease desselben major.minor.patch: "^1.2.3" lässt 1.3.0-beta.1 nicht zu.

npm outdated berichtet, npm update installiert

npm outdated listet jede Abhängigkeit, die zurückliegt: Current ist installiert, Wanted ist die höchste Version, die Ihr Bereich erlaubt, Latest die Version, die die Registry als latest markiert. Liegt etwas zurück, endet es mit Exit-Code 1. npm update installiert Wanted: Es ändert node_modules und package-lock.json und lässt den Bereich in package.json, wie er ist; npm update --save schreibt auch ihn um, aus "^1.1.1" wird "^1.2.2". Keiner der beiden geht über Ihren Bereich hinaus. Für einen neuen Major installieren Sie ihn: npm install dep1@latest speichert "^2.0.0". npm install dep1@1.1.1 speichert "^1.1.1", npms Standard-Operator; mit --save-exact wird genau "1.1.1" gespeichert.

Quellen

Zuletzt geprüft am 30. September 2026