Zum Inhalt springen
aviral gupta

// I3.4 · ca. 36 Min. · Aufbau

npm: Abhängigkeiten, Lock-Datei und Skripte

Nach dieser Lektion fügen Sie einem Projekt mit npm Pakete hinzu, nutzen sie in Ihrem Code per Namen und starten die Aufgaben des Projekts als npm-Skripte.

Lektion 4 von 5 in I3 Module, Node.js und npm

Danach können Sie

  • Pakete mit npm install, -D und -E installieren und sagen, was package.json, package-lock.json und node_modules enthalten
  • Ein installiertes Paket per Namen importieren und den Befehl eines Pakets über ein Skript oder npx nutzen
  • Skripte mit npm run und npm test starten, pre- und post-Skripte vorhersagen und Argumente nach -- übergeben
  1. Aufwärmen · Aufgabe 1 von 7

    Zum Aufwärmen aus Lektion I3.3. package.json sagt "module". Was gibt node tools.cjs aus?

    // package.json
    {"type": "module"}
    
    // tools.cjs
    const path = require("node:path");
    console.log(path.basename("/docs/notes.txt"));
  2. Vorhersagen · Aufgabe 2 von 7

    Sagen Sie es vorher, bevor Sie weiterlesen. Das neueste tiny-case in der Registry ist 2.1.0. Was steht nach diesem Befehl in package.json unter "dependencies"?

    npm install tiny-case
  3. Üben · Aufgabe 3 von 7

    say-hi brauchen Sie nur während der Entwicklung. Ergänzen Sie das Flag, das es unter devDependencies speichert.

    npm install ____ say-hi
    npm install say-hi
  4. Üben · Aufgabe 4 von 7

    Ordnen Sie jedem Teil eines Projekts zu, was er nach npm install enthält.

  5. Üben · Aufgabe 5 von 7

    say-hi ist mit -D installiert und bringt einen Befehl say-hi mit. Was ist die letzte Zeile, die npm run greet ausgibt?

    {
      "scripts": {
        "greet": "say-hi there"
      }
    }
  6. Denksport · Aufgabe 6 von 7

    Knobelaufgabe. step.js gibt das Wort aus, das es bekommt. Welche Wörter gibt npm test in welcher Reihenfolge aus?

    // package.json
    {
      "name": "app",
      "version": "1.0.0",
      "scripts": {
        "posttest": "node step.js post",
        "test": "node step.js test",
        "pretest": "node step.js pre"
      }
    }
  7. Anwenden · Aufgabe 7 von 7

    Kleine Aufgabe: Führen Sie in einem neuen Ordner npm init -y aus, setzen Sie "type": "module" und installieren Sie ein Paket, das Text in einen Slug verwandelt (auf Ihrem Rechner zum Beispiel slugify; hier tiny-case). Schreiben Sie slug.js, das das Paket per Namen importiert und einen Slug aus seinen Argumenten ausgibt, und ein Skript "slug": "node slug.js". Starten Sie npm run slug -- My First Post. Löschen Sie dann node_modules und führen Sie npm ci aus.

    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

Ein installiertes Paket nutzen

In diesem Projekt lief npm install tiny-case, package.json führt also "tiny-case": "^2.1.0" unter dependencies, und das Paket liegt in node_modules/tiny-case. main.js importiert es per reinem Namen. Die zwei Dateien des Pakets sind ebenfalls gezeigt, damit Sie sehen, was Node.js dort findet: Seine package.json sagt über "exports", welche Datei geladen wird.

main.js

// npm install tiny-case put the package into node_modules/tiny-case.
// A bare name (no ./) is looked up in node_modules:
import {kebab} from "tiny-case";

const titles = ["Hello World", "  ES Modules in Node  "];
for (const title of titles) {
  console.log(kebab(title));
}

node_modules/tiny-case/package.json

{"name": "tiny-case", "version": "2.1.0", "type": "module", "exports": "./index.js"}

node_modules/tiny-case/index.js

export const kebab = (text) => text.trim().toLowerCase().split(/\s+/).join("-");

Ausführen mit

node main.js

Ausgabe

hello-world
es-modules-in-node
  • Sie schreiben nie selbst Dateien in node_modules: Das tut npm install, und es darf sie jederzeit ersetzen.
  • Ein reiner Name wie "tiny-case" meint ein Paket; "./tiny-case.js" meinte Ihre eigene Datei.
  • Wegen "exports" lässt sich nur "tiny-case" selbst importieren, nicht "tiny-case/index.js".

Übungen

Übung 1 von 2

Wo npm ein Paket speichert

addDependency(pkg, name, version, options) gibt ein neues package.json-Objekt mit dem hinzugefügten Paket zurück, so wie npm install es speichern würde: unter devDependencies, wenn options.dev wahr ist, sonst unter dependencies; mit der genauen Version, wenn options.exact wahr ist, sonst mit ^ davor. pkg darf sich nicht ändern. Der Startcode ignoriert beide Optionen.

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

    Wählen Sie zuerst den Feldnamen aus options.dev, dann den Bereich aus options.exact.

  2. Hinweis 2

    Ein berechneter Schlüssel, [field]: …, legt das neue Objekt unter dem gewählten Feld ab.

  3. Hinweis 3

    Spreaden Sie pkg[field] in ein neues Objekt, damit das Original unverändert bleibt.

Eine Lösung zeigen

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

export function addDependency(pkg, name, version, options = {}) {
  const field = options.dev ? "devDependencies" : "dependencies";
  const range = options.exact ? version : "^" + version;
  return {...pkg, [field]: {...pkg[field], [name]: range}};
}
Auf dem eigenen Computer ausführen

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

main.js

export function addDependency(pkg, name, version, options = {}) {
  const dependencies = {...pkg.dependencies, [name]: "^" + version};
  return {...pkg, dependencies};
}

main.test.js

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

const base = {name: 'app', version: '1.0.0', dependencies: {'tiny-case': '^2.1.0'}};

test('Ein Paket landet mit Caret unter dependencies', () => {
  const pkg = addDependency(base, 'chalk', '5.6.2');
  assert.deepEqual(pkg.dependencies, {'tiny-case': '^2.1.0', chalk: '^5.6.2'}, `dependencies: ${JSON.stringify(pkg.dependencies)}`);
});

test('Mit dev landet es unter devDependencies', () => {
  const pkg = addDependency(base, 'say-hi', '1.0.3', {dev: true});
  assert.deepEqual(pkg.devDependencies, {'say-hi': '^1.0.3'}, `devDependencies: ${JSON.stringify(pkg.devDependencies)}`);
  assert.deepEqual(pkg.dependencies, {'tiny-case': '^2.1.0'}, `dependencies: ${JSON.stringify(pkg.dependencies)}`);
});

test('Mit exact hat die Version kein Caret', () => {
  const pkg = addDependency(base, 'chalk', '5.6.2', {exact: true});
  assert.equal(pkg.dependencies.chalk, '5.6.2', `chalk: ${pkg.dependencies.chalk}`);
});

test('Das ursprüngliche Objekt bleibt unverändert', () => {
  addDependency(base, 'say-hi', '1.0.3', {dev: true});
  assert.deepEqual(base, {name: 'app', version: '1.0.0', dependencies: {'tiny-case': '^2.1.0'}}, `base: ${JSON.stringify(base)}`);
});

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

Welche Befehle npm run startet

runOrder(scripts, name, args) gibt die Befehlszeilen zurück, die npm run name -- …args starten würde, in Reihenfolge: das pre-Skript, falls es eines gibt, dann das Skript selbst mit den angehängten Argumenten, dann das post-Skript, falls es eines gibt. Die Argumente gehen nur an das Skript selbst. Gibt es das Skript nicht, werfen Sie einen Error mit der Meldung Missing script: "name". Der Startcode gibt nur das Skript zurück.

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.hasOwn(scripts, "pre" + name) sagt Ihnen, ob es ein pre-Skript gibt.

  2. Hinweis 2

    Hängen Sie die Argumente nur an den mittleren Befehl: [scripts[name], ...args].join(" ").

  3. Hinweis 3

    Prüfen Sie zuerst den Namen und werfen Sie, bevor Sie die Liste bauen.

Eine Lösung zeigen

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

export function runOrder(scripts, name, args = []) {
  if (!Object.hasOwn(scripts, name)) {
    throw new Error('Missing script: "' + name + '"');
  }
  const order = [];
  if (Object.hasOwn(scripts, "pre" + name)) order.push(scripts["pre" + name]);
  order.push([scripts[name], ...args].join(" "));
  if (Object.hasOwn(scripts, "post" + name)) order.push(scripts["post" + name]);
  return order;
}
Auf dem eigenen Computer ausführen

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

main.js

export function runOrder(scripts, name, args = []) {
  return [[scripts[name], ...args].join(" ")];
}

main.test.js

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

const scripts = {prebuild: 'node clean.js', build: 'node build.js', postbuild: 'node zip.js', test: 'node --test'};

test('Ein Skript ohne pre und post läuft allein', () => {
  const order = runOrder(scripts, 'test');
  assert.deepEqual(order, ['node --test'], `order: ${JSON.stringify(order)}`);
});

test('Pre- und post-Skripte laufen um das Skript herum', () => {
  const order = runOrder(scripts, 'build');
  assert.deepEqual(order, ['node clean.js', 'node build.js', 'node zip.js'], `order: ${JSON.stringify(order)}`);
});

test('Argumente gehen nur an das Skript selbst', () => {
  const order = runOrder(scripts, 'build', ['--watch']);
  assert.deepEqual(order, ['node clean.js', 'node build.js --watch', 'node zip.js'], `order: ${JSON.stringify(order)}`);
});

test('Ein fehlendes Skript wirft Missing script', () => {
  assert.throws(() => runOrder(scripts, 'biuld'), {message: 'Missing script: "biuld"'}, 'runOrder muss bei einem Namen werfen, der nicht in scripts steht');
});

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

Code vor npm install ausführen

import {kebab} from "tiny-case";
console.log(kebab("Hello World"));

Was Node.js ausgibt

Error [ERR_MODULE_NOT_FOUND]: Cannot find package 'tiny-case' imported from

Warum, und die Lösung

package.json führt tiny-case vielleicht auf, aber das Paket liegt nicht in node_modules: Nach einem frischen git clone ist node_modules leer. Führen Sie zuerst npm install (oder npm ci) aus. Derselbe Fehler erscheint bei einem Tippfehler im Paketnamen.

Eine Datei innerhalb eines Pakets importieren

import {kebab} from "tiny-case/index.js";
console.log(kebab("Hello World"));

Was Node.js ausgibt

Error [ERR_PACKAGE_PATH_NOT_EXPORTED]: Package subpath './index.js' is not defined by "exports"

Warum, und die Lösung

Das Feld "exports" des Pakets führt auf, was Sie importieren dürfen, und der Pfad seiner eigenen index.js steht nicht auf dieser Liste. Importieren Sie das Paket per Namen: import {kebab} from "tiny-case". Pfade in ein Paket können sich mit jedem Update ändern; die exportierten Namen sind das, was das Paket verspricht.

Ein Default-Import aus einem Paket mit benannten Exporten

import kebab from "tiny-case";
console.log(kebab("Hello World"));

Was Node.js ausgibt

SyntaxError: The requested module 'tiny-case' does not provide an export named 'default'

Warum, und die Lösung

tiny-case ist ein ES-Modul, das kebab benannt exportiert und keinen Default-Export hat. Nutzen Sie Klammern: import {kebab} from "tiny-case". Die README eines Pakets zeigt, wie es zu importieren ist; steht dort import x from …, hat es einen Default-Export.

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

Eine Installation schreibt drei Dinge

npm install tiny-case lädt das Paket nach node_modules/tiny-case, schreibt "tiny-case": "^2.1.0" unter dependencies in package.json und hält den genauen installierten Baum in package-lock.json fest. Das ^ erlaubt spätere 2.x-Versionen. Mit -D landet das Paket stattdessen in devDependencies: Werkzeuge, die Sie nur zur Entwicklung brauchen, etwa Test-Runner. -E speichert die genaue Version ohne ^. Halten Sie package.json und package-lock.json in Git, nie node_modules: npm install oder npm ci baut es neu auf.

Ein Paket per Namen nutzen

In Ihrem Code verwendet import {kebab} from "tiny-case" einen reinen Namen, ohne ./ und ohne Dateiendung. Node.js sucht ihn in node_modules, und die eigene package.json des Pakets sagt, welche Datei geladen wird: Ihr Feld "exports" sperrt auch Pfade, die es nicht aufführt, etwa "tiny-case/index.js". Ein Paket kann außerdem einen Befehl mitbringen, etwa say-hi, den npm nach node_modules/.bin verlinkt. Ihre Shell sucht dort nicht, npm-Skripte und npx dagegen schon.

Skripte sind benannte Befehle

Das Objekt "scripts" in package.json benennt Befehle: "test": "node --test". npm run test führt ihn aus; test und start funktionieren auch ohne run. Gibt es prebuild oder postbuild, führt npm run build sie vor und nach build aus. Argumente nach -- gehen an das Skript: npm run build -- --watch. Sie erreichen nur dieses Skript, nicht seine pre- und post-Skripte. Skripte laufen immer im Ordner der package.json, und ein fehlender Name scheitert mit Missing script.

Quellen

Zuletzt geprüft am 5. Oktober 2026