Aufwärmen · Aufgabe 1 von 7
// 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.
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
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Ü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.tgznpm install ../check/check-0.3.1.tgzÜben · Aufgabe 4 von 7
Ordnen Sie jedem Feld eines Paketeintrags in package-lock.json zu, was es festhält.
Üben · Aufgabe 5 von 7
Was macht der letzte Befehl?
npm install ../greet/greet-1.2.0.tgz rm package-lock.json npm ciDenksport · 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 ciAnwenden · 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.jsAusgabe
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
Hinweis 1
Object.entries(lock.packages) liefert Paare [path, entry], über die Sie laufen können.
Hinweis 2
Überspringen Sie Pfade, die nicht mit "node_modules/" beginnen. Schneiden Sie dieses Präfix ab, um den Namen zu erhalten.
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.jsPrüfungen ausführen (learnrun.js muss im selben Ordner liegen):
node --testlearnrun.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
Hinweis 1
Verteilen Sie beide Felder in ein Objekt: {...pkg.dependencies, ...pkg.devDependencies}. undefined zu verteilen ist erlaubt.
Hinweis 2
Machen Sie dasselbe mit lock.packages[""] und sammeln Sie dann alle Namen beider Objekte in einem Set.
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.jsPrüfungen ausführen (learnrun.js muss im selben Ordner liegen):
node --testlearnrun.js herunterladenHä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 fromWarum, 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 objectWarum, 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.