LIMESODA Blog

npm – Die eierlegende Wollmilchsau

Der „Node Package Manager“ (npm) ist mittlerweile zum Standard für die Paketverwaltung in der JavaScript-Welt mutiert. Vor einiger Zeit musste man sich noch mit bower herumplagen, mittlerweile gibt es aber jedes wichtige Paket auch per npm und der Abstand zu bower vergrössert sich stetig:

Quelle: http://www.modulecounts.com/

Aus der Grafik ist recht eindrucksvoll ersichtlich dass uns npm noch länger begleiten wird, und das ist auch gut so. Es kann jedenfalls nicht schaden sich näher mit dem Tool auseinander zu setzen.

Initialisierung

Grundlage von npm ist eine package.json Datei pro Projekt. Diese beinhaltet unter anderem den Projektnamen, alle Dependencies und Versionen, aber auch sogenannte run scripts. Die package.json wird per npm init im Projekt-Root erstellt. Hier ein einfaches Beispiel:

{
  "name": "limesoda-project",
  "version": "2.0.0",
  "description": "The best project ever",
  "main": "main.js",
  "scripts": {
    "build": "gulp",
    "serve": "gulp serve",
    "production": "gulp --production"
  },
  "author": "Andreas Teufel",
  "devDependencies": {
    "autoprefixer": "^6.3.6",
    "babel-core": "^6.9.1",
    "babel-loader": "^6.2.4",
    "babel-preset-es2015": "^6.9.0",
    "babel-preset-react": "^6.11.1",
    "browser-sync": "^2.13.0",
    "bundle-loader": "^0.5.4",
    "cssnano": "^3.7.1",
    "eslint": "^3.0.1",
    "express": "^4.14.0",
    "gulp": "^3.9.1",
    "gulp-changed": "^1.3.0",
    "gulp-imagemin": "^3.0.1",
    "gulp-postcss": "^6.1.1",
    "postcss-cssnext": "^2.7.0",
    "script-loader": "^0.7.0",
    "webpack": "^1.13.1",
    "webpack-stream": "^3.2.0"
  },
  "dependencies": {
    "react": "^15.2.0",
    "react-cookie": "^0.4.7",
    "react-dom": "^15.2.0",
    "react-router": "^2.5.1"
  }
}

Dependency Resolution

Pakete besitzen meist Abhängigkeiten, diese müssen natürlich ebenfalls installiert werden. Bei Version 2 wurden diese unter jedem installierten Paket extra installiert. Hier am Beispiel eines Paketes „moduleA“ mit der Abhängigkeit „moduleB“:

/node_modules/moduleA/node_modules/moduleB

Das Problem: Wird ein neues Modul installiert, welches die gleiche Abhängigkeit besitzt, so wird diese mehrfach installiert:

/node_modules/moduleA/node_modules/moduleB
/node_modules/moduleC/node_modules/moduleB

Version 3 von npm löst das Problem, indem Sub-Abhängigkeiten „flat“ installiert werden. Installiert man also „moduleA“ und „moduleC“, so sieht die Ordnerstruktur danach wie folgt aus:

/node_modules/moduleA/
/node_modules/moduleB/
/node_modules/moduleC/

Das kann zwar zu Verwirrung führen, nachdem „moduleB“ nicht in der package.json aufscheint, sorgt aber für weniger Redundanz.

Natürlich kann „moduleC“ eine andere Version von „moduleB“ benötigen als „moduleA“, in diesem Fall fällt npm wieder auf die frühere Verschachtelung zurück:

/node_modules/moduleA/
/node_modules/moduleB_v1/
/node_modules/moduleC/node_modules/moduleB_v2

Das Verhalten wird hier ebenfalls anschaulich erklärt: https://docs.npmjs.com/how-npm-works/npm3

Globale/Lokale Dependencies

Nicht nur Projektabhängigkeiten können per npm installiert werden, sondern auch Command Line Tools wie zum Beispiel gulp oder webpack. Wichtig ist hierbei die globale Installation mit dem Parameter -g. Es können auch gleich mehrere Pakete gleichzeitig installiert werden:

npm install -g webpack gulp

Lokale Dependencies sollten immer mit dem Parameter –save oder –save-dev installiert werden, damit sie gleich in die package.json aufgenommen werden. Ein Unterschied besteht hierbei nur in der Platzierung innerhalb der package.json. Grundsätzlich werden per –save-dev Pakete installiert die zur Entwicklung benötigt werden (Test-Frameworks, Build-Tools, …). Per –save werden Pakete installiert welche zur Laufzeit verwendet werden (jQuery, React, …):

npm install --save jquery react
npm install --save-dev autoprefixer postcss-cssnext babel-loader

Update von npm-Paketen

Pakete während der Entwicklung aktuell zu halten macht nicht immer Sinn, bei längerer Entwicklungszeit kann es notwendig werden durch ein Dependency-Update den Code umzuschreiben. Es spricht aber ansonsten nichts dagegen, vor allem wenn man brav seine Unit Tests geschrieben hat ;)

Vor dem Update sollte man per npm outdated prüfen, für welche Pakete es neue Versionen gibt:

Damit die Paketversionen in der package.json ebenfalls angepasst werden, sollte npm update –save oder npm update –save-dev verwendet werden.

Run Scripts und Lifecycle Hooks

Es gibt eine Reihe von vordefinierten Lifecycle Hooks, mit denen automatisch Scripte aufgerufen werden können. Eine vollständige Liste befindet sich in der offiziellen Dokumentation: https://docs.npmjs.com/misc/scripts.

Sehr interessant ist aber auch die Möglichkeit eigene Scripte aufzurufen. Der Trend geht aktuell weg von Task Runners wie Grunt oder Gulp zu simplen Node.js-Scripts, welche per npm aufgerufen werden. Im oben angeführten Beispiel einer package.json Datei existieren bereits einige Run Scripts, hiermit können diese aufgerufen werden:

npm run build
npm run serve
npm run production

Eigene Pakete publishen

Irgendwie müssen die Pakete natürlich auch programmiert werden, damit sie ein anderer User verwenden kann. Auch das funktioniert mit npm über die Kommandozeile:

npm publish
npm publish --access=restricted
npm publish --access=public

Als „restricted“ kann ein Paket nur mit einem kostenpflichtigen npm Account published werden. Die vollständige Dokumentation: https://docs.npmjs.com/cli/publish

Davor muss natürlich ein npm Benutzerkonto angelegt werden, mit welchem man sich vor dem Publishen verbindet:

#user anlegen
npm adduser
#login
npm login

Nach „leftpadgate“ hat npm verhindert dass einmal veröffentlichte Pakete einfach entfernt werden können, um Abhängigkeiten zu anderen Paketen aufrecht zu erhalten. npm unpublish funktioniert daher nur noch 24 Stunden, danach kann ein Paket nur als „deprecated“ markiert werden: https://docs.npmjs.com/cli/deprecate

Ich hoffe dass ich die eine oder andere unbekannte Funktion näherbringen konnte, npm ist jedenfalls ein geniales Tool und sollte mittlerweile von jedem Frontent-Developer verwendet werden. Dann natürlich auch gleich mit all den coolen Dingen, die sich per npm simpel installieren lassen: Babel, Webpack, React, …

Die mobile Version verlassen