Zum Inhalt springen
aviral gupta

// B2.2 · ca. 36 Min. · Einstieg

Installieren, package-lock.json und npm ci

Nach dieser Lektion fügen Sie Pakete hinzu und entfernen sie, lesen package-lock.json, installieren ein Projekt mit npm ci exakt neu und starten Paketbefehle mit npx.

Lektion 2 von 5 in B2 npm und Pakete

Danach können Sie

  • Pakete mit npm install, --save-dev und npm uninstall hinzufügen und entfernen und sagen, wo sie landen
  • package-lock.json lesen: version, resolved, integrity und dev, und warum sie eingecheckt wird
  • Mit npm ci exakt neu installieren, vorhersagen, wann es abbricht, und Paketbefehle mit npx starten
  1. Aufwärmen · Aufgabe 1 von 7

    Aufwärmen aus Modul B1: Sie haben node:fs und node:util importiert, ohne etwas zu installieren. Welcher dieser Imports schlägt in einem neuen Projekt, in dem noch nichts installiert ist, fehl, bis Sie ein Paket mit npm installieren?

  2. Vorhersagen · Aufgabe 2 von 7

    Sagen Sie voraus, bevor Sie weiterlesen. greet-1.2.0.tgz ist ein mit npm pack erzeugtes Paket. app/package.json hat noch keine "dependencies". Sie führen diesen Befehl in app aus. Was steht danach unter "dependencies"?

    npm install ../greet/greet-1.2.0.tgz
  3. Üben · Aufgabe 3 von 7

    check ist ein Werkzeug, das Sie nur beim Entwickeln brauchen. Ergänzen Sie die Option, damit npm es unter "devDependencies" speichert.

    npm install ____ ../check/check-0.3.1.tgz
    npm install ../check/check-0.3.1.tgz
  4. Üben · Aufgabe 4 von 7

    Ordnen Sie jedem Feld eines Paketeintrags in package-lock.json zu, was es festhält.

  5. Üben · Aufgabe 5 von 7

    Was macht der letzte Befehl?

    npm install ../greet/greet-1.2.0.tgz
    rm package-lock.json
    npm ci
  6. Denksport · Aufgabe 6 von 7

    Knobelaufgabe. greet ist installiert und steht in package-lock.json. Dann trägt jemand "check": "file:../check/check-0.3.1.tgz" von Hand im Editor unter "dependencies" ein und führt npm install nicht aus. Was macht dieser Befehl?

    npm ci
  7. Anwenden · Aufgabe 7 von 7

    Mini-Aufgabe, offline. Legen Sie einen Ordner greet an, mit package.json ("name": "greet", "version": "1.2.0", "type": "module", "exports": "./index.js") und einer index.js, die greet(name) exportiert. Führen Sie darin npm pack aus. Legen Sie daneben app mit npm init -y und "type": "module" an, installieren Sie ../greet/greet-1.2.0.tgz und importieren Sie greet in main.js. Führen Sie es aus. Löschen Sie dann node_modules, führen Sie npm ci aus und starten Sie main.js erneut.

    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

Was npm install festhält

Dieses Programm spielt Ihr Terminal und braucht kein Netzwerk. Es erzeugt ein winziges Paket, greet 1.2.0, packt es mit npm pack und installiert das Tarball in eine App in einem temporären Ordner. Dann gibt es aus, was npm in package.json und package-lock.json geschrieben hat, und startet die App, die greet über seinen Paketnamen importiert.

main.js

// Plays the part of your terminal: packs a tiny local package, installs it
// into an app and shows what npm recorded. No registry, no network.
import {execSync} from "node:child_process";
import {mkdirSync, mkdtempSync, readFileSync, writeFileSync} from "node:fs";
import {tmpdir} from "node:os";
import {join} from "node:path";

const root = mkdtempSync(join(tmpdir(), "deps-"));
const put = (file, text) => {
  mkdirSync(join(root, file, ".."), {recursive: true});
  writeFileSync(join(root, file), text);
};
const run = (command, dir) => execSync(command, {cwd: join(root, dir), encoding: "utf8"});

// A package of our own, greet 1.2.0, packed into greet-1.2.0.tgz.
put("greet/package.json", JSON.stringify({name: "greet", version: "1.2.0", type: "module", exports: "./index.js"}));
put("greet/index.js", 'export const greet = (name) => "Hello, " + name + "!";\n');
run("npm pack --silent", "greet");

// The app that installs it.
put("app/package.json", JSON.stringify({name: "app", version: "1.0.0", private: true, type: "module"}));
put("app/main.js", 'import {greet} from "greet";\nconsole.log(greet("Ada"));\n');
run("npm install --silent --no-audit --no-fund ../greet/greet-1.2.0.tgz", "app");

const pkg = JSON.parse(readFileSync(join(root, "app/package.json"), "utf8"));
const lock = JSON.parse(readFileSync(join(root, "app/package-lock.json"), "utf8"));
const entry = lock.packages["node_modules/greet"];
console.log("dependencies:", pkg.dependencies);
console.log("lockfileVersion:", lock.lockfileVersion);
console.log("locked:", entry.version, "from", entry.resolved);
console.log("integrity:", entry.integrity.slice(0, 7) + "...");
console.log(run("node main.js", "app").trim());

Ausführen mit

node main.js

Ausgabe

dependencies: { greet: 'file:../greet/greet-1.2.0.tgz' }
lockfileVersion: 3
locked: 1.2.0 from file:../greet/greet-1.2.0.tgz
integrity: sha512-...
Hello, Ada!
  • package.json behält, was Sie verlangt haben: den Pfad des Tarballs.
  • package-lock.json behält, was installiert wurde: die exakte Version 1.2.0, ihre Herkunft und einen sha512-Hash der Datei.
  • import {greet} from "greet" funktioniert, weil npm das Paket nach app/node_modules/greet entpackt hat.
  • Löschen Sie app/node_modules und führen Sie in app npm ci aus: Derselbe Baum entsteht aus dem Lock neu.

Übungen

Übung 1 von 2

Die Versionen in einer Lock-Datei

Schreiben Sie lockedVersions(lock). Die Funktion erhält eine eingelesene package-lock.json und gibt ein Objekt zurück, das jedem Paketnamen der obersten Ebene seine festgehaltene Version zuordnet, etwa {greet: "1.2.0"}. Pakete der obersten Ebene sind die Schlüssel von lock.packages, die mit node_modules/ beginnen und kein weiteres /node_modules/ enthalten. Lassen Sie Namen mit Scope wie @acme/log ganz. Überspringen Sie das Projekt selbst, den Schlüssel "".

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

    Object.entries(lock.packages) liefert Paare [path, entry], über die Sie laufen können.

  2. Hinweis 2

    Überspringen Sie Pfade, die nicht mit "node_modules/" beginnen. Schneiden Sie dieses Präfix ab, um den Namen zu erhalten.

  3. Hinweis 3

    Ein Name, der noch "/node_modules/" enthält, gehört zu einem verschachtelten Paket: überspringen. "@acme/log" enthält einen Schrägstrich, aber kein "/node_modules/", und bleibt daher.

Eine Lösung zeigen

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

// Maps each top-level package in a parsed package-lock.json to its version.
export function lockedVersions(lock) {
  const versions = {};
  for (const [path, entry] of Object.entries(lock.packages ?? {})) {
    if (!path.startsWith("node_modules/")) continue;
    const name = path.slice("node_modules/".length);
    if (name.includes("/node_modules/")) continue;
    versions[name] = entry.version;
  }
  return versions;
}

const lock = {lockfileVersion: 3, packages: {"": {name: "app"}, "node_modules/greet": {version: "1.2.0"}}};
console.log(lockedVersions(lock));
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

// Maps each top-level package in a parsed package-lock.json to its version.
export function lockedVersions(lock) {
  const versions = {};
  return versions;
}

const lock = {lockfileVersion: 3, packages: {"": {name: "app"}, "node_modules/greet": {version: "1.2.0"}}};
console.log(lockedVersions(lock));

main.test.js

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

const lock = {
  name: 'app',
  lockfileVersion: 3,
  packages: {
    '': {name: 'app', version: '1.0.0'},
    'node_modules/check': {version: '0.3.1', dev: true},
    'node_modules/greet': {version: '1.2.0'},
    'node_modules/@acme/log': {version: '2.0.0'},
    'node_modules/greet/node_modules/tiny': {version: '0.1.0'}
  }
};

test('ordnet jedem Paket der obersten Ebene seine Version zu', () => {
  const got = lockedVersions(lock);
  assert.deepEqual(got, {check: '0.3.1', greet: '1.2.0', '@acme/log': '2.0.0'}, `lockedVersions(lock) lieferte ${JSON.stringify(got)}`);
});

test('liefert ein leeres Objekt, wenn nichts installiert ist', () => {
  const got = lockedVersions({lockfileVersion: 3, packages: {'': {name: 'app'}}});
  assert.deepEqual(got, {}, `ein Lock nur mit dem Projekt ergab ${JSON.stringify(got)}`);
});

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

Würde npm ci sich beschweren?

npm ci bricht ab, wenn package.json und das Lock nicht übereinstimmen. Schreiben Sie outOfSync(pkg, lock), eine einfache Version dieser Prüfung. Sammeln Sie, was package.json unter "dependencies" und "devDependencies" verlangt und was der Wurzeleintrag des Locks, lock.packages[""], in denselben beiden Feldern festhält. Geben Sie die sortierten Namen zurück, deren Angabe abweicht oder die nur auf einer Seite stehen. Synchron ergibt [].

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

    Verteilen Sie beide Felder in ein Objekt: {...pkg.dependencies, ...pkg.devDependencies}. undefined zu verteilen ist erlaubt.

  2. Hinweis 2

    Machen Sie dasselbe mit lock.packages[""] und sammeln Sie dann alle Namen beider Objekte in einem Set.

  3. Hinweis 3

    Behalten Sie einen Namen, wenn asked[name] !== locked[name] gilt; eine fehlende Seite ist undefined und zählt daher als verschieden. Sortieren Sie das Ergebnis.

Eine Lösung zeigen

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

// Names that package.json and the root entry of package-lock.json disagree on.
export function outOfSync(pkg, lock) {
  const root = lock.packages?.[""] ?? {};
  const asked = {...pkg.dependencies, ...pkg.devDependencies};
  const locked = {...root.dependencies, ...root.devDependencies};
  const names = new Set([...Object.keys(asked), ...Object.keys(locked)]);
  return [...names].filter((name) => asked[name] !== locked[name]).sort();
}

const pkg = {dependencies: {greet: "file:../greet/greet-1.2.0.tgz", check: "file:../check/check-0.3.1.tgz"}};
const lock = {packages: {"": {dependencies: {greet: "file:../greet/greet-1.2.0.tgz"}}}};
console.log(outOfSync(pkg, lock));
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

// Names that package.json and the root entry of package-lock.json disagree on.
export function outOfSync(pkg, lock) {
  return [];
}

const pkg = {dependencies: {greet: "file:../greet/greet-1.2.0.tgz", check: "file:../check/check-0.3.1.tgz"}};
const lock = {packages: {"": {dependencies: {greet: "file:../greet/greet-1.2.0.tgz"}}}};
console.log(outOfSync(pkg, lock));

main.test.js

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

const GREET = 'file:../greet/greet-1.2.0.tgz';
const CHECK = 'file:../check/check-0.3.1.tgz';
const lock = {lockfileVersion: 3, packages: {'': {name: 'app', dependencies: {greet: GREET}, devDependencies: {check: CHECK}}}};

test('liefert [], wenn package.json und das Lock übereinstimmen', () => {
  const got = outOfSync({dependencies: {greet: GREET}, devDependencies: {check: CHECK}}, lock);
  assert.deepEqual(got, [], `outOfSync lieferte ${JSON.stringify(got)} bei übereinstimmenden Dateien`);
});

test('nennt eine von Hand in package.json ergänzte Abhängigkeit', () => {
  const got = outOfSync({dependencies: {greet: GREET, extra: 'file:../extra'}, devDependencies: {check: CHECK}}, lock);
  assert.deepEqual(got, ['extra'], `outOfSync lieferte ${JSON.stringify(got)}`);
});

test('nennt Abhängigkeiten, deren Angabe sich geändert hat oder die entfernt wurden', () => {
  const got = outOfSync({dependencies: {greet: 'file:../greet/greet-1.3.0.tgz'}}, lock);
  assert.deepEqual(got, ['check', 'greet'], `outOfSync lieferte ${JSON.stringify(got)}; erwartet: beide Namen, sortiert`);
});

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 frischen Klon vor dem Installieren starten

// main.js of a project you just cloned. package.json lists greet,
// but node_modules is not in the repository.
import {greet} from "greet";

console.log(greet("Ada"));

Was Node.js ausgibt

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

Warum, und die Lösung

Ein bloßer Name wie greet wird in node_modules gesucht, und node_modules ist nicht eingecheckt. Führen Sie nach dem Klonen npm ci aus, das genau installiert, was package-lock.json festhält, oder npm install, solange es noch kein Lock gibt. Starten Sie das Programm dann erneut.

Ein Paket in "packages" über seinen Namen suchen

const lock = {
  name: "app",
  version: "1.0.0",
  lockfileVersion: 3,
  requires: true,
  packages: {
    "": {
      name: "app",
      version: "1.0.0",
      dependencies: {greet: "file:../greet/greet-1.2.0.tgz"},
      devDependencies: {check: "file:../check/check-0.3.1.tgz"}
    },
    "node_modules/check": {version: "0.3.1", resolved: "file:../check/check-0.3.1.tgz", integrity: "sha512-4izY", dev: true},
    "node_modules/greet": {version: "1.2.0", resolved: "file:../greet/greet-1.2.0.tgz", integrity: "sha512-4I5b"}
  }
};

// The keys of "packages" are paths, not names.
console.log(lock.packages["greet"].version);

Was Node.js ausgibt

TypeError: Cannot read properties of undefined (reading 'version')

Warum, und die Lösung

Die Schlüssel von "packages" sind Ordnerpfade: "" für das Projekt, "node_modules/greet" für greet. lock.packages["greet"] ist undefined, daher schlägt das Lesen von .version fehl. Verwenden Sie lock.packages["node_modules/greet"].version, also "1.2.0".

Den alten Abschnitt "dependencies" lesen

const lock = {
  name: "app",
  version: "1.0.0",
  lockfileVersion: 3,
  requires: true,
  packages: {
    "": {
      name: "app",
      version: "1.0.0",
      dependencies: {greet: "file:../greet/greet-1.2.0.tgz"},
      devDependencies: {check: "file:../check/check-0.3.1.tgz"}
    },
    "node_modules/check": {version: "0.3.1", resolved: "file:../check/check-0.3.1.tgz", integrity: "sha512-4izY", dev: true},
    "node_modules/greet": {version: "1.2.0", resolved: "file:../greet/greet-1.2.0.tgz", integrity: "sha512-4I5b"}
  }
};

// Code written for old lock files reads lock.dependencies.
console.log(Object.keys(lock.dependencies));

Was Node.js ausgibt

TypeError: Cannot convert undefined or null to object

Warum, und die Lösung

Der Abschnitt "dependencies" ist Altlast für lockfileVersion 1. Ab npm 9 wird lockfileVersion 3 geschrieben, das nur "packages" kennt; lock.dependencies ist daher undefined, und Object.keys kann damit nichts anfangen. Lesen Sie stattdessen lock.packages und dessen Schlüssel "node_modules/...".

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

npm install legt Pakete in node_modules ab

npm install <paket> installiert ein Paket und alles, wovon es abhängt, in node_modules und trägt es in package.json unter "dependencies" ein; mit --save-dev (-D) landet es unter "devDependencies". Ohne Namen installiert npm install alles, was package.json aufführt. Pakete kommen meist aus der npm-Registry; diese Lektion nutzt ein mit npm pack erzeugtes Tarball, damit alles offline geht: npm install ../greet/greet-1.2.0.tgz wird als "file:../greet/greet-1.2.0.tgz" gespeichert. Befehle, die ein Paket mitbringt, werden nach node_modules/.bin verlinkt; dort finden npm-Skripte und npx sie, Ihre Shell nicht. npm uninstall greet entfernt es aus node_modules, package.json und package-lock.json.

package-lock.json hält den exakten Baum fest

npm schreibt package-lock.json, sobald es node_modules oder package.json ändert. Sie checken die Datei ein, node_modules dagegen nicht: Mit dem Lock bauen Kolleginnen, Kollegen und CI denselben Baum nach. Unter "packages" steht der Schlüssel "" für Ihr Projekt und "node_modules/greet" für ein installiertes Paket: "version" ist die exakte Version, "resolved" die Herkunft, "integrity" ein sha512-Hash der Paketdatei, und "dev": true markiert, was nur devDependencies brauchen. Ab npm 9 steht dort "lockfileVersion": 3, ohne den alten Abschnitt "dependencies". Passt das Lock zu package.json, nimmt npm install die festgehaltenen Versionen; widersprechen sie sich, gewinnt package.json und npm aktualisiert das Lock.

npm ci installiert genau, was im Lock steht

npm ci ist die saubere Installation für CI und frische Klone. Ohne package-lock.json bricht es ab. Es löscht zuerst node_modules, installiert genau, was das Lock aufführt, und schreibt nie in package.json oder das Lock. Passen beide nicht zusammen, etwa weil jemand package.json von Hand geändert hat, bricht es ab, statt das Lock anzupassen: Führen Sie npm install aus und checken Sie das neue Lock ein. Es installiert nur ganze Projekte, nie ein einzelnes Paket, und --omit=dev lässt devDependencies weg. npx hello startet den Befehl hello aus einem installierten Paket; ist ein Paket nicht installiert, fragt npx zuerst und holt es dann aus der Registry in den Cache von npm.

Quellen

Zuletzt geprüft am 30. September 2026