Node, npm und das JavaScript-Ökosystem werden, gerade in der Enterprise-Entwicklung, gerne belächelt. So wurde das Thema z.B. bei @autoweird.fm intensiv diskutiert . Trotzdem führt momentan kein Weg an modernen Web-Anwendungen vorbei. Das Problem: npm und package.json sind für viele eine Blackbox, die ich in diesem Artikel ein wenig demystifizieren möchte.
Projekt-Setup
Fangen wir ganz vorne an: Wenn ein neues Projekt angelegt wird, kann man natürlich eine alte package.json kopieren, selbige von Hand schreiben oder aber es etwas einfacher angehen: Mit npm init
(docs.npmjs.com/cli/init ) wird ein Dialog gestartet, in dem man folgende Dinge über das eigene Projekt angeben kann:
- Name des Packages
- Version
- Beschreibung
- Einstiegspunkt
- Test-Befehl
- Git Repository
- Keywords (für die npm-Suche)
- Autor
- Lizenz
Hier schlägt npm immer einen sinnvollen Standard vor, und man kann viele dieser Standards einfach akzeptieren. Noch einfacher ist es npm init -y
(oder --yes
) zu nutzen, damit npm einfach eine package.json mit diesen Standards anlegt. Um zu vermeiden, dass ein Modul versehentlich veröffentlicht wird, kann man dieses in der package.json mit “private”: true
entsprechend auszeichnen.
Dependencies
Libraries und Frameworks lassen sich mit npm install
(docs.npmjs.com/cli/install ) installieren. Auch hier gibt es einen kürzeren Befehl: npm i
.
Neben den normalen Abhängigkeiten erlaubt es npm auch, Dev-Dependencies zu installieren (npm install --save-dev
oder kurz: npm i -D
) . Dev-Dependencies sind alle Abhängigkeiten, die nur während der Entwicklung, nicht aber zur Laufzeit benötigt werden.
Beispiele für solche Libraries sind der Testrunner, Buildtools und Linter. Was hier oft vergessen wird: In vielen Frontend-Projekten werden die im Einsatz befindlichen Libraries und Frameworks oft im Rahmen des builds in eine oder mehrere JavaScript-Dateien gepackt. In diesem Fall sind dann auch, streng genommen, React, Angular und Co. Dev-Dependencies. Die richtige Verwendung von Dependencies kann die Größe von npm-Modulen erheblich beeinflussen.
In Produktion
Wenn ihr npm mit dem Flag --production
nutzt oder die NODE_ENV
-Umgebungsvariable den Wert “production” hat, wird npm keine Dev-Depenencies installieren.
Versionen
Auch wenn einige Libraries sich nicht daran halten: npm setzt stark auf Semantic Versioning und bietet euch zahlreiche Optionen, dies zu nutzen. Trotzdem muss hier klar darauf hingewiesen werden, dass man je nach verwendeter Library vorsichtig mit den Möglichkeiten von npm umgehen muss.
Wer ganz sicher gehen will, trägt immer eine feste Version in die package.json ein. Der Standard bei npm ist ein Zirkumflex vor der Version (^
).
Versionen in npm definieren
- fixe Version, wie z.B. “1.0.1”: In diesem Fall installiert npm auch nur die dort angegebene Version.
- Versionsbereich, wie z.B. “>1.0.0”: Es ist möglich, hier einen größeren Bereich abzudecken. Die möglichen Vergleichsoperatoren sind “<”, “<="”," “="">”, “>=” und “=”.
- Tags: Es ist möglich, einer Version einen Tag wie “-beta” oder “-alpha.3” zu geben, um Versionen, die nicht stabil sind, zu veröffentlichen.
- Patchversion, mit z.B “~1.0.0”: In diesem Fall wird die aktuellste minor-Version genutzt. Dies ist in vielen Fällen ein sinnvoller Standard, wenn die im Einsatz befindliche Library semantic versioning korrekt umsetzt.
- Die erste Zahl (ungleich 0) bleibt stabil mit Zirkumflex: “^1.0.0”: Hier sind also i.d.R. auch Minor Upgrades Teil der unterstützten Version. Bei “^0.0.1” würde sich die Version allerdings nicht verändern, da die erste Zahl, die nicht 0 ist, in diesem Fall die Minor-Version ist.
Dependencies aktualisieren
Gerade während der Entwicklung macht es häufig Sinn, die verwendeten Dependencies auf dem aktuellen Stand zu halten. Mit Hilfe von npm outdated
kann man sich eine Liste der Dependencies anzeigen lassen und diese dann mit npm update
auch auf die aktuelle Version aktualisieren.
package-lock.json und npm-shrinkwrap.json
Immer wenn über npm der Inhalt des node_modules-Ordner oder die package.json geändert wird, wird die package-lock.json verändert (bzw. erzeugt). In dieser wird der komplette Dependency-Tree aufgeführt, um
die Installation unabhängig von inzwischen aktuelleren Versionen zu reproduzieren. Diese Datei sollte unter Versionskontrolle gehalten werden, damit im Team die gleichen Dependencies verwendet werden. Wichtig hier: Es geht dabei nicht darum, Versionen festzuzurren (dies könnt ihr mit shrinkwrap), sondern vielmehr darum die Arbeit im Team zu vereinfachen.
Wenn zwei Entwickler neue Abhängigkeiten einbringen, kann dies Konflikte in der package-lock.json verursachen. Daher muss hier besonders darauf geachtet werden, dass die package-lock.json nicht einfach blind gemerged oder überschrieben wird, sondern die Updates kontrolliert sequenziell eingepflegt werden. Hier empfiehlt es sich, diese Situationen dazu zu nutzen, generell mit Hilfe von npm outdated
die Anwendung auf veraltete Abhängigkeiten zu überprüfen und Patches bzw. Minor Updates einzupflegen. Im Zuge dieser Aktualisierung kann dann auch eine neue, frische, package-lock.json erstellt werden.
Mit npm shrinkwrap
wird neben der package-lock.json eine npm-shrinkwrap.json angelegt. Diese entspricht inhaltlich der package-lock.json, wird aber immer der package-lock.json vorgezogen. Wenn diese Datei mit dem Modul veröffentlicht wird, stellt dies sicher, dass die dort aufgeführten Dependencies unverändert bleiben. Nützlich ist dies vor allem bei globalen Libraries wie Kommandozeilen-Tools. Bei Libraries und Frameworks, bei denen Nutzer die transitiven Abhängigkeiten noch selber pflegen können sollen, sollte auf die Datei verzichtet werden.
Skripte
Das npm CLI kann auch als Taskrunner verwendet werden. Dazu können in der package.json Skripte (docs.npmjs.com/misc/scripts ) definiert werden, die über npm ausführbar sind. Über diese ist es auch möglich, Hooks für Lifecycle Events zu definieren, die npm von sich aus mitbringt (z.B. mit Skripten für preinstall
oder postinstall
).
Lifecycle events
- publish
- install
- uninstall
- version
- test
- stop
- start
- restart
Bei all diesen Lifecycle Events können noch “pre” oder “post” vorangestellt werden. Ebenso ist es möglich, npm-Skripte in anderen Skripten wiederzuverwenden. Ein Beispiel zu npm version
sieht dann so aus:
1"scripts": { 2 "preversion": "npm test", 3 "version": "npm run build && git add -A dist", 4 "postversion": "git push && git push --tags && rm -rf build/temp" 5}
Ausgeführt werden die Skripte dann mit npm
oder auch mit npm run
. Es ist nämlich durchaus möglich, hier eigene Skripte zu definieren:
1"scripts": { 2 "build": "webpack" 3}
Dieses Skript könnte man dann mit npm run build
starten.
Zusätzlich erweitert npm die PATH
-Umgebungsvariable für die in der package.json definierten Skripte um node_modules/.bin, hier werden alle ausführbaren Skripte gelinkt. Dadurch können in der package.json auch Skripte wie “lint”: “eslint”
definiert werden.
Durch dieses Feature muss so gut wie nie eine Library global installiert werden, was früher durchaus bei Lintern oder anderen Tools notwendig war.
Semantic Versioning
Sehr oft beobachtet man, dass Versionen in der package.json manuell geändert werden. Wenn das eigene Modul allerdings Semantic Versioning richtig folgen möchte, lohnt sich ein Blick auf npm version
(docs.npmjs.com/cli/version ). Dieser Befehl überlässt npm die Aktualisierung der Version. Dabei ist es möglich, sowohl eine feste Version anzugeben, z.B. npm version 1.0.0
aber auch, npm die Erhöhung der Version zu überlassen (z.B. npm version patch
). Valide Parameter hier sind: patch
, minor
, major
, prepatch
, preminor
, premajor
, prerelease
oder from-git
. Hierbei nutzt from-git
den aktuellen Tag, während die anderen Parameter die Version entsprechend erhöhen.
Die Option -m
erlaubt es, eine Commit-Message hinzuzufügen (npm version patch -m “Updated module to version %s”
– %s wird durch die neue Version ersetzt).
Die folgenden Schritte werden von npm version
ausgeführt:
- Überprüfung, ob alle Dateien im Verzeichnis hinzugefügt/committet sind
- Führe das preversion-Skript, wenn vorhanden, durch
- Erhöhe die Version in der package.json
- Führe das version-Skript, wenn vorhanden, durch
- commit und tag
- Führe das postversion-Skript, wenn vorhanden, durch
Und viel mehr
Und natürlich bietet npm hier wesentlich mehr. Die Dokumentation von npm ist sehr umfangreich und bietet auch zahlreiche Beispiele. Es lohnt sich hier definitiv, einen genaueren Blick auf diese zu werfen, um npm im eigenen Projekt optimal zu nutzen.
Weitere Beiträge
von Daniel Zenzes
Dein Job bei codecentric?
Jobs
Agile Developer und Consultant (w/d/m)
Alle Standorte
Weitere Artikel in diesem Themenbereich
Entdecke spannende weiterführende Themen und lass dich von der codecentric Welt inspirieren.
Gemeinsam bessere Projekte umsetzen.
Wir helfen deinem Unternehmen.
Du stehst vor einer großen IT-Herausforderung? Wir sorgen für eine maßgeschneiderte Unterstützung. Informiere dich jetzt.
Hilf uns, noch besser zu werden.
Wir sind immer auf der Suche nach neuen Talenten. Auch für dich ist die passende Stelle dabei.
Blog-Autor*in
Daniel Zenzes
Du hast noch Fragen zu diesem Thema? Dann sprich mich einfach an.
Du hast noch Fragen zu diesem Thema? Dann sprich mich einfach an.