Du hast deinen AdBlocker an?

Es wäre ein Traum, wenn du ihn für t3n.de deaktivierst. Wir zeigen dir gerne, wie das geht. Und natürlich erklären wir dir auch, warum uns das so wichtig ist. Digitales High-five, deine t3n-Redaktion

Entwicklung & Design

Statt Grunt, Gulp und Co.: Wie man npm als Build-Tool verwendet

(Grafik: Shutterstock)

npm ist ein fantastisches Werkzeug, das viel mehr bietet, als man auf den ersten Blick sieht, sagt Keith Cirkel. In seinem Artikel verrät er mehr.

Letzten Monat habe ich meine Meinung dazu geäußert, warum wir aufhören sollten, Grunt, Gulp und so weiter zu verwenden. Ich schlug vor, dass wir statt dessen anfangen sollten, npm zu verwenden. npms scripts-Direktive kann alles, was diese Build-Tools können, kürzer, eleganter, mit weniger Paketabhängigkeiten und weniger Verwaltungs-Overhead übernehmen. Der erste Entwurf des Original-Artikels war weit über 6.000 Worte lang – weil er in die Tiefe ging darüber, wie man npm als Alternative benutzen könnte, aber ich löschte ihn zuliebe eines kürzeren – und weil es ein Artikel war, in dem ich meine Meinung ausdrückte, kein Tutorial. Dennoch war die Antwort ziemlich überwältigend – viele Leute antworteten darauf, dass diese Build-Tools ihnen Features böten, die npm nicht kann (oder nicht macht), und einige Entwickler waren so dreist, mir ein Gruntfile zu übergeben mit den Worten: „Wie könnte man das in npm machen?!“ Deshalb dachte ich mir, ich ziehe die How-tos aus dem Original-Entwurf heraus und erstelle einen neuen Artikel, der sich nur darauf konzentriert, wie man diese üblichen Tasks mit npm erledigt.

grunt-gulp

npm ist ein fantastisches Werkzeug, das viel mehr bietet, als man auf den ersten Blick sieht. Es ist zum Backbone der Node.js-Community geworden – viele, auch ich, nutzen es jeden Tag sehr ausgiebig. Wenn man meine Bash-History anschaut (na gut, Fish-History), dann ist npm tatsächlich nach git mein meistgenutzter Befehl. Dennoch finde ich jeden Tag neue Features von npm (und natürlich werden immer noch neue entwickelt!). Die meisten von ihnen sind dafür gedacht, npm zu einem großartigen Paketmanager zu machen, aber npm hat ein tolles Set an Funktionen, die dem Ausführen von Tasks in einem Paket-Lebenszyklus gewidmet sind – mit anderen Worten: es ist ein tolles Tool für Build-Skripte.

npm-Skripte

Zuerst einmal müssen wir herausfinden, wie npm unsere Build-Skripte managen kann. Als Teil von npms Core verfügt es über den npm run-script-Befehl (kurz npm run). Dieser Befehl durchsucht dein package.json und zieht das scripts-Objekt heraus. Das erste Argument, das an npm run geht, bezieht sich auf eine Eigenschaft im scripts-Objekt – es führt den Wert der Eigenschaft als Befehl in der Standard-Shell des Betriebssystems aus (normalerweise Bash, außer auf Windows – aber dazu später). Sagen wir also, du hast eine package.json Konfiguration, die wie folgt aussieht:

{
"name": "myproject",
"devDependencies": {
"jshint": "latest",
"browserify": "latest",
"mocha": "latest"
},
"scripts": {
"lint": "jshint **.js",
"test": "mocha test/"
}
}

Wenn du npm run lint ausführst, wird npm eine Shell öffnen und jshint **.js ausführen. Wenn du npm run test aufrufst, wird npm eine Shell öffnen und mocha test/ ausführen. Die Shell-Umgebung hat deinen node_modules/.bin-Ordner zum Pfad hinzugefügt, was bedeutet, dass jede Abhängigkeit, die Binaries installiert, direkt ausführbar wird – in anderen Worten, man spart sich das "./node_modules/.bin/jshint **.js" oder "$(npm bin)/jshint **.js". Wenn du npm run ohne Argumente ausführst, gibt es dir eine Liste der verfügbaren Befehle zurück, zum Beispiel:

Available scripts in the user-service package:
lint
jshint **.js
test
mocha test/

Die npm run-Shell-Umgebung bietet eine Menge hilfreiche Features, um sicherzustellen, dass deine Skripte so kurz wie möglich werden. Zum Beispiel enthält der Shell-Pfad deinen ./node_modules/.bin/-Ordner, was bedeutet, dass alle installierten Abhängigkeiten, welche Binaries enthalten, direkt aus der Script-Shell aufgerufen werden können. Außerdem gibt es eine ganze Menge super-praktische Umgebungsvariablen, die npm anzeigt, wie zum Beispiel den derzeit laufenden Task, den Paketnamen und die Version, das npm-Loglevel und so weiter. Du kannst sie alle entdecken, wenn du ein Script erstellst und aufrufst, das env ausführt:

"scripts": {
"env": "env"
}

Shortcut-Skripte

npm bietet auch ein paar bequeme Shortcuts. Die Befehle npm test, npm start und npm stop sind alle Shortcuts für ihre run-Äquivalente. So ist npm test zum Beispiel nur ein Shortcut für npm run test. Diese Shortcuts sind aus zweierlei Gründen nützlich: 1. Es gibt beliebte Tasks, welche in den meisten Projekten Verwendung finden, und daher ist es nett, nicht jedes Mal so viel tippen zu müssen. 2. Viel wichtiger – es bietet ein Standard-Interface innerhalb von npm fürs Testen, Starten und Stoppen von Paketen. Viele CI-Tools wie etwa Travis nutzen dieses Verhalten und machen das Default-Kommando für ein Node.js-Projekt zu npm test. Es wird dadurch auch einfacher, neue Entwickler zu deinem Projekt dazuzuholen, wenn sie wissen, dass sie einfach ein Skript wie npm test ausführen können, ohne jemals irgendwelche Dokumentation lesen zu müssen.

Pre- und Post-Hooks

Ein weiteres cooles Feature von npm ist, dass jedes ausführbare Skript auch über ein Set von pre- und post-hooks verfügt, welche sich ganz einfach im scripts-Objekt definieren lassen. Wenn du zum Beispiel npm run lint ausführst, wird npm, obwohl es keine vorgefasste Idee davon hat, was der lint-Task ist, sofort npm run prelint ausführen, gefolgt von npm run lint, gefolgt von npm run postlint. Das gleiche gilt für jeden Befehl, inklusive npm test (npm run pretest, npm run test, npm run posttest). Die pre- und post-Skripte sind außerdem Exit-Code-sensitiv. Wenn also dein pretest-Skript mit einem non-zero-Exit-Code endet, hält npm sofort an und führt die Skripte test und posttest nicht aus. Du kannst aber ein pre-Skript nicht mit pre versehen, also wird prepretest ignoriert. npm führt auch die pre- und post-hooks für ein paar interne Befehle aus: install, uninstall, publish, update. Du kannst diese Verhalten für die internen Befehle nicht überschreiben – aber du kannst ihr Verhalten mit pre- und post-Skripts beeinflussen. Das heißt, dass du Sachen tun kannst wie das hier:

  "scripts": {
"lint": "jshint **.js",
"build": "browserify index.js > myproject.min.js",
"test": "mocha test/",

"prepublish": "npm run build # also runs npm run prebuild",
"prebuild": "npm run test # also runs npm run pretest",
"pretest": "npm run lint"
}

Weitergeben von Argumenten

Ein anderes cooles Feature von npm (mindestens seit npm 2.0.0) ist das Durchreichen von Argumenten-Sets an die untergeordneten Tools. Dies kann ein wenig kompliziert sein, aber hier ist ein Beispiel:

  "scripts": {
"test": "mocha test/",
"test:xunit": "npm run test -- --reporter xunit"
}

Mit dieser Konfiguration können wir ganz einfach npm run test ausführen – was mocha test/ ausführt, aber wir können es mit benutzerdefinierten Parametern mit einem -- Präfix erweitern. Zum Beispiel führt npm run test -- anothertest.js mocha/ anothertest.js aus, oder, noch nützlicher, expandiert npm run test -- --grep parser nach mocha test/ --grep parser (das nur die Tests mit "parser" im Titel ausführt). Im package.json enthalten ist test:xunit, was erfolgreich mocha test --reporter xunit ausführt. Dieses Setup kann unglaublich nützlich sein für das Zusammenstellen von Befehlen für einige fortgeschrittene Konfigurationen.

npm-Konfigurationsvariablen

Eine Sache noch, die sich zu erwähnen lohnt – npm besitzt eine config-Direktive für deine package.json. Das ermöglicht dir das Setzen von beliebigen Variablen, welche als Umgebungsvariablen in deinen scripts aufgegriffen werden können. Hier ist ein Beispiel:

  "name": "fooproject",
"config": {
"reporter": "xunit"
},
"scripts": {
"test": "mocha test/ --reporter $npm_package_config_reporter"
"test:dev": "npm run test --fooproject:reporter=spec"
}

Hier hat das config-Objekt die Eigenschaft reporter auf 'xunit' gesetzt. Alle Konfigurationsoptionen werden als Umgebungsvariablen zugänglich, indem sie das Präfix npm_package_config_ erhalten (was zugegebenermaßen die Variablennamen ganz schön aufbläht). In dem obigen Beispiel nutzt der Befehl npm run test die Variable $npm_package_config_mocha_reporter, die zu mocha test/ --reporter xunit erweitert wird. Diese kann auf zwei bequeme Arten ersetzt werden:

  1. Genauso wie der test:dev-Task kannst du die Konfigurationsvariable reporter zu spec ändern, indem du --fooproject:reporter verwendest. Du kannst fooproject durch deinen Projektnamen ersetzen und reporter mit der Konfigurations-Variable, die du überschreibst.
  2. Sie können auch als Teil der Benutzerkonfiguration überschrieben werden. Durch das Ausführen von npm config set fooproject:reporter spec erscheint ein Eintrag in meinem ~/.npmrc (fooproject:reporter=spec), der zur Laufzeit gelesen wird und die Variable npm_package_config_reporter überschreibt. Das bedeutet, dass auf meinem lokalen Rechner für alle Zeiten npm run test nach mocha test/ --reporter spec erweitert wird. Ich kann meine persönlichen Einstellungen dafür mit npm config delete fooproject:mocha_reporter entfernen. Ein gutes Setup dafür ist das Vorhalten von einigen vernünftigen Defaults in deiner package.json – aber deine eigenen Anpassungen können in deinem ~/.npmrc verstaut werden.

Okay, um ehrlich zu sein, bin ich nicht gerade verliebt in die Art, wie das funktioniert. Während das Setup trivial erscheint (ein „config“-Objekt in deiner JSON vorzuhalten), erscheint der Versuch, diese zu benutzen, zu langatmig und kompliziert. Ich hätte auch gern einen einfacheren Weg, die package-Konfigurationen zu überschreiben, ohne den Package-Namen angeben zu müssen – es wäre toll, wenn es statt dessen Standard-Konventionen gäbe, in denen ich meine bevorzugten mocha reporter für alle Packages in meinem ./.npmrc festlegen könnte.

Der andere Nachteil dieser Konfigurationen ist, dass sie nicht besonders Windows-freundlich sind – Windows verwendet % für Variablen, während bash $ verwendet. Sie funktionieren ohne Probleme, wenn du sie innerhalb eines Node.js-Skripts nutzt, aber wenn jemand einen Weg kennt, wie man sie in Windows über die Shell-Befehle zum Laufen bringt, lasst es mich wissen!

Bitte beachte unsere Community-Richtlinien

Eine Reaktion
Sascha Kühl

Hallo …,

wer nicht affin mit dem ‚Werkzeug‘ (engl. Tool) resp. (Kommandozeilen-)Befehl ‚grep‘ u. / od. RegExp, also sog. ‚regulären Ausdrücken‘ (engl. regular expressions) wie (z. B.) BRE - basic regular expressions (einfache reguläre Ausdrücke), ERE - extended regular expressions (erweiterte reguläre Ausdrücke), PCRE - perl compatible regular expressions (perl-kompatible reguläre Ausdrücke) ist, der findet unter (https://goo.gl/ljr0pM) hierzu eine vollständige, deutschsprachige Anleitung u. Referenz.

Ciao, Sascha.

Antworten

Melde dich mit deinem t3n-Account an oder fülle die unteren Felder aus.

Abbrechen

Finde einen Job, den du liebst