Wo die Kilobytes in deinem Build tatsächlich herkommen
Eine Aufschlüsselung nach Modul beantwortet in fünf Minuten, was Wochen an Vermutungen nicht klären: welches Paket den Ausschlag gibt und ob es womöglich zweimal im Build liegt.
KI-generiertDieses Bild wurde mit KI erzeugt · Yves Hoppe / KI / cmt
Der Zuwachs verteilt sich auf viele kleine Entscheidungen
Kein einzelner Vorgang macht ein Bundle groß. Es ist die Summe: eine Bibliothek für Datumsformatierung, weil eine Zeile Code sonst umständlich wäre. Ein Paket mit fünftausend Symbolen für die vier, die tatsächlich verwendet werden. Eine zweite Version derselben Bibliothek, weil ein anderes Paket sie in einem anderen Versionsbereich mitbringt. Jede Entscheidung war vertretbar, das Ergebnis ist es nicht mehr.
Was das kostet, ist mehr als Übertragungszeit. Die Bytes müssen ausgepackt, ausgewertet und ausgeführt werden, und diese Arbeit passiert im Hauptstrang. Ein Bild derselben Größe belegt keine Rechenzeit und kann nachgeladen werden, JavaScript nicht. Deshalb ist die vergleichbare Zahl nicht die übertragene Größe, sondern die unkomprimierte, und die entscheidende Größe ist die auf einem durchschnittlichen Telefon, nicht auf dem Entwicklungsrechner.
Und die Sache verschlechtert sich stillschweigend. Ein Paket, das im Update von CommonJS-Ausgabe auf eine andere Struktur wechselt, oder ein neuer Import mitten in einer gemeinsam genutzten Datei kann das Ergebnis um Hunderte Kilobyte verändern, ohne dass jemand etwas davon merkt. Ohne eine Größenprüfung in der Pipeline fällt das erst auf, wenn sich jemand über die Ladezeit beschwert.
KI-generiertDieses Bild wurde mit KI erzeugt · Yves Hoppe / KI / cmt
Fünf Beobachtungen und ihre tatsächliche Ursache
Die folgenden fünf Fälle erklären den größten Teil aller unerwartet großen Bündel, und sie unterscheiden sich in der Ursache, nicht im Symptom, deshalb hilft nur die Aufschlüsselung und nicht das Gefühl.
Symptom
Ein Import für eine einzige Hilfsfunktion lässt das Bündel um mehrere hundert Kilobyte wachsen.
Ursache
Der Import geht über eine Sammeldatei, die alles weiterexportiert. Sobald in dieser Datei ein Seiteneffekt steckt oder das Paket CommonJS ausliefert, kann das Werkzeug nichts weglassen.
Lösung
Direkt aus dem Untermodul importieren, ein Paket wählen, das seine Funktionen einzeln ausliefert, oder die eine Funktion selbst schreiben, wenn sie überschaubar ist.
Symptom
Die Aufschlüsselung zeigt dieselbe Bibliothek zweimal, in zwei Versionen.
Ursache
Zwei Abhängigkeiten verlangen unterschiedliche Versionsbereiche, npm legt deshalb zwei Fassungen an. Im Bündel landen beide, samt getrenntem Zustand.
Lösung
Mit npm ls die Verursacher bestimmen, mit npm dedupe zusammenlegen, wo es die Bereiche erlauben, und andernfalls über das Feld overrides eine gemeinsame Version erzwingen und danach testen.
Symptom
Trotz benannter Importe entfernt der Bundler nichts, das Ergebnis bleibt gleich groß.
Ursache
Das Entfernen ungenutzten Codes setzt statisch analysierbare Module voraus. Bei CommonJS-Ausgabe, bei fehlender Angabe zu Seiteneffekten oder bei Zugriffen über berechnete Eigenschaftsnamen muss das Werkzeug alles behalten.
Lösung
Prüfen, ob das Paket eine ESM-Fassung mitbringt, im eigenen Paket sideEffects korrekt setzen, also Stildateien ausnehmen, und Sammeldateien mit Weiterexporten in der eigenen Anwendung meiden.
Symptom
Im Bündel steckt Code für Sprachstände und Browser, die in keiner Anforderung stehen.
Ursache
Die Zielbrowser sind zu weit gefasst, deshalb erzeugt die Bau-Kette zusätzliche Umwandlungen und lädt Polyfills mit ein.
Lösung
Die Browserliste an den tatsächlichen Bedarf anpassen und das Ergebnis vergleichen. Der Befehl npx browserslist zeigt, welche Browser aus der aktuellen Angabe folgen, und die Zahl überrascht regelmäßig.
Symptom
Nach dem Aufteilen in viele kleine Bündel wurde die Seite langsamer statt schneller.
Ursache
Die Teile hängen voneinander ab und werden nacheinander geladen, statt gleichzeitig. Zusätzlich taucht gemeinsam genutzter Code in mehreren Teilen auf.
Lösung
Weniger und größere Schnitte setzen, entlang der Navigation statt je Komponente, und die Bündel des nächsten Schritts über modulepreload ankündigen, damit sie parallel geladen werden.
Fünf Fragen an einen zu großen Build
- 01 Liegt dieselbe Bibliothek in zwei Versionen im Baum? npm ls zeigt es.
- 02 Kommt eine ganze Bibliothek herein, obwohl du eine Funktion brauchst?
- 03 Liefert die Abhängigkeit nur CommonJS? Dann bleibt im Zweifel alles drin.
- 04 Stehen in der Browserliste noch Browser, die du nicht mehr bedienst?
- 05 Lädt die Startseite Code für Bereiche, die kaum jemand öffnet?
Was du danach in wenigen Minuten beantworten kannst
Die Reihenfolge ist immer dieselbe: messen, die größten Beiträge benennen, die Ursache je Beitrag bestimmen und erst dann eingreifen. Für die vier häufigsten Ursachen gibt es jeweils eine Standardantwort, die keine Architekturdiskussion braucht.
Die Zusammensetzung sichtbar machen
Ein Analysewerkzeug zeigt je Modul den Anteil am Ergebnis. Für Rollup und Vite gibt es rollup-plugin-visualizer, für webpack den Bundle Analyzer, und mit vorhandenen Source Maps liefert source-map-explorer dieselbe Aufschlüsselung für ein bereits gebautes Bündel.
Doppelte Versionen finden
npm ls mit dem Paketnamen zeigt, wie oft eine Bibliothek im Baum steckt und wer sie jeweils verlangt. Zwei Fassungen derselben Bibliothek bedeuten zwei Kopien im Bündel, und das lässt sich über das Feld overrides zusammenführen.
Gezielt statt pauschal importieren
Ein Import aus einer Sammeldatei zieht im Zweifel das ganze Paket herein. Nimm den direkten Pfad zur einzelnen Funktion oder ein Paket, das seine Funktionen einzeln ausliefert, dann bleibt nur, was du wirklich verwendest.
Die Browserliste prüfen
Polyfills und die Ausgabe für ältere Sprachstände richten sich nach den eingestellten Zielbrowsern. Steht dort noch eine Fassung, die niemand mehr benutzt, bezahlen alle anderen dafür mit zusätzlichem Code in jedem Bündel.
Nach Route aufteilen
Ein dynamisches import() erzeugt ein eigenes Bündel, das erst beim Betreten des Bereichs geladen wird. Sinnvoll sind Schnitte entlang der Navigation und um selten geöffnete Bereiche wie Verwaltung, Editor oder Druckansicht.
Eine Grenze in die Pipeline setzen
Eine Prüfung der Bündelgröße bei jeder Zusammenführung meldet den Zuwachs, solange er noch einer Änderung zuzuordnen ist. Ohne diese Grenze wächst das Ergebnis unbemerkt weiter, bis es wieder eine eigene Aufräumaktion braucht.
KI-generiertDieses Bild wurde mit KI erzeugt · Yves Hoppe / KI / cmt
Erst sichtbar machen, dann anfassen
Ein Analysewerkzeug zeigt das Ergebnis als Fläche: Jedes Modul bekommt einen Anteil, verschachtelt nach Verzeichnissen. Damit ist in wenigen Sekunden klar, ob das größte Stück aus deinem Code oder aus node_modules kommt, und welches Paket es ist. Für Rollup und Vite gibt es rollup-plugin-visualizer, für webpack den Bundle Analyzer, und wenn nur ein fertiges Bündel mit Source Map vorliegt, liefert source-map-explorer dieselbe Auskunft nachträglich.
Achte dabei genau darauf, welche Zahl du liest. Die Werkzeuge zeigen bis zu drei: die Größe der Eingabedatei, die Größe nach der Minimierung und die Größe nach der Komprimierung. Für die Übertragung zählt die letzte, für die Zeit, die der Browser mit Auswerten und Ausführen verbringt, die mittlere. Wer Erfolge in der komprimierten Zahl misst, unterschätzt regelmäßig, was auf dem Gerät tatsächlich passiert.
Eine zweite Sicht liefert der Coverage-Bereich der Entwicklungswerkzeuge: Er zeigt je Datei, wie viel davon beim Laden der Seite tatsächlich ausgeführt wurde. Wichtig ist die Deutung. Nicht ausgeführt heißt nicht überflüssig, sondern nur, dass dieser Teil bisher nicht gebraucht wurde. Als Hinweis darauf, welche Bereiche sich zum Nachladen eignen, ist die Ansicht trotzdem der schnellste Einstieg.
Wo die Masse üblicherweise herkommt
Der häufigste einzelne Posten ist eine Bibliothek, von der eine Funktion gebraucht wird. Bei der Datumsformatierung lohnt vorher der Blick auf Intl.DateTimeFormat und Intl.NumberFormat: Beide sind im Browser vorhanden, beherrschen Sprachen und Zeitzonen und kosten null Bytes im Bündel. Für echtes Rechnen mit Datumsangaben bleibt eine Bibliothek sinnvoll, dann aber eine, aus der sich einzelne Funktionen importieren lassen.
Der zweite Posten sind Sammlungen: Symbolbibliotheken, Komponentenbaukästen und Werkzeugkisten, die über eine einzige Einstiegsdatei alles weiterexportieren. Ob dabei nur das Verwendete übrig bleibt, hängt vollständig davon ab, ob das Paket ESM ausliefert und Seiteneffekte korrekt angibt. Die Aufschlüsselung beantwortet die Frage in Sekunden, jede Diskussion darüber dauert länger.
Der dritte Posten sind doppelte Abhängigkeiten, und die fallen in der Aufschlüsselung sofort auf, weil derselbe Name zweimal erscheint. Der Grund steht in npm ls: zwei Pakete, die unterschiedliche Versionsbereiche verlangen. Neben der Größe entsteht dabei ein zweites Problem, denn zwei Fassungen bedeuten zwei getrennte Zustände, und Prüfungen mit instanceof schlagen zwischen ihnen fehl.
Was die Werkzeuge nicht entfernen dürfen
Tree Shaking ist keine Magie, sondern statische Analyse, und die funktioniert nur bei ES-Modulen, weil dort feststeht, was exportiert und was importiert wird. Bei CommonJS wird module.exports zur Laufzeit zusammengesetzt, deshalb muss das Werkzeug alles behalten. Ein Paket, das nur eine CommonJS-Fassung mitbringt, landet also vollständig im Bündel, selbst wenn du eine einzige Funktion daraus verwendest.
Der zweite Grund sind Seiteneffekte. Wenn ein Modul beim Laden etwas tut, ein Polyfill setzt, ein Element registriert oder eine Stildatei einbindet, darf es nicht entfernt werden, auch wenn keiner seiner Exporte verwendet wird. Pakete geben über das Feld sideEffects an, ob sie frei davon sind. Steht dort false, obwohl Stildateien importiert werden, verschwinden diese Stile im Produktionsbau, deshalb gehören sie ausdrücklich in die Ausnahmeliste.
Der dritte Grund ist dynamischer Zugriff. Wer Module über berechnete Namen ausliest oder Funktionen über ein Objekt mit variablen Schlüsseln nachschlägt, macht die Analyse unmöglich. Dasselbe gilt für Sammeldateien mit Weiterexporten in der eigenen Anwendung: bequem beim Importieren, und zugleich sorgen sie dafür, dass jede Datei, die eine einzige Funktion importiert, den gesamten Baum dahinter mitzieht.
Aufteilen mit Absicht
Ein dynamisches import() ist die einzige Anweisung, die einen Schnitt erzeugt. Alles andere sind Einstellungen darüber, wie die entstehenden Teile zusammengelegt werden. Sinnvolle Schnitte liegen dort, wo Nutzer ohnehin auf etwas warten: beim Wechsel der Route, beim Öffnen eines Dialogs, beim ersten Aufruf eines Editors oder einer Diagrammansicht. Ein Schnitt je Komponente bringt dagegen wenig und erzeugt viele kleine Anfragen.
Das gern gehörte Rezept vom großen gemeinsamen Fremdcode-Bündel ist überholt. Ein einziges Bündel mit allen Abhängigkeiten wird bei jeder Aktualisierung eines einzigen Pakets vollständig ungültig, und der Zwischenspeicher der wiederkehrenden Besucher ist wertlos. Besser ist eine Aufteilung nach tatsächlichen Nutzungsgruppen, mit stabilen Namen, damit unveränderte Teile im Zwischenspeicher bleiben.
Wenn ein nachgeladenes Bündel selbst wieder etwas nachlädt, entsteht eine Kette, in der jede Anfrage auf die vorige wartet. Dagegen hilft es, den nächsten Schritt anzukündigen, im Browser über einen Eintrag mit modulepreload im Dokument. Und beim Nachladen auf Interaktion lohnt es sich, den Ladevorgang schon beim Überfahren des Elements zu starten, dann ist das Bündel beim Klick oft bereits da.
Was Größe auf dem Gerät bedeutet
Übertragen wird komprimiert, verarbeitet wird unkomprimiert. Der Browser muss den Code auspacken, auswerten und ausführen, und diese Arbeit findet im Hauptstrang statt, also genau dort, wo auch die Bedienung stattfindet. Deshalb ist es irreführend, die Größe von JavaScript mit der Größe von Bildern zu vergleichen: Ein Bild kostet Übertragung, ein Bündel kostet Übertragung und Rechenzeit.
Der Unterschied zwischen Geräten ist dabei größer als der zwischen Verbindungen. Ein aktuelles Notebook wertet ein paar hundert Kilobyte in wenigen Millisekunden aus, ein Telefon aus der Mittelklasse braucht dafür ein Vielfaches. Wenn du die Auswirkung ernsthaft beurteilen willst, miss mit eingeschalteter CPU-Drosselung, und zwar an derselben Stelle, an der du später die Verbesserung nachweisen willst.
Zum Schluss eine Frage der Reihenfolge: Nicht alles, was groß ist, ist auch dringend. Ein umfangreicher Editor, der erst nach einem Klick geladen wird, stört niemanden. Ein kleines Analyseskript, das das Anzeigen der Seite verzögert, sehr wohl. Die brauchbare Zielgröße ist deshalb nicht das gesamte Bündel, sondern die Menge an Code, die vor der ersten Bedienbarkeit ausgeführt werden muss.
Dazu passende Kurse
Mit einer Aufschlüsselung nach Modul siehst du in wenigen Minuten, was tatsächlich in deinem Build landet , und genau dieses Vorgehen üben die JavaScript-Kurse bei cmt.
Wie ein Projekt entsteht, das von Anfang an kleine Auslieferungspakete erzeugt, zeigen Kurse zu Aufbau und Konfiguration moderner Frontend-Builds .
Wie sicher bist du beim Thema wirklich?
Lesen fühlt sich schnell nach Können an. Ein kurzer Test zeigt dir, was davon schon sitzt und wo sich ein Kurs lohnt. Kostenlos, ohne Anmeldung, mit einer Erklärung zu jeder Antwort.
Wo du genau das übst
Ein sehr gutes, praxisorientiertes und nachhaltiges Seminar. So sollte es immer sein. Vielen Dank.
Die Schulung ist gerade für Einsteiger oder Entwickler*innen mit eingestaubtem Basiswissen sehr hilfreich.
Ich bin sehr glücklich Franz als Trainer gehabt zu haben. Er hat offensichtlich unglaublich viel Wissen zu den Themen, und schafft es, dieses auch interaktiv und verständlich weiterzugeben.
Häufige Fragen
Ab welcher Größe ist ein Bundle zu groß?
Lohnt es sich, eine Bibliothek durch eigenen Code zu ersetzen?
Warum entfernt der Bundler ungenutzte Exporte nicht?
Ist ein großes gemeinsames Bündel für Fremdcode gut für den Zwischenspeicher?
Deine Ansprechpartner
Du bist dir nicht sicher, welcher Kurs oder welches Level zu dir passt? Wir beraten dich persönlich und kostenlos.
Yves Hoppe
Weiterbildung & Beratung
Hilft dir, aus dem Webentwicklung-Programm den passenden Kurs für deinen Stand zu finden.
Norbert Jansen
Beratung & Inhouse
Plant mit dir Inhouse-Trainings, die auf eure Abläufe und euren Datenbestand zugeschnitten sind.
Builds, deren Größe du erklären kannst
In den Kursen zu modernen Bau-Werkzeugen bei cmt schlüsselst du ein echtes Bündel auf, findest die größten Beiträge und siehst, was ein Schnitt an der richtigen Stelle bringt.