JS
() =>
JavaScript · Tree Shaking · Performance · Bundle-Optimierung
JavaScript Tree Shaking
Bundle-Größe durch Dead Code Elimination halbieren

Tree Shaking ist der Prozess, mit dem moderne Bundler ungenutzten JavaScript-Code aus dem finalen Bundle entfernen. Was simpel klingt, hat komplexe Voraussetzungen: ES-Module sind Pflicht, bestimmte Code-Muster blockieren Tree Shaking, und falsch konfigurierte sideEffects-Flags können gesamte Module eliminieren oder bewahren — mit messbaren Auswirkungen auf Ladezeiten.

13 Min. Lesezeit ESM · sideEffects · Rollup · Webpack · Vite · Bundle-Analyse Dead Code Elimination · Scope Hoisting

1. Was Tree Shaking ist und warum es wichtig ist

Tree Shaking ist die Technik, mit der JavaScript-Bundler ungenutzte Exporte und deren Abhängigkeiten aus dem finalen Bundle entfernen. Der Name kommt vom Bild, einen Baum zu schütteln — was nicht befestigt ist, fällt herunter. In der Praxis bedeutet das: Wenn eine Anwendung nur format aus einer Utility-Bibliothek mit hundert Funktionen importiert, enthält das finale Bundle nur format und dessen Abhängigkeiten — nicht die anderen 99 Funktionen. Das reduziert die Bundle-Größe direkt proportional zum Anteil ungenutzten Codes.

Die Auswirkungen auf Ladezeiten sind erheblich. Jedes Kilobyte JavaScript bedeutet Parse-Zeit, Kompilierungszeit und Übertragungszeit — besonders auf mobilen Geräten und schwachen Netzwerken. Bibliotheken wie lodash haben in ihrer CJS-Version mehrere hundert Kilobyte; mit korrektem Tree Shaking landet nur der tatsächlich genutzte Code im Bundle. Das ist kein akademisches Detail: Studien zeigen, dass jede Sekunde Ladezeit die Conversion-Rate um mehrere Prozent senkt. Tree Shaking ist damit nicht nur eine technische Optimierung, sondern direkt geschäftsrelevant.

2. Warum ES-Module Pflicht sind: statische Import-Analyse

Der Kern von Tree Shaking ist die statische Analyse von Import- und Export-Beziehungen. Nur wenn ein Bundler zur Build-Zeit — ohne Code auszuführen — bestimmen kann, welche Exporte wirklich verwendet werden, kann er sicher entscheiden, welche Teile er entfernen darf. ES-Module ermöglichen das: import und export sind statische Konstrukte — sie stehen immer auf der obersten Ebene, können nicht bedingt sein und müssen statische String-Literale als Pfade haben. Das erlaubt dem Bundler, den vollständigen Import-Export-Graphen zu konstruieren, bevor eine einzige Zeile ausgeführt wird.

CommonJS-Module (require() und module.exports) sind dagegen dynamisch: require() kann zur Laufzeit mit einem berechneten Pfad aufgerufen werden, Exporte können zur Laufzeit als Properties von module.exports hinzugefügt werden. Das macht statische Analyse unmöglich. Ein Bundler, der auf ein CJS-Modul trifft, muss das gesamte Modul in das Bundle aufnehmen — er kann nicht wissen, welche Exporte in einem fremden Runtime-Kontext genutzt werden. Tree Shaking funktioniert deshalb ausschließlich mit ESM — das ist keine Designentscheidung der Bundler, sondern eine mathematische Notwendigkeit aus der Dynamik von CJS.


// ESM: static — bundler can analyze at build time WITHOUT executing code
import { formatDate, parseDate } from "./date-utils.js";
// Bundler knows: only formatDate and parseDate are used
// → everything else in date-utils.js is candidate for tree-shaking

// CJS: dynamic — bundler cannot know what will be used at runtime
const utils = require("./date-utils"); // entire module included
utils.formatDate(new Date());
// OR: computed require — impossible to analyze statically
const fnName = "format" + "Date";
const fn = require("./date-utils")[fnName]; // bundler gives up

// ESM: conditional imports are forbidden — enables static graph
// This is INVALID in ESM (syntax error):
// if (condition) { import { x } from "./mod.js"; }

// Valid ESM alternative: dynamic import() — but affects tree-shaking
const { x } = await import(condition ? "./a.js" : "./b.js");
// Bundler includes both ./a.js and ./b.js when condition is dynamic

3. Das sideEffects-Flag in package.json

Das sideEffects-Feld in der package.json einer Bibliothek ist der wichtigste Hebel für Tree Shaking bei externen Abhängigkeiten. Ohne dieses Feld muss der Bundler konservativ sein: Ein importiertes Modul könnte Seiteneffekte beim Import haben (CSS-Injection, globale Registrierungen, Prototyp-Erweiterungen) und darf deshalb nicht entfernt werden, selbst wenn kein Export des Moduls verwendet wird. Mit "sideEffects": false signalisiert die Bibliothek: Kein Modul hat Seiteneffekte beim Import — der Bundler darf sicher alle ungenutzten Module entfernen.

"sideEffects": ["*.css", "polyfills.js"] ist das differenzierte Muster: Die meisten Dateien haben keine Seiteneffekte, aber CSS-Importe und die Polyfill-Datei müssen bewahrt werden. Das ist für Komponenten-Bibliotheken wie Material UI oder Ant Design kritisch — sie importieren CSS als Seiteneffekt, und ohne explizite Ausnahme im sideEffects-Array würde Tree Shaking diese CSS-Importe entfernen und das Styling kaputt machen. Als Bibliotheks-Autor ist das sideEffects-Feld Pflicht — ohne es liefert man seinen Nutzern schlechteres Tree Shaking als nötig.

4. Code-Muster, die Tree Shaking verhindern

Verschiedene JavaScript-Muster sind für Bundler bei der statischen Analyse undurchsichtig und verhindern effektives Tree Shaking. Das häufigste: Objekt-Exporte, bei denen alle Funktionen als Properties eines einzigen Exports gebündelt werden. export default { formatDate, parseDate, ... } exportiert ein Objekt — der Bundler kann nicht wissen, welche Properties des Objekts genutzt werden, und muss das gesamte Objekt beibehalten. Named Exports (export function formatDate()) ermöglichen dagegen präzises Tree Shaking auf Funktionsebene.

Ein weiteres kritisches Muster: IIFE-wrapped Module und Klassen mit statischen Methoden. Da Klassen in JavaScript mutierbar sind und statische Methoden zur Laufzeit hinzugefügt werden können, behandeln Bundler Klassen konservativ — sie behalten die gesamte Klasse, auch wenn nur eine statische Methode verwendet wird. Das Gegenpattern: Standalone-Funktionen als named Exports statt als statische Methoden. Wer eine Utility-Bibliothek schreibt, sollte bewusst auf Klassen-basierte APIs verzichten, wenn Tree Shaking wichtig ist.


// date-utils.js — tree-shaking friendly library design

// BAD: default export object — bundler cannot tree-shake individual functions
export default {
  formatDate: (d) => d.toISOString(),
  parseDate: (s) => new Date(s),
  diffDays: (a, b) => Math.abs(a - b) / 86400000,
  // 97 more functions — ALL included even if you only use formatDate
};

// GOOD: named exports — each function is individually tree-shakeable
export function formatDate(date) {
  return date.toISOString();
}

export function parseDate(str) {
  return new Date(str);
}

export function diffDays(dateA, dateB) {
  return Math.abs(dateA - dateB) / 86400000;
}

// Consumer only imports what they need
import { formatDate } from "./date-utils.js";
// parseDate and diffDays are excluded from the bundle

// BAD: class with static methods — entire class is retained
export class DateUtils {
  static formatDate(d) { return d.toISOString(); }
  static parseDate(s) { return new Date(s); }
}
// import { DateUtils } from "./date-utils" then DateUtils.formatDate()
// → entire class bundle, even if only formatDate is used

// Pure annotation — hint to bundler that function has no side effects
export const expensiveCalc = /*#__PURE__*/ computeConstants();
// Without /*#__PURE__*/, bundler assumes function call has side effects

5. Tree-Shaking-freundliche Bibliotheken schreiben

Eine Tree Shaking-freundliche Bibliothek zu schreiben erfordert bewusste Entscheidungen auf mehreren Ebenen. Erstens: Nur Named Exports verwenden, niemals Default-Export-Objekte. Zweitens: "sideEffects": false in der package.json setzen, es sei denn, bestimmte Dateien haben tatsächlich Seiteneffekte. Drittens: Das ESM-Build als module-Einstiegspunkt in der package.json angeben, zusätzlich zum CJS-Build unter main. Bundler bevorzugen den module-Einstiegspunkt für Tree Shaking.

Der vierte wichtige Punkt: Keine Barrel-Dateien (index.js) mit Re-Exports aus vielen Untermodulen, wenn jedes Untermodul seiteneffektfrei ist. Barrel-Dateien, die hunderte von Exporten re-exportieren, machen dem Bundler die Analyse schwer und führen oft dazu, dass mehr Code als nötig eingeschlossen wird. Stattdessen: Deep Imports ermöglichen (import { formatDate } from "date-utils/date"`), oder die Barrel-Datei explizit als "sideEffects": false markieren und auf korrekte ESM-Struktur setzen, damit der Bundler die Re-Export-Kette auflösen kann.

6. Tree Shaking mit Webpack und Rollup konfigurieren

Rollup war der erste Bundler mit nativem Tree Shaking-Support und ist bis heute der Standard für Library-Builds. Rollup's Tree Shaking ist besonders aggressiv: Es analysiert den vollständigen Abhängigkeitsgraphen und entfernt alles, was nicht zum Output beiträgt. Für Anwendungsbundles mit Webpack: Tree Shaking ist in Production-Mode automatisch aktiv durch die Kombination aus mode: "production" (aktiviert TerserPlugin für Dead Code Elimination), optimization.usedExports: true (markiert nicht verwendete Exporte) und optimization.sideEffects: true (liest das sideEffects-Flag aus package.json).

Wichtig: Webpack's Tree Shaking funktioniert zweistufig. Zunächst markiert es ungenutzte Exporte mit einem Kommentar (/* unused harmony export */). In einem zweiten Schritt entfernt TerserPlugin diese markierten Exports tatsächlich aus dem minimierten Output. Ohne TerserPlugin (also im Development-Mode) sind ungenutzte Exporte im Bundle vorhanden, aber markiert — sichtbar im unminimiertem Build. Das ist wichtig für das Debugging: Wer Tree Shaking debuggen will, muss im Production-Mode bauen oder Terser explizit aktivieren.

7. Tree Shaking: ESM vs. CJS vs. IIFE im Vergleich

Die Wahl des Modul-Formats hat direkte Auswirkungen auf die Effektivität von Tree Shaking. ESM ermöglicht vollständiges Tree Shaking — der Bundler kann den gesamten Import-Export-Graphen statisch analysieren. CJS ist dynamisch und ermöglicht kein Tree Shaking auf Modul-Ebene — der Bundler muss das gesamte Modul einschließen. IIFE-Bundles sind unveränderbar — kein Bundler kann aus einem fertig gebündelten IIFE-Output weiteres Tree Shaking betreiben.

Format Tree Shaking möglich? Typischer Einsatz Bundler-Support
ESM (.mjs, type:module) Vollständig Alle modernen Apps und Libraries Rollup, Webpack 5, Vite, esbuild
CJS (.cjs, require) Nicht möglich Legacy Node.js, alte Bibliotheken Kein Bundler kann CJS shaken
UMD Nicht möglich Browser + Node kompatibel, veraltet CJS-Pfad dominiert, kein Shaking
IIFE Nicht möglich Script-Tag Einbindung, CDN Fertig gebündelt, nicht analysierbar
ESM + CJS dual Über module-Feld Moderne Libraries mit Rückwärtskompatibilität Bundler nutzen module-Feld für ESM

Für Library-Autoren ist das duale Publish-Pattern die Empfehlung: ESM-Build unter exports["."].import und module, CJS-Build unter exports["."].require und main. Bundler bevorzugen automatisch das ESM-Build und ermöglichen damit Tree Shaking. Node.js ohne Bundler nutzt das CJS-Build für Rückwärtskompatibilität. Mit dem neuen exports-Feld in package.json (Conditional Exports) ist das sauber konfigurierbar.

8. Bundle-Analyse: toten Code mit Tools sichtbar machen

Ohne Analyse-Tools ist es unmöglich zu wissen, ob Tree Shaking tatsächlich greift und was im Bundle steckt. Das wichtigste Tool für Webpack-Projekte ist webpack-bundle-analyzer — es erstellt eine interaktive Treemap, die zeigt, welche Module wie viel Platz im Bundle einnehmen. Große, unerwartete Blöcke zeigen sofort, wo Tree Shaking nicht funktioniert oder wo Bibliotheken ohne Tree-Shaking-Support eingebunden sind. Für Vite-Projekte bietet rollup-plugin-visualizer dieselbe Funktionalität.

Ein zweites wichtiges Tool ist bundle-buddy oder die Webpack-integrierte stats.json-Ausgabe, die zeigt, welche Module aus welchen Dateien kommen und wie viel Code aus jeder Abhängigkeit tatsächlich im Bundle landet. Für schnelle Checks ohne Build-Integration: bundlephobia.com zeigt für jede npm-Bibliothek, ob sie Tree-Shaking unterstützt und wie groß der minimale Import einer einzelnen Funktion ist. Das ist der erste Check, bevor eine neue Abhängigkeit ins Projekt aufgenommen wird.


// webpack.config.js — full tree shaking configuration
const { BundleAnalyzerPlugin } = require("webpack-bundle-analyzer");

module.exports = {
  mode: "production", // enables tree shaking + terser minification
  optimization: {
    usedExports: true,    // marks unused exports for removal
    sideEffects: true,    // respects package.json "sideEffects" field
    concatenateModules: true, // scope hoisting — reduces module overhead
    minimize: true,       // terser removes marked unused exports
  },
  plugins: [
    // Generate interactive bundle visualization
    new BundleAnalyzerPlugin({
      analyzerMode: "static", // creates report.html — no server needed
      openAnalyzer: false,
    }),
  ],
};

// package.json of a tree-shaking-friendly library
// {
//   "name": "my-utils",
//   "main": "./dist/index.cjs",        // CJS for Node.js require()
//   "module": "./dist/index.mjs",      // ESM for bundlers (tree shaking)
//   "exports": {
//     ".": {
//       "import": "./dist/index.mjs",  // ESM — preferred by bundlers
//       "require": "./dist/index.cjs"  // CJS — for legacy Node.js
//     }
//   },
//   "sideEffects": false               // no modules have import side effects
// }

// Mark a specific function call as pure (no side effects)
// so bundler can remove it if result is unused
const VERSION = /*#__PURE__*/ computeVersion();

9. Scope Hoisting: der Partner von Tree Shaking

Scope Hoisting — in Webpack als Module Concatenation bekannt — ist die natürliche Ergänzung zu Tree Shaking. Ohne Scope Hoisting wrapt ein Bundler jedes Modul in eine eigene Funktion, um den Scope zu isolieren. Das erzeugt erheblichen Overhead: Tausende kleiner Wrapper-Funktionen im Bundle, jede mit ihrem eigenen Scope-Setup. Mit Scope Hoisting fasst der Bundler Module, die statisch verknüpft sind und keine zyklischen Abhängigkeiten haben, in einem einzigen Scope zusammen — weniger Funktionen, weniger Overhead, kleineres Bundle, schnellere Ausführung.

Scope Hoisting setzt, wie Tree Shaking, ESM voraus. CJS-Module müssen separat gewrapped bleiben, weil ihre dynamische Natur eine statische Fusion unmöglich macht. In Webpack ist Scope Hoisting mit optimization.concatenateModules: true aktiv (Standard in Production-Mode). Rollup macht Scope Hoisting immer — es ist ein fundamentales Designziel von Rollup, nicht eine optionale Optimierung. Das ist einer der Hauptgründe, warum Rollup-Bundles für Libraries so effizient sind: kein Modul-Wrapper-Overhead, kein duplizierter Scope-Setup-Code.

10. Zusammenfassung

Tree Shaking ist eine der effektivsten Performance-Optimierungen für JavaScript-Anwendungen — aber nur, wenn die Voraussetzungen stimmen. ES-Module sind zwingend notwendig, da nur sie die statische Import-Export-Analyse ermöglichen, die Bundler für Tree Shaking benötigen. Das sideEffects-Feld in package.json ist für Bibliotheks-Autoren Pflicht — ohne es verhindert der Bundler vorsichtshalber das Entfernen von Modulen. Named Exports statt Default-Export-Objekte, keine Klassen mit statischen Methoden und der /*#__PURE__*/-Kommentar für seiteneffektfreie Funktionsaufrufe sind die Code-Patterns, die Tree Shaking auf Funktionsebene ermöglichen.

Mit Bundle-Analyse-Tools wie webpack-bundle-analyzer und rollup-plugin-visualizer lässt sich messen, ob Tree Shaking tatsächlich greift und welche Bibliotheken ungenutzten Code ins Bundle bringen. Das Ergebnis konsequenten Tree Shakings ist messbar: Bundles, die nur den tatsächlich verwendeten Code enthalten, laden schneller, parsen schneller und führen schneller aus — besonders auf mobilen Geräten, wo diese Unterschiede in direktem Zusammenhang mit Nutzererfahrung und Conversion-Rate stehen.

Mironsoft

JavaScript-Performance, Bundle-Optimierung und Build-Pipeline-Modernisierung

Bundle zu groß? Wir analysieren und optimieren.

Wir analysieren euren Build-Output, identifizieren Bibliotheken ohne Tree-Shaking-Support und optimieren die Build-Konfiguration für maximale Dead-Code-Elimination.

Bundle-Analyse

Vollständige Analyse des Build-Outputs — tote Code identifizieren, Bibliotheken ohne Tree-Shaking aufspüren

Build-Optimierung

Webpack/Vite-Konfiguration für maximales Tree Shaking, Scope Hoisting und Code-Splitting

Library-Design

Tree-Shaking-freundliche API-Gestaltung, sideEffects-Konfiguration und Dual-Publish-Setup

JavaScript Tree Shaking — Das Wichtigste auf einen Blick

ESM ist Pflicht

Tree Shaking funktioniert nur mit ES-Modulen. CJS (require) und IIFE erlauben keine statische Import-Analyse — Bundler müssen alles einschließen.

sideEffects-Flag

"sideEffects": false in package.json signalisiert: Alle Module sicher entfernbar. Array für Ausnahmen (CSS, Polyfills). Pflicht für jede Bibliothek.

Named Exports bevorzugen

export function foo() statt export default { foo }. Klassen mit statischen Methoden blockieren Tree Shaking. /*#__PURE__*/ für seiteneffektfreie Calls.

Analyse-Tools

webpack-bundle-analyzer: Treemap des Bundle-Inhalts. rollup-plugin-visualizer für Vite. bundlephobia.com: Tree-Shaking-Support vor npm install prüfen.

11. FAQ: JavaScript Tree Shaking und Bundle-Größe

1Was ist Tree Shaking?
Bundler-Technik zum Entfernen ungenutzter Exporte und deren Abhängigkeiten aus dem finalen Bundle. Nur tatsächlich genutzter Code landet im Output — messbar kleinere Bundles.
2Warum nicht mit CommonJS?
require() ist dynamisch — Bundler können CJS nicht statisch analysieren und müssen alles einschließen. Nur ESM mit statischen import/export erlaubt Tree Shaking.
3sideEffects: false bedeutet?
Kein Modul hat Seiteneffekte beim Import — Bundler kann alle ungenutzten Module sicher entfernen. Ohne dieses Flag muss er konservativ vorgehen und mehr einschließen.
4Welche Muster blockieren Tree Shaking?
Default-Export-Objekte, Klassen mit statischen Methoden, dynamische Property-Zugriffe, require() mit berechneten Pfaden. Named Exports und standalone Funktionen ermöglichen Shaking.
5Webpack Tree Shaking aktivieren?
mode: 'production' aktiviert es automatisch. Explizit: usedExports: true, sideEffects: true, minimize: true mit Terser. Development-Mode markiert nur, entfernt nicht.
6Tree Shaking vs. Scope Hoisting?
Tree Shaking entfernt ungenutzten Code. Scope Hoisting fasst Module zusammen und eliminiert Wrapper-Overhead. Beide setzen ESM voraus, ergänzen sich und sind in Production-Mode aktiv.
7Wie prüfen ob Tree Shaking funktioniert?
webpack-bundle-analyzer: Interaktive Treemap. Im Webpack-Output nach '/* unused harmony export */' suchen. bundlephobia.com vor npm install für Tree-Shaking-Support.
8/*#__PURE__*/ wofür?
Hint an Bundler: Dieser Funktionsaufruf hat keine Seiteneffekte, kann entfernt werden wenn das Ergebnis nicht genutzt wird. Nützlich für Modul-Level-Berechnungen und Factory-Aufrufe.
9Wie soll eine Library aufgebaut sein?
Named Exports, sideEffects: false, ESM unter "module"-Feld, CJS unter "main". Conditional Exports mit dem exports-Feld für präzise Pfadsteuerung und Rückwärtskompatibilität.
10Vite ohne Konfiguration?
Ja — Vite nutzt Rollup für Production-Builds mit nativem Tree Shaking und Scope Hoisting. Voraussetzung: Abhängigkeiten mit ESM-Build und korrektem sideEffects-Flag.