10 Punkte von GN⁺ 2026-03-23 | 2 Kommentare | Auf WhatsApp teilen
  • 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

 
click 2026-03-23

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 require basieren, mal etwas aktualisiert würden.

 
GN⁺ 2026-03-23
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

    • Ich experimentiere selbst seit einigen Jahren mit diesem Ansatz und habe auch die Tutorial-Seite plainvanillaweb.com erstellt
      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“
    • Für einfache Marketing-Seiten mag das gehen, aber bei Apps mit vielen Funktionen sind Abhängigkeiten oft unverzichtbar
      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
    • Ich habe 2022 ein Projekt auf diese Weise umgesetzt und hatte keinerlei Probleme mit CVEs oder Versionsmigrationen
      Der Kunde hat für Migrationen keinen einzigen Cent bezahlt
    • Komponenten zu rendern ist einfach, aber der Kern eines Frameworks ist die Bereitstellung reaktiver Updates des Modells
      Mich würde interessieren, wie Modell-Updates behandelt werden, wie in diesem Artikel
    • Ich arbeite seit fast 20 Jahren mit JS und bin letztlich bei einem Ansatz gelandet, bei dem ich nur minimale Abhängigkeiten lasse und den Rest selbst baue
      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

    • Die JS-Standardbibliothek ist inzwischen ziemlich umfangreich; mich würde interessieren, welche Funktionen deiner Ansicht nach noch fehlen
    • Ich mag eher wütende Texte (rants). Sie helfen nicht nur dabei, die Gefühle der Leute zu verstehen, sondern auch deren Gründe
  • 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-stuff

    • Das erinnert mich an Kurt Vonneguts Schreibregel: „Jeder Satz muss entweder den Charakter offenbaren oder die Handlung voranbringen“
      Wie beim Gegensatz zwischen Demosthenes und Cicero ist guter Code der, aus dem man nichts mehr entfernen kann
    • Jede Software enthält unnötige Teile, aber bei npm-Paketen und Web-Apps ist es besonders schlimm
      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)

    • Tatsächlich gibt es Leute, die alte JS-Engines unterstützen wollen, und Leute, die unzählige Mini-Pakete bauen
      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
    • Dieser Drang, Kompatibilität bis einschließlich ES6 beizubehalten, wirkt seltsam
      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)
    • Nicht nur alte Browser müssen unterstützt werden, sondern auch seltsame Browser
      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
    • Im Web ist die Kultur stark von „jetzt ausrollen und später korrigieren“ geprägt, daher bleiben veraltete Abhängigkeiten lange bestehen
    • Wie bei Architekturentscheidungen in Angular kann eine Struktur aus der Vergangenheit heute zu Ineffizienz führen
      Früher wurden für asynchrones Tracking Dinge wie setTimeout überschrieben, heute lässt sich das mit signals viel einfacher lösen
  • Ich 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

    • Ich frage mich, ob das nur Eitelkeit ist oder ob es tatsächlich einen Nutzen bringt
      Anthropic gibt zum Beispiel Open-Source-Maintainern mit vielen npm-Downloads kostenloses Claude
    • Wie im Artikel immich.app/cursed-knowledge gibt es Leute, die unter dem Vorwand der „Abwärtskompatibilität“ 50 Pakete hinzufügen
    • Auch aus Sicherheitssicht ist das ernst
      Jedes einzelne dieser Mikro-Pakete vergrößert die Angriffsfläche
      Der Wettbewerb um Downloadzahlen erhöht das Risiko eher noch
    • Früher gab es sogar einen Fall, in dem jemand in eine Organisation eindrang, sein eigenes Paket als Abhängigkeit hinzufügte und so GitHub-Stars für den Lebenslauf sammeln wollte
    • Es ist auch ein kulturelles Problem. In der JS-Community wird das direkte Einfügen von sieben Zeilen Code als „das Rad neu erfinden“ kritisiert
      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

    • Es ist gut, eine schlechte Realität verstehen zu wollen, aber zu viel Akzeptanz ist Defätismus
      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
    • Für mich ist der Artikel kein Angriff auf Entwickler, sondern eine rationale Kritik am Status quo
  • 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 selten
    Das 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“

    • Für interne Nutzung ist das der richtige Ansatz. Wenn ein Unternehmen den unterstützten Browser festlegt, wird die Verwaltung einfacher
      Dass Safari weniger Speicher verbraucht, kann ich nachvollziehen, aber eine einheitliche Richtlinie ist effizienter
    • Dinge einfach zu halten bringt am Ende den größten Gewinn
  • 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

    • In solchen Teams wurden meist Build-Tools, die um 2015 eingerichtet wurden, bis heute nicht ersetzt
      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“
    • Tatsächlich gibt es eine bestimmte Person, die solche Altversionen-Unterstützung fordert, und sie pflegt viele Low-Level-Pakete
    • Nebenbei habe ich auch gehört, dass die Deutsche Bahn noch immer Windows 3.1 benutzt