JS
() =>
JavaScript · Rollup · Vite · Performance · Build-Tools
Bundle-Analyse mit Rollup und Vite
Bundles verstehen, optimieren, dauerhaft kontrollieren

Ein 2-MB-JavaScript-Bundle ist keine Seltenheit, aber es ist fast immer vermeidbar. Bundle-Analyse mit Rollup und Vite macht unsichtbare Probleme sichtbar: Tree-Shaking-Lücken, doppelte Dependencies, unnötige Polyfills – und liefert die Grundlage für gezielte Optimierung.

13 Min. Lesezeit Rollup · Vite · Tree-Shaking · Code-Splitting · CI-Budget Rollup 4.x · Vite 5.x · Node.js 18+

1. Warum Bundle-Analyse entscheidend ist

JavaScript-Bundle-Größe ist einer der direktesten Einflussfaktoren auf die Ladezeit einer Web-Anwendung. Jedes Kilobyte JavaScript muss heruntergeladen, geparst und kompiliert werden, bevor die erste Interaktion möglich ist. Lighthouse und Core Web Vitals messen diese Zeit direkt: Time to Interactive (TTI) und Total Blocking Time (TBT) sinken proportional zur eingesparten Bundle-Größe. Eine Bundle-Analyse mit Rollup oder Vite zeigt, welche Teile des Bundles tatsächlich wertvoll sind und welche unnötigerweise mitgeschleppt werden.

Das überraschende Ergebnis einer ersten Bundle-Analyse in einem gewachsenen Projekt: Oft machen wenige große Pakete den Großteil des Bundles aus – Datumsbibliotheken wie Moment.js mit vollständigen Locale-Dateien, UI-Komponentenbibliotheken, die zu großen Teilen importiert aber kaum genutzt werden, oder Polyfills für Browser, die die Zielgruppe längst nicht mehr nutzt. Ohne visuelle Bundle-Analyse sind diese Probleme unsichtbar, weil npm ls und package.json nur die Pakete zeigen, nicht deren kompilierte Größe im tatsächlichen Bundle.

2. rollup-plugin-visualizer einrichten

Das rollup-plugin-visualizer ist das Standard-Tool für die Bundle-Analyse in Rollup-Projekten. Es generiert nach dem Build einen interaktiven Treemap-Report als HTML-Datei, der zeigt wie groß jedes Modul im finalen Bundle ist – in drei Darstellungen: treemap (verschachtelt nach Modul-Hierarchie), sunburst (radialer Kreis) und network (Abhängigkeitsgraph). Installiert wird es mit npm install -D rollup-plugin-visualizer und in die Rollup-Konfiguration eingetragen.

Der Visualizer bietet drei Größenmodi: stat (unkomprimierte Größe vor der Bundling-Verarbeitung), parsed (Größe nach dem Bundling, vor Minifikation) und gzip (simulierte Gzip-komprimierte Größe). Für Performance-Entscheidungen ist der gzip-Modus am relevantesten, weil er der tatsächlich übertragenen Größe am nächsten kommt. Der Unterschied kann erheblich sein: Ein lodash-chunk, der als parsed 50 KB groß erscheint, kann nach gzip nur 15 KB groß sein.


// rollup.config.js — Bundle analysis with rollup-plugin-visualizer
import { visualizer } from 'rollup-plugin-visualizer';
import { defineConfig } from 'rollup';

export default defineConfig({
  input: 'src/main.js',
  output: {
    dir: 'dist',
    format: 'es',
    chunkFileNames: '[name]-[hash].js',
  },
  plugins: [
    // Only run visualizer in analysis mode to avoid bloating normal builds
    process.env.ANALYZE && visualizer({
      filename: 'dist/bundle-report.html',
      open: true,          // auto-open browser after build
      gzipSize: true,      // show gzip-estimated sizes
      brotliSize: true,    // show brotli-estimated sizes
      template: 'treemap', // treemap | sunburst | network | list | raw-data
    }),
  ].filter(Boolean),
});

// Run analysis: ANALYZE=1 rollup -c
// Or add to package.json:
// "analyze": "ANALYZE=1 rollup -c"

3. Bundle-Analyse in Vite-Projekten

Vite basiert intern auf Rollup für Production-Builds, weshalb das rollup-plugin-visualizer direkt in der Vite-Konfiguration verwendet werden kann. Alternativ gibt es vite-bundle-visualizer als eigenständiges Tool: Es ruft intern vite build auf und erzeugt direkt einen Visualizer-Report, ohne die Vite-Konfiguration anfassen zu müssen. Das ist für schnelle Einmalanalysen ideal, ohne das Projekt dauerhaft anzupassen.

Vite bietet zusätzlich die eingebaute Option build.rollupOptions.output.manualChunks, mit der man gezielt bestimmt, welche Module in welchen Chunks landen. Ohne diese Konfiguration entscheidet Rollup autonom über die Chunk-Aufteilung, was bei großen Abhängigkeitsbäumen zu suboptimalen Ergebnissen führen kann. Eine typische Optimierung: Alle großen Third-Party-Bibliotheken in einen separaten vendor-Chunk packen, der sich seltener ändert und somit länger im Browser-Cache bleibt als der Anwendungs-Code.


// vite.config.js — Bundle analysis and chunk optimization
import { defineConfig } from 'vite';
import { visualizer } from 'rollup-plugin-visualizer';

export default defineConfig({
  build: {
    rollupOptions: {
      output: {
        // Manual chunk splitting for better caching
        manualChunks(id) {
          // Large UI framework in its own chunk
          if (id.includes('node_modules/vue') || id.includes('node_modules/@vue')) {
            return 'vue-vendor';
          }
          // Chart library (heavy) in dedicated chunk
          if (id.includes('node_modules/chart.js') || id.includes('node_modules/echarts')) {
            return 'charts';
          }
          // All other node_modules in vendor chunk
          if (id.includes('node_modules')) {
            return 'vendor';
          }
        },
        // Deterministic chunk names for better cache invalidation
        chunkFileNames: 'assets/[name]-[hash].js',
        entryFileNames: 'assets/[name]-[hash].js',
        assetFileNames: 'assets/[name]-[hash][extname]',
      },
    },
  },
  plugins: [
    process.env.ANALYZE === 'true' && visualizer({
      filename: 'dist/bundle-analysis.html',
      open: true,
      gzipSize: true,
      template: 'treemap',
    }),
  ].filter(Boolean),
});
// ANALYZE=true vite build

4. Tree-Shaking-Probleme erkennen und beheben

Tree-Shaking – das automatische Entfernen von nicht genutztem Code durch Rollup – funktioniert nur unter bestimmten Bedingungen. Es funktioniert ausschließlich mit ES Modules (import/export), nicht mit CommonJS (require()). Es funktioniert nicht wenn ein Modul Seiteneffekte hat, die nicht deklariert sind. Es funktioniert nicht wenn man ein Objekt als Ganzes importiert statt einzelne named exports. Die Bundle-Analyse macht sichtbar, wann Tree-Shaking nicht greift: eine gesamte Bibliothek erscheint im Bundle, obwohl nur eine Funktion davon genutzt wird.

Ein häufiges Tree-Shaking-Problem in der Bundle-Analyse: Lodash. import _ from 'lodash' oder import { debounce } from 'lodash' schließt das gesamte Lodash-Bundle ein (~530 KB ungezipped), weil Lodash das Common JS-Format nutzt und kein Tree-Shaking ermöglicht. Die Lösung: import debounce from 'lodash/debounce' (direkter Dateipfad) oder der Wechsel zu lodash-es, der ES-Module-kompatibel ist. Eine andere häufige Quelle: import * as Icon from '@heroicons/react' – das importiert alle Heroicons statt nur die benötigten.

5. Code-Splitting: Chunks sinnvoll aufteilen

Code-Splitting ist die wichtigste Technik, um die initiale Bundle-Größe zu reduzieren. Statt einer einzelnen JavaScript-Datei erzeugt das Build-Tool mehrere Chunks, von denen der Browser beim ersten Laden nur den benötigten lädt. Rollup und Vite unterstützen zwei Arten von Code-Splitting: automatisches Splitting bei dynamischen import()-Aufrufen und manuelles Splitting via manualChunks-Konfiguration.

Die Bundle-Analyse hilft beim Erkennen von suboptimalem Code-Splitting: Wenn viele kleine Chunks entstehen (z.B. durch zu aggressives manuelles Splitting oder viele kleine dynamische Imports), entstehen unnötig viele HTTP-Requests und das Browser-Preloading wird ineffizient. Wenn ein einzelner Chunk sehr groß ist, blockiert er das Laden. Das Gleichgewicht: Third-Party-Bibliotheken in stabile, selten ändernde Chunks; Route-spezifischer Code in eigene Chunks für lazy Loading; gemeinsam genutzte Utilities in shared-Chunks, die von mehreren Routen parallel genutzt werden.


// Advanced manualChunks strategy based on bundle analysis findings
// Use after visualizer reveals inefficient chunk distribution

export default defineConfig({
  build: {
    rollupOptions: {
      output: {
        manualChunks(id) {
          // Route-level splitting — each route loads its own chunk
          if (id.includes('/pages/dashboard/')) return 'page-dashboard';
          if (id.includes('/pages/checkout/'))  return 'page-checkout';
          if (id.includes('/pages/admin/'))     return 'page-admin';

          // Heavy optional features — only loaded when needed
          if (id.includes('node_modules/monaco-editor')) return 'monaco';
          if (id.includes('node_modules/pdf-lib'))       return 'pdf';

          // Stable third-party: long-lived cache, rarely changes
          if (id.includes('node_modules')) return 'vendor';
        },
      },
    },
    // Warn when any chunk exceeds 500 KB (before gzip)
    chunkSizeWarningLimit: 500,
  },
});

// After building, verify chunks with:
// ls -lh dist/assets/ | sort -k5 -h
// Large unexpected chunks = manualChunks needs adjustment

6. Doppelte Dependencies finden

Eine häufige Ursache für unnötige Bundle-Größe in der Bundle-Analyse: doppelte Dependencies. Das entsteht, wenn zwei Pakete verschiedene Versionen derselben Abhängigkeit mitbringen, die nicht dedupliziert werden können – weil die Versionen semantisch inkompatibel sind. Im Visualizer erscheint die Bibliothek dann mehrfach im Bundle, unter verschiedenen Chunk-Pfaden oder Versionsverzeichnissen. Ein typisches Beispiel: React in v17 und v18 gleichzeitig im Bundle, wenn eine alte Bibliothek noch auf React v17 zeigt.

Das Tool npm ls zeigt die vollständige Dependency-Baumstruktur und macht doppelte Versionen sichtbar. npm dedupe oder pnpm dedupe versucht, Duplikate aufzulösen. In Vite-Projekten kann man resolve.dedupe in der Konfiguration setzen, um sicherzustellen, dass ein Paket immer aus einer einzigen Quelle aufgelöst wird. Bei hartnäckigen Duplikaten – wenn eine Bibliothek explizit eine andere Version braucht – ist das Upgrade der problematischen Bibliothek meist die einzige saubere Lösung.

7. Dynamische Imports und Lazy Loading

Dynamische Imports (import('./modul.js')) sind die direkteste Methode, Code aus dem initialen Bundle zu verlagern. Rollup und Vite erkennen dynamische Imports und erstellen automatisch separate Chunks für jedes dynamisch importierte Modul. Der Browser lädt diese Chunks nur, wenn der dynamische Import tatsächlich aufgerufen wird – nicht beim initialen Laden der Seite.

In der Bundle-Analyse nach der Einführung dynamischer Imports ist es wichtig zu prüfen, ob die Chunks auch tatsächlich als separate Dateien erzeugt werden. Rollup kann unter bestimmten Umständen Chunks inlinen, wenn sie für zu klein oder zu eng verbunden mit dem Hauptchunk befunden werden. Das output.inlineDynamicImports-Flag muss auf false stehen (Default). Auch die Chunk-Namen sollten aussagekräftig sein: /* webpackChunkName: "feature-x" */ funktioniert nicht in Rollup/Vite – stattdessen verwendet man rollupOptions.output.chunkFileNames oder benennt das dynamisch importierte Modul entsprechend.

8. Bundle-Budget in CI durchsetzen

Die Bundle-Analyse ist am wertvollsten, wenn sie nicht nur einmalig manuell durchgeführt wird, sondern als automatisierter Schritt in der CI-Pipeline läuft. Mehrere Tools erlauben das Definieren eines Bundle-Budgets: Wenn der Build ein definiertes Limit überschreitet, schlägt die Pipeline fehl. Das verhindert, dass neue Dependencies oder unvorsichtige Imports das Bundle unbemerkt aufblähen.

Rollup schreibt Bundle-Statistiken in das bundle-Objekt des generateBundle-Hooks. Ein einfaches Skript kann nach dem Build die Chunk-Größen auslesen und mit definierten Limits vergleichen. Vite 5.x bietet build.chunkSizeWarningLimit als einfache Warnungsschwelle. Für präzisere Budget-Checks eignet sich das Tool bundlewatch, das über einen PR-Kommentar genau anzeigt um wie viel KB ein Bundle durch einen Commit gewachsen ist – ein starkes Signal für Code-Reviewer, die keine Build-Tools auf ihrer lokalen Maschine ausführen.


// ci-bundle-check.js — Fail CI if any chunk exceeds size limits
// Run after: vite build --reporter=json
import { readFileSync, readdirSync, statSync } from 'node:fs';

// Budget definitions (in bytes, before gzip)
const BUDGETS = {
  'vendor': 300 * 1024,   // 300 KB — stable third-party code
  'main':   100 * 1024,   // 100 KB — app entry point
  'page-':  80  * 1024,   // 80 KB per page chunk
  default:  150 * 1024,   // 150 KB fallback for all other chunks
};

const distDir = 'dist/assets';
const files = readdirSync(distDir).filter(f => f.endsWith('.js'));

let failed = false;

for (const file of files) {
  const size = statSync(`${distDir}/${file}`).size;

  // Find matching budget key
  const budgetKey = Object.keys(BUDGETS).find(k => file.startsWith(k)) ?? 'default';
  const limit = BUDGETS[budgetKey];

  const kb = (size / 1024).toFixed(1);
  const limitKb = (limit / 1024).toFixed(0);

  if (size > limit) {
    console.error(`BUDGET EXCEEDED: ${file} is ${kb}KB (limit: ${limitKb}KB)`);
    failed = true;
  } else {
    console.log(`OK: ${file} — ${kb}KB / ${limitKb}KB`);
  }
}

if (failed) process.exit(1);

9. Rollup vs. Vite Bundle-Output im Vergleich

Obwohl Vite intern Rollup für Production-Builds verwendet, unterscheiden sich die Out-of-the-Box-Ergebnisse. Vite hat eigene Defaults für Code-Splitting, Chunk-Naming und das Handling von CSS-in-JS, die sich von einem reinen Rollup-Setup unterscheiden. Die Bundle-Analyse beider Tools zeigt diese Unterschiede: Vite erzeugt standardmäßig agressiveres Code-Splitting mit mehr, kleineren Chunks; ein reines Rollup-Setup erzeugt standardmäßig weniger, größere Chunks.

Eigenschaft Rollup (direkt) Vite (Rollup-basiert) Empfehlung
Analyse-Tool rollup-plugin-visualizer visualizer oder vite-bundle-visualizer Beide nutzen gleichen Visualizer
Standard-Splitting Konservativ Aggressiver (mehr Chunks) manualChunks in beiden konfigurieren
Tree-Shaking Vollständig konfigurierbar Vollständig konfigurierbar Gleich gut – beide nutzen Rollup
CSS-Handling Plugin nötig Eingebaut Vite für Full-Stack-Apps
Build-Speed Langsamer (JS-Bundler) Schneller (Rolldown-Migration) Vite für Developer Experience

Die Bundle-Analyse ist in beiden Tools identisch aussagekräftig, weil der gleiche Visualizer verwendet wird. Der Workflow ist immer gleich: Build erzeugen, Visualizer-Report öffnen, größte Module identifizieren, Tree-Shaking-Probleme beheben, Code-Splitting anpassen, neu bauen, vergleichen. Das iterative Vorgehen – Build analysieren, optimieren, erneut analysieren – ist dabei wirksamer als jede einzelne Maßnahme allein.

Mironsoft

Frontend-Performance, Build-Optimierung und Core Web Vitals

JavaScript-Bundle zu groß und Ladezeit zu hoch?

Wir analysieren Ihr Bundle mit rollup-plugin-visualizer, identifizieren Tree-Shaking-Lücken, doppelte Dependencies und falsch konfiguriertes Code-Splitting – und liefern einen konkreten Optimierungsplan.

Bundle-Audit

Visualizer-Analyse, Tree-Shaking-Check, Duplicate-Detection und Chunk-Bewertung

Optimierung

manualChunks-Strategie, dynamische Imports, Bibliotheksmigration und Polyfill-Bereinigung

CI-Integration

Bundle-Budget in Pipeline einrichten, bundlewatch-Integration und PR-Kommentare für Größenänderungen

10. Zusammenfassung

Die Bundle-Analyse mit Rollup und Vite macht unsichtbare Bundle-Probleme sichtbar und ist der erste Schritt jeder substanziellen Performance-Optimierung. Das rollup-plugin-visualizer – in Rollup direkt, in Vite als Plugin – erzeugt interaktive Treemap-Reports, die zeigen welche Module wie viel Platz im Bundle belegen. Die drei häufigsten Findings: fehlgeschlagenes Tree-Shaking durch CommonJS-Dependencies, falsch konfiguriertes Code-Splitting das entweder zu wenige oder zu viele Chunks erzeugt, und doppelte Dependencies durch Versions-Konflikte.

Die Optimierungsstrategie folgt einem klaren Ablauf: Analysieren, größte Module identifizieren, Tree-Shaking durch korrekte Import-Syntax korrigieren, manualChunks für strategisches Code-Splitting konfigurieren, schwere Bibliotheken durch leichtere Alternativen ersetzen und dynamische Imports für wenig genutzte Features einführen. Das Bundle-Budget in CI – ein Skript, das den Build fehlschlägt wenn Limits überschritten werden – stellt sicher, dass diese Optimierungen nicht durch zukünftige Entwicklungen rückgängig gemacht werden. Bundle-Analyse ist keine einmalige Aufgabe, sondern ein kontinuierlicher Prozess.

Bundle-Analyse mit Rollup und Vite — Das Wichtigste auf einen Blick

Tool einrichten

npm install -D rollup-plugin-visualizer. In Rollup/Vite als Plugin. ANALYZE=1 als Env-Variable. gzipSize: true für realistische Größenangaben.

Tree-Shaking reparieren

import { debounce } from 'lodash/debounce' statt lodash als Ganzes. lodash-es statt lodash für ES-Module. Kein import * as wenn einzelne exports ausreichen.

Code-Splitting

manualChunks für vendor/app/page-Trennung. Dynamische import() für Routen und optionale Features. chunkSizeWarningLimit als Frühwarnsystem.

CI-Budget

Bundle-Größen nach Build prüfen und Pipeline bei Überschreitung fehlschlagen lassen. bundlewatch für PR-Kommentare mit Größenänderungen pro Commit.

11. FAQ: Bundle-Analyse mit Rollup und Vite

1Was ist Bundle-Analyse und warum wichtig?
Visualisiert welche Module wie viel Platz im Bundle belegen. Unverzichtbar für TTI und Core Web Vitals. Tree-Shaking-Lücken und unnötige Dependencies sind ohne Visualisierung unsichtbar.
2rollup-plugin-visualizer installieren?
npm install -D rollup-plugin-visualizer. In rollup.config.js oder vite.config.js als Plugin. Nur bei ANALYZE=1 aktivieren um normale Builds nicht zu verlangsamen.
3Wann funktioniert Tree-Shaking nicht?
Bei CommonJS (require). Bei import * as ... statt named exports. Bei Paketen mit nicht deklaerierten Seiteneffekten. Lösung: lodash-es, direkte Pfade, ES-Module-Pakete bevorzugen.
4Was ist manualChunks?
rollupOptions.output.manualChunks bestimmt welche Module in welche Chunks landen. node_modules → vendor. Schwere Libs → eigene Chunks. Routes → page-Chunks für Lazy Loading.
5Doppelte Dependencies finden?
Im Visualizer: gleiche Bibliothek mehrfach. CLI: npm ls <package>. Lösung: npm dedupe oder Vite resolve.dedupe. Oder problematische Bibliothek upgraden.
6stat vs. parsed vs. gzip im Visualizer?
stat: vor Bundling. parsed: nach Bundling, vor Minifikation. gzip: simulierte Übertragungsgröße — am relevantesten für Performance-Entscheidungen.
7Bundle-Budget in CI implementieren?
Nach Build Chunk-Dateigrößen mit Limits vergleichen, process.exit(1) bei Überschreitung. Oder bundlewatch für PR-Kommentare mit Größenänderungen pro Commit.
8Wann dynamische Imports nutzen?
Router-Views, selten genutzte Features (PDF-Export, Charts), Admin-Bereiche, schwere Bibliotheken. import('./modul') erstellt automatisch separaten Chunk.
9Vite vs. Rollup bei Bundle-Analyse?
Vite nutzt Rollup intern — gleicher Visualizer. Vite hat eigene Defaults (aggressiveres Splitting, eingebautes CSS). manualChunks funktioniert in beiden identisch.
10Wie viel kann Bundle-Analyse einsparen?
In nicht optimierten Projekten oft 40–70%. Moment.js → date-fns: 200 KB. Lodash → lodash-es: 300+ KB. Route-Splitting: 60–80% des initialen Loads vermieden.