Node.js in ddev: nub und das Add-on ddev-nub
Zusammenfassung
Node.js-Tooling in PHP-Projekten war lange ein Flickenteppich: eigenes npx, eigenes nvm, eigener Bundler, und jedes Sub-Tool bringt seine eigenen Macken mit. nub bündelt das alles in einem einzigen, in Rust geschriebenen CLI-Werkzeug – und läuft dabei, anders als Bun oder Deno , auf dem echten Node-Runtime statt einer Neuimplementierung. Damit bleibt die Kompatibilität hoch, während Skripte spürbar schneller starten. Mit dem Add-on ddev-nub wandert das Werkzeug direkt in den Webcontainer von ddev , ganz ohne globale Installation auf dem Host. Dieser Artikel zeigt, was nub anders macht, wie das Add-on entstanden ist und wie man es in fünf Minuten in einem bestehenden ddev-Projekt ausprobiert.
Der Grund, warum das Thema überhaupt aufkam
Node.js-Projekte in ddev fühlen sich manchmal an wie ein Auto mit drei verschiedenen Zündschlüsseln. Für den Paketmanager einer, für die Node-Version einer, für den lokalen Skriptaufruf noch einer. nvm hier, npx da, ein .nvmrc, das die halbe Zeit ignoriert wird, weil der Container ohnehin eine andere Version mitbringt. Wer regelmäßig zwischen mehreren Projekten mit unterschiedlichen Node-Ständen wechselt, kennt den kleinen Moment der Verzweiflung, wenn ein Build plötzlich mit einer kryptischen Fehlermeldung abbricht, nur weil im Hintergrund die falsche Node-Version aktiv war.
Bun und Deno haben genau dieses Problem angehen wollen, allerdings auf dem Weg, Node komplett neu zu bauen. Das bringt Geschwindigkeit, aber auch Kompatibilitätslücken – gerade bei älteren oder sehr node-spezifischen Paketen bricht dann mal etwas weg, das vorher klaglos funktioniert hat.
Was nub anders macht
nub geht den entgegengesetzten Weg. Statt Node zu ersetzen, wird die echte Node-Runtime im Hintergrund weiterverwendet – nub legt sich als schnelle, in Rust geschriebene Schicht darüber. Das klingt nach einem kleinen Unterschied, ist aber der entscheidende: Laut den Angaben der Macher erreicht nub rund 98,8 % Kompatibilität mit Node’s eigener Test-Suite, während Deno bei etwa 77,4 % und Bun bei rund 40,5 % liegen. Diese Zahlen stammen aus eigenen Benchmarks des nub-Teams – ich würde sie also mit der üblichen Vorsicht genießen, die man Herstellerangaben entgegenbringt, aber die Grundidee dahinter leuchtet ein.
Konkret bringt nub:
- Einen Datei-Runner, der TypeScript ohne separaten Transpilationsschritt ausführt und dabei auch Dinge unterstützt, die Node selbst ablehnt (Enums, Namespaces, Parameter Properties).
- Eine deutlich schnellere Skriptausführung als
pnpm run– laut Hersteller bis zu 24-mal schneller, weil der übliche Bootstrap-Overhead wegfällt. nubxals Ersatz fürnpx, der Binaries ausnode_modules/.bindirekt und ohne Umweg ausführt.- Einen eigenen Paketmanager, der bestehende npm-, pnpm- und Bun-Lockfiles respektiert, statt sie zu ignorieren. Lifecycle-Skripte von Abhängigkeiten (also der Code, der bei
postinstall& Co. automatisch mitläuft) sind standardmäßig blockiert – ein hübscher Nebeneffekt, wenn man an die letzten Supply-Chain-Vorfälle im npm-Ökosystem denkt. - Automatische Node-Versionsverwaltung über
.node-version,.nvmrcoderpackage.json#engines– die richtige Version wird bei Bedarf einfach nachgeladen.
Warum ich daraus ein ddev-Add-on gebaut habe
Das eigentliche Problem war weniger nub selbst als die Integration in einen bestehenden ddev-Workflow. nub global auf dem Host zu installieren, widerspricht dem ganzen Sinn von ddev: Ein Projekt soll reproduzierbar sein, ohne dass jeder im Team erst noch manuell Tools auf seiner Maschine einrichtet. Also habe ich ddev-nub gebaut – ein Add-on, das nub direkt in den Web-Container von ddev installiert.
Die Installation ist denkbar unspektakulär:
ddev add-on get MetaSyntactical/ddev-nub
ddev restart
Danach stehen im Container zwei neue Befehle bereit: ddev nub für die nub-eigenen Kommandos und ddev nubx als Pendant zu npx. Wer eine bestimmte nub-Version fixieren möchte, trägt sie in .ddev/.env.nub über --nub-version ein. Lässt man den Wert leer, installiert das Add-on bei jedem Build automatisch die aktuelle Version – praktisch für eigene Experimente, in Teamprojekten würde ich aber klar zur fixierten Version raten, damit nicht plötzlich ein Update mitten im Sprint für Überraschungen sorgt.
Ein kurzer Praxistest
Ein typischer Ablauf in einem Frontend-lastigen Symfony- oder reinen Node-Projekt sieht dann so aus:
ddev nubx vite build
ddev nub run test
Der spürbarste Unterschied zeigt sich bei häufig wiederholten Befehlen im Entwicklungsalltag – jedes Mal ein paar hundert Millisekunden gespart, klingt nach wenig, summiert sich aber über einen Arbeitstag mit Dutzenden Build- und Test-Läufen durchaus spürbar auf. Bei einem einmaligen Produktions-Build merkt man davon naturgemäß wenig. Wer eigene Zahlen sehen möchte, sollte den Vergleich ohnehin selbst im eigenen Projekt fahren – Benchmarks von Tool-Herstellern sind ein guter erster Anhaltspunkt, ersetzen aber keinen Test unter den eigenen Bedingungen (Paketanzahl, Monorepo oder nicht, CI-Runner-Hardware).
Ein Punkt, der in der Praxis öfter übersehen wird: nub verhält sich in einem Monorepo mit pnpm-Workspaces bewusst kompatibel und respektiert die Filter-Syntax von pnpm. Wer also schon mit pnpm --filter arbeitet, muss beim Umstieg keine neuen Befehlsmuster lernen, sondern tauscht im Grunde nur das vorangestellte Kommando aus.
Wie reif ist das Ganze?
nub ist ein junges Projekt, und “jung” heißt in der Tooling-Welt meistens auch: Es gibt noch ein paar Ecken und Kanten. Wir werden nub deshalb in eigenen Projekten einsetzen und die Integration mit ddev dabei kontinuierlich verbessern. Wer heute schon produktive Projekte darauf umstellen will, sollte das mit Bedacht tun und die Versionierung im Blick behalten. Für lokale Entwicklung und zum Ausprobieren ist der Aufwand über das Add-on aber gering genug, dass sich ein Blick durchaus lohnt – gerade wenn man ohnehin schon mit den ewigen kleinen Reibungsverlusten rund um Node-Versionen und Skriptausführung zu kämpfen hat.
Wer es ausprobieren möchte, findet das Add-on unter github.com/MetaSyntactical/ddev-nub – Rückmeldungen und Issues sind willkommen.
Ein Beispielprojekt zum Nachvollziehen
Alles, was oben beschrieben ist, gibt es auch zum Anfassen statt nur auf dem Papier: ddev-nub-example ist ein eigenes, schlankes Repository, das genau die hier gezeigten Befehle 1:1 nachvollziehbar macht, ohne dass jemand vorher selbst ein Setup zusammenklicken muss.
Damit das Beispiel einen echten Job für nub hat, bringt es einen kleinen, aber echten
Node-Anteil mit: ein einfaches Vite-Frontend und ein TypeScript-Modul mit Enum, Namespace und
einer Parameter Property, das genau die transpilationsfreie Ausführung zeigt, die weiter oben
erwähnt wurde. Dazu eine .node-version-Datei für die automatische Versionsverwaltung,
package.json-Scripts, die sich per ddev nub run und ddev nubx ausführen lassen, sowie eine
kurze Anleitung, wie man selbst grobe Zeitvergleiche fährt, statt sich blind auf die
Benchmark-Zahlen des Herstellers zu verlassen. Das README folgt dabei bewusst dem Ablauf dieses
Artikels, damit man zwischen Text und Code nicht hin- und herspringen muss. Das ddev-nub-Add-on
selbst ist im Repository schon vorkonfiguriert – nach dem Klonen reicht ddev start.