- Im npm-Ökosystem gilt die Aufblähung von Abhängigkeitsbäumen als zentrales Problem; sie entsteht durch die Unterstützung alter Laufzeitumgebungen, atomare Paketstrukturen und den Einsatz veralteter Ponyfills
- Kleine Utility-Pakete, die aus Gründen der Kompatibilität mit alten Engines und der Cross-Realm-Sicherheit weiter gepflegt werden, bleiben auch in modernen Umgebungen unnötigerweise bestehen
- Die atomare Architektur sollte die Wiederverwendbarkeit erhöhen, wirkt in der Praxis jedoch als ineffiziente Struktur, die Duplikate sowie Sicherheits- und Wartungskosten erhöht
- Veraltete Ponyfill-Pakete für Funktionen, die bereits von allen Engines unterstützt werden, werden nicht entfernt und verursachen dadurch unnötige Downloads und Verwaltungsaufwand
- Die Community treibt mit Werkzeugen wie e18e, knip und module-replacements die Bereinigung unnötiger Abhängigkeiten und den Umstieg auf native Funktionen voran
Die drei Säulen der Aufblähung von JavaScript-Abhängigkeiten
- Mit dem Wachstum der e18e-Community nehmen leistungsorientierte Beiträge zu, und es laufen Cleanup-Aktivitäten, die unnötige oder nicht mehr gepflegte Pakete entfernen
- Im npm-Ökosystem wird die Aufblähung von Abhängigkeitsbäumen (dependency bloat) als großes Problem gesehen; als Hauptursachen gelten die Unterstützung alter Laufzeiten, atomare Paketstrukturen und die Nutzung veralteter Ponyfills
1. Unterstützung alter Laufzeitumgebungen (einschließlich Sicherheit und Realms)
- In npm-Bäumen gibt es viele kleine Utility-Pakete wie
is-string und hasown, die aus drei Gründen weiterbestehen
- Unterstützung sehr alter Engines (z. B. ES3, IE6/7, frühes Node.js)
-
Schutz vor Manipulation des globalen Namespace
- Verarbeitung von Cross-Realm-Werten
-
Unterstützung alter Engines
- In ES3-Umgebungen fehlen ES5-Funktionen wie
Array.prototype.forEach, Object.keys und Object.defineProperty
- In solchen Umgebungen muss man sie selbst implementieren oder ein Polyfill verwenden
- Die beste Lösung ist ein Upgrade, aber einige Nutzer bleiben weiterhin bei alten Versionen
-
Schutz vor Manipulation des globalen Namespace
- Node verwendet intern das Konzept der primordials, um globale Objekte frühzeitig zu wrappen und vor Manipulation zu schützen
- Wenn man zum Beispiel
Map neu definiert, kann Node selbst kaputtgehen; deshalb behält Node Referenzen auf die Originale
- Einige Paket-Maintainer wenden dieses Muster auch auf allgemeine Pakete an und nutzen sicherheitsorientierte Pakete wie
math-intrinsics
-
Cross-Realm-Werte
- Beim Übergeben von Objekten zwischen iframes tritt das Problem auf, dass
instanceof-Prüfungen fehlschlagen
- Beispiel:
window.RegExp !== iframeWindow.RegExp
- Test-Frameworks wie
chai führen Realm-übergreifende Typprüfungen mit Object.prototype.toString.call(val) durch
- Pakete wie
is-string existieren für diese Cross-Realm-Kompatibilität
-
Das Problem
- Die meisten Entwickler nutzen modernes Node oder Evergreen-Browser, sodass diese Kompatibilität unnötig ist
- Trotzdem liegen diese Pakete auf dem „Hot Path“ allgemeiner Abhängigkeitsbäume, sodass alle die Kosten tragen
2. Atomare Architektur
- Manche Entwickler vertreten die Ansicht, Pakete sollten in möglichst kleine Einheiten zerlegt werden, um daraus wiederverwendbare Bausteine zu machen
- Dadurch existieren viele extrem fein granulare Pakete wie
shebang-regex, arrify, slash, path-key, onetime und is-wsl
- Beispiel:
shebang-regex enthält nur eine einzige Zeile Regex (/^#!(.*)/)
-
Das Problem
- Die meisten atomaren Pakete werden nicht wiederverwendet oder haben nur einen einzigen Konsumenten
- Beispiele:
shebang-regex → wird nur von shebang-command verwendet
cli-boxes → wird nur von boxen, ink verwendet
onetime → wird nur von restore-cursor verwendet
- In solchen Fällen ist das funktional dasselbe wie Inline-Code, verursacht aber zusätzliche Kosten durch npm-Anfragen, Entpacken und Bandbreite
-
Duplikationsproblem
- Beispiel: Im Abhängigkeitsbaum von
nuxt@4.4.2 gibt es von is-docker, is-stream, is-wsl und path-key jeweils zwei Versionen als Duplikate
- Ersetzt man sie durch Inline-Code, entfallen Versionskonflikte und Auflösungskosten, sodass die Duplikationskosten fast verschwinden
-
Größeres Supply-Chain-Risiko
- Je mehr Pakete es gibt, desto höher ist das Sicherheits- und Wartungsrisiko
- Tatsächlich gab es einen Fall, in dem ein Maintainer viele kleine Pakete verwaltete, sein Konto kompromittiert wurde und dadurch Hunderte Pakete gleichzeitig beschädigt wurden
- Einfacher Code wie
Array.isArray(val) ? val : [val] kann direkt inline geschrieben werden, statt dafür ein eigenes Paket zu pflegen
-
Fazit
- Die atomare Architektur hat sich entgegen ihrer Absicht zu einer ineffizienten und riskanten Struktur entwickelt
- Ohne nennenswerten praktischen Nutzen für die meisten Nutzer trägt das gesamte Ökosystem die Kosten
3. Veraltete Ponyfills
- Ein Polyfill ist Code, der einer Umgebung eine von der Engine nicht unterstützte Funktion hinzufügt,
ein Ponyfill ist dagegen eine alternative Implementierung, die man direkt importiert, ohne die Umgebung zu verändern
- Beispiel:
@fastly/performance-observer-polyfill bietet sowohl Polyfill als auch Ponyfill
-
Das Problem
- Ponyfills waren früher nützlich, werden aber nicht entfernt, obwohl die Ziel-Funktion bereits von allen Engines unterstützt wird
- Beispiele:
globalthis (seit 2019 unterstützt, 49 Millionen Downloads pro Woche)
indexof (seit 2010 unterstützt, 2,3 Millionen Downloads pro Woche)
object.entries (seit 2017 unterstützt, 35 Millionen Downloads pro Woche)
- Solche Pakete existieren meist nur noch, weil sie nie entfernt wurden
- Wenn alle LTS-Engines eine Funktion unterstützen, sollte das Ponyfill entfernt werden
Wege aus der Aufblähung
- Wegen der tiefen Verschachtelung von Abhängigkeitsbäumen ist Bereinigung schwierig, aber durch Zusammenarbeit der Community möglich
- Jeder Entwickler sollte sich fragen: „Brauche ich dieses Paket wirklich?“ Falls nicht, sollte man ein Issue eröffnen oder nach Ersatzpaketen suchen
- Das Projekt module-replacements bietet eine Liste von Paketen, die durch native Funktionen ersetzt werden können
-
Einsatz von knip
- knip ist ein Tool zur Erkennung ungenutzter Abhängigkeiten und toten Codes
- Es ist keine direkte Lösung, aber ein guter Ausgangspunkt für die Bereinigung
-
Nutzung der e18e CLI
- Mit dem Befehl
@e18e/cli analyze lassen sich ersetzbare Abhängigkeiten erkennen
- Beispiel: automatische Migration von
chalk zu picocolors
- Künftig sollen je nach Umgebung auch native Funktionen wie Nodes
styleText empfohlen werden
-
Nutzung von npmgraph
- npmgraph.js.org ist ein Visualisierungstool für Abhängigkeitsbäume
- Beispiel: Im Baum von
eslint@10.1.0 ist der Branch find-up isoliert
- Für eine einfache Dateisuche sind keine 6 Pakete nötig; stattdessen kann eine kleinere Alternative wie
empathic genutzt werden
-
Projekt module replacements
- Die Community pflegt einen Datensatz, der ersetzbare Pakete und native Funktionen aufeinander abbildet
- Über das codemods-Projekt werden auch automatische Migrationen unterstützt
Fazit
- Die aktuelle Aufblähung bedeutet, dass wegen einer kleinen Gruppe von Nutzern mit Alt-Kompatibilität oder speziellen Strukturen das Ganze die Kosten trägt
- Früher war das unvermeidlich, aber heute mit ausgereiften modernen Engines und APIs ist es unnötige Last
- Künftig sollte diese kleine Gruppe einen separaten Stack pflegen, während der Rest auf eine leichte, moderne Codebasis umsteigt
- Projekte wie e18e und npmx unterstützen das durch Dokumentation und Tooling,
und jeder Entwickler sollte die eigenen Abhängigkeiten prüfen und fragen: „Warum brauche ich das?“
- Alle können gemeinsam aufräumen
2 Kommentare
Wenn ich selbst Bibliotheken erstelle, biete ich zwar weiterhin noch einen cjs-Build an,
aber ich fände es schon gut, wenn Bibliotheken, die selbst 2026 nicht einmal ein esm-Beispiel haben und komplett auf
requirebasieren, mal etwas aktualisiert würden.Hacker-News-Kommentare
Ich denke, der beste Weg ist derzeit, mit abhängigkeitsfreiem JavaScript zu entwickeln
Die JS/CSS-Standardbibliotheken sind hervorragend, und statische Analyse (JSDoc-Checks in TypeScript), ES-Module, Web Components usw. sind ebenfalls stark genug
Viele sagen, dieser Ansatz sei bei Skalierbarkeit oder Wartbarkeit im Nachteil, aber meiner Erfahrung nach konnte ich damit eher eine einfache, leicht veränderbare Struktur beibehalten
Das meiste, was Frameworks oder Build-Tools tun, lässt sich durch Browser-Bordmittel und Vanilla-Patterns ersetzen
Allerdings ist dieser Ansatz noch immer ungewohnt, und das Problem ist, dass das meiste Tutorial-Ökosystem auf große Frameworks ausgerichtet ist
Selbst wenn man React-Code vollständig auf Vanilla umstellt, bleibt die Modularität erhalten und der Code wird nur etwa 1,5-mal länger; dafür ist die Performance wegen der fehlenden Abhängigkeiten sogar besser
Das heißt natürlich nicht, dass Abhängigkeiten schlecht sind. Viele Entwickler sitzen nur in der festen Vorstellung fest, dass man sie „zwingend verwenden muss“
Ich baue zum Beispiel Seiten mit umfangreichen Kartenfunktionen und muss dafür Bibliotheken wie mapbox/maplibre/openlayers verwenden, für die es keine echte Alternative gibt
Der Kunde hat für Migrationen keinen einzigen Cent bezahlt
Mich würde interessieren, wie Modell-Updates behandelt werden, wie in diesem Artikel
Dadurch ist es sogar einfacher geworden, große Codebasen mit wenigen Leuten zu pflegen
Dank moderner Tools ist eigenes Implementieren heute viel leichter als früher, und es passt auch gut zu agentic engineering
Der Artikel ist gut geschrieben und erklärt das Problem klar, ohne emotional zu werden
Ich denke, ein Teil der Ursache ist, dass JS nie eine echte Standardbibliothek hatte
Guter Artikel, aber ich denke, die eigentliche Wurzel des Problems ist unnötiger Zusatz (= Bloat) selbst
Ich würde gern Saint-Exupéry zitieren: „Perfektion ist nicht dann erreicht, wenn es nichts mehr hinzuzufügen gibt, sondern wenn man nichts mehr weglassen kann“
Die meiste Software wird eher mit der Frage geschrieben: „Wie kann man leichter mehr hinzufügen?“ statt „Wie macht man es eleganter?“
Die Antwort lautet immer
npm i more-stuffWie beim Gegensatz zwischen Demosthenes und Cicero ist guter Code der, aus dem man nichts mehr entfernen kann
JS muss sowohl frühere als auch zukünftige Browser-Kompatibilität berücksichtigen, und als UI-zentrierte Sprache wird es durch Accessibility, Internationalisierung und Mobile-Support aufgebläht
In vielen Fällen scheint das ein Problem versteckter technischer Schulden zu sein
Ursache ist, dass das Compile-Target nicht auf ESx angehoben wird und Pakete oder Implementierungen nicht aktualisiert werden
ES5 wird bereits seit 13 Jahren von allen Browsern unterstützt (caniuse.com/es5)
Beide halten ihr Verhalten für ein Feature und pflegen viele populäre Pakete
Deshalb ist Veränderung schwierig. Die Community kritisiert das gelegentlich, aber sie haben aus ihrer Sicht eine eigene Logik
Wenn man mit Babel auf alte Versionen transpiliert, wird der Code umfangreich und langsam, und auf alten Browsern läuft er wegen CSS- oder JS-Funktionsgrenzen oft ohnehin nicht
Sogar Polyfills haben schon Probleme verursacht (ein Polyfill für den Exponentialoperator, das BigInt nicht verarbeiten konnte)
Es gibt Konsolen, Fernseher, alte Android-Geräte, iPod touch, den eingebauten Facebook-Browser und viele weitere Umgebungen
Deshalb lasse ich nur ein einziges externes Modul zu und löse den Rest über die Konfiguration des Transpilers
Früher wurden für asynchrones Tracking Dinge wie
setTimeoutüberschrieben, heute lässt sich das mit signals viel einfacher lösenIch glaube, einige Paketautoren zerlegen Abhängigkeitsbäume künstlich, um ihre Downloadzahlen zu erhöhen
Dass es Pakete mit sieben Zeilen gibt, ist absurd. Die Lockfile-Metadaten sind größer als der Code
Früher bestanden 5 % der create-react-app-Abhängigkeiten aus Mini-Paketen eines einzelnen Autors
Beispiele sind has-symbols, is-string und ljharb
Anthropic gibt zum Beispiel Open-Source-Maintainern mit vielen npm-Downloads kostenloses Claude
Jedes einzelne dieser Mikro-Pakete vergrößert die Angriffsfläche
Der Wettbewerb um Downloadzahlen erhöht das Risiko eher noch
In anderen Kulturen gilt genau das eher als gute Praxis
Bevor man das JS-Ökosystem kritisiert, sollte man 30 years of br tags lesen
Dann versteht man den Entwicklungsprozess von JS und seinen Tools besser
Einfach zu sagen „JS-Entwickler sind das Problem“ zeigt mangelndes technisches Denken
Wir sollten immer über bessere Theorie und bessere Praxis nachdenken
Da sich die Softwarewelt schnell verändert, müssen wir uns selbst immer wieder eine „falsche Beerdigung“ geben und veraltete Praktiken ablegen
Ich betreue eine 9 Jahre alte Node.js-Codebasis mit nur 8 Abhängigkeiten, und alle haben keine transitive Abhängigkeiten
Ich nutze zuerst die eingebauten Funktionen von Node und implementiere nur das Nötige selbst
Das ist viel stabiler und weniger stressig als früher
Auch die Standardbibliothek von Deno ist hervorragend; zusammen mit den Grundfunktionen der Runtime lassen sich mit wenigen Paketen problemlos Apps bauen
JS ist eine ziemlich gute Sprache, wenn man vorsichtig damit umgeht
Ich verstehe das Argument der Cross-Realm-Sicherheit bei Paketen wie
is-string, aber in der Praxis sind solche Fälle seltenDas Problem ist, dass npm das Veröffentlichen zu leicht macht und sich die Philosophie „Module zerlegen und einzeln publizieren“ übermäßig ausgedehnt hat
Die Nutzer prüfen den Abhängigkeitsbaum nicht, sondern installieren einfach, wodurch optionale Kosten zu Standardkosten werden
Das Ponyfill-Problem ließe sich automatisiert lösen
Ein Renovate-ähnlicher Bot, der Funktionen erkennt und entfernt, die in einer Node-LTS-Version bereits unterstützt werden, wäre zum Beispiel hilfreich
Bei unserer internen PWA gibt es genau ein Prinzip:
„Auf die neueste Chrome-Version aktualisieren. Wenn es dann noch Probleme gibt, schauen wir weiter“
Dass Safari weniger Speicher verbraucht, kann ich nachvollziehen, aber eine einheitliche Richtlinie ist effizienter
Aussagen wie „Wir müssen bis ES3 (also IE6/7-Niveau) unterstützen“ sind wirklich schwer nachzuvollziehen
Schon aus Sicherheitsgründen sollten selbst Banking-Websites solche uralten Browser blockieren
Den Webpack-, Babel- und Polyfill-Stack zu modernisieren ist aufwendig, also lässt man alles einfach so
Das ist eine Kultur nach dem Motto: „Was nicht kaputt ist, wird nicht repariert“