JavaScript-Bundler Turbopack: "Mit den Erfahrungen aus zehn Jahren Webpack"

Tobias Koppers entwickelte einst Webpack und arbeitet nun am Nachfolger Turbopack mit deutlichem Performance-Boost. Im Interview erklärt er die Hintergründe.

Lesezeit: 15 Min.
In Pocket speichern
vorlesen Druckansicht Kommentare lesen 8 Beiträge
Business,Concept,-,High,Speed,Abstract,Mrt,Track,Of,Motion

(Bild: voyata/Shutterstock.com)

Von
  • Maika Möbus
Inhaltsverzeichnis

Das quelloffene React-Framework Next.js ist kürzlich in Version 13 erschienen und brachte den JavaScript-Bundler Turbopack mit. Er ist als Nachfolger von Webpack angelegt, soll bis zu 700-mal schneller sein als der bewährte Bundler und befindet sich derzeit in der Alpha-Phase. An der Spitze der Entwicklung von Turbopack steht Webpack-Erfinder Tobias Koppers, der im April 2021 dem hinter Next.js stehenden Unternehmen Vercel beigetreten ist. Mit ihm sprach heise Developer über die Entstehungshintergründe und Funktionsweise des neuen Open-Source-Tools Turbopack. Dabei kann er auf Erfahrungen aus zehn Jahren Webpack-Entwicklung zurückgreifen.

heise Developer: Hallo, Tobias. Wir sprechen heute über Bundler – daher zunächst einmal: Welche Funktion erfüllen JavaScript-Bundler und wann benötigt man sie?

Tobias Koppers: In der ursprünglichen Bedeutung ist ein JavaScript-Bundler ein Werkzeug, um die JavaScript-Dateien einer Webanwendung zu einem sogenannten "Bundle", also einer einzigen (oder wenigen) JavaScript-Datei(en), die den kompletten Code der Anwendung enthält, zu verpacken. Dabei bedient sich der Bundler eines Modulsystems, beispielsweise ECMAScript Modules, CommonJS oder AMD, um Beziehungen zwischen den Dateien zu identifizieren und den richtigen Code in die richtige Reihenfolge zu bringen.

Interviewgast Tobias Koppers

Tobias Koppers ist ein Softwareentwickler aus Bayern. Während er eigentlich C++ und C# verwendete, stolperte er für ein Projekt in die JavaScript/Web-Entwicklung. Dort begann er auch mit dem Open-Source-Projekt webpack, welches er nun schon seit 10 Jahren entwickelt. Seit fast zwei Jahren arbeitet er nun bei Vercel, wo er nun auch noch an den Open-Source-Projekten Next.js und Turbopack entwickelt.

Heutzutage fassen wir den Begriff Bundler etwas weiter. Er ist mehr zu einem Compiler, Optimierer und Entwicklungswerkzeug für Webanwendungen geworden. Er beschränkt sich jetzt nicht nur auf JavaScript, sondern handhabt auch andere Ressourcen einer Webanwendung. Er bündelt CSS, optimiert Bilder und Schriften oder kümmert sich um die Cachebarkeit von statischen Ressourcen. Außerdem transformiert er Ressourcen untereinander, zum Beispiel TypeScript zu JavaScript, PNG zu WebP, oder modernes JavaScript zu JavaScript für ältere Browser. Meist macht er das aber nicht selbst, sondern verwendet eine Reihe von Plug-ins, die die benötigten Funktionen hinzufügen.

Während der Entwicklung unterstützt ein Bundler oft durch das Bereitstellen eines sogenannten Dev-Servers, der die Anwendung "just in time" kompiliert und Änderungen direkt mit dem Browser synchronisiert. Außerdem stellt er sogenannte SourceMaps zur Verfügung, die das Debuggen im Browser ermöglichen.

Theoretisch bräuchte man keinen Bundler, um Webanwendungen zu entwickeln. In der Praxis wäre das aber in mehrerlei Hinsicht sehr ineffizient. Zum einen macht der Verzicht auf Entwicklungswerkzeuge und Sprachen wie TypeScript die Entwicklung der Anwendung aufwendig und langsam für Entwicklerinnen und Entwickler. Zum anderen ist eine unoptimiert ausgelieferte Webanwendung langsam für den Endnutzer. Somit verwenden in der Praxis so gut wie alle Webanwendungen den ein oder anderen Bundler.

Das von dir entwickelte und 2012 erschienene Webpack ist heute der meistgenutzte JavaScript-Bundler und weist auf npm derzeit über 25 Millionen Downloads pro Woche auf. Hattest du mit dieser Erfolgsgeschichte gerechnet, als du ihn entwickelt hast?

Ich war damals selbst neu in der Webentwicklung und wusste nicht einmal vom Potenzial von Bundlern, zumal Bundler damals nicht einen so großen Stellenwert hatten wie heute. Vor zehn Jahren waren Webanwendungen im heutigen Sinne, mit vergleichsweise großem Volumen an JavaScript-Code, erst noch am Entstehen. Ich muss auch zugeben, dass Webpack damals etwas Glück hatte und den richtigen Zeitpunkt getroffen hat. Webpack ist entstanden, weil ich "Code Splitting", also das dynamische Nachladen von Teilen der Anwendung, in existierenden Bundlern vermisst habe. Es ist also von Anfang an auf größere Anwendungen ausgelegt gewesen, was dann sehr gut zu dem Wachstum in der Webentwicklung gepasst hat.

Heise-Konferenz: enterJS Web-Performance Day

Die Veranstalter heise Developer, iX und dpunkt.verlag richten am am 1. Dezember 2022 den enterJS Web-Performance Day aus. Performance-Expertinnen und -Experten zeigen in sieben Online-Vorträgen, wie sich Webanwendungen beschleunigen lassen. Der Frühbucherrabatt gilt bis 15. November.

Deep Dives bieten zwei 2-Tages-Workshops, die ebenfalls online stattfinden:

Im Rahmen deiner Tätigkeit bei Vercel bist du nun federführend bei der Entwicklung des JavaScript-Bundlers Turbopack, der kürzlich in Next.js 13 als Alpha-Version erschienen ist und Webpack ablösen soll. Was hat dich dazu bewogen und was sind die Vorteile von Turbopack gegenüber Webpack?

Als ich bei Vercel begonnen habe, war es eine meiner Aufgaben, die Performance von Webpack (und Next.js) zu optimieren. Hauptsächlich die Performance während der Entwicklung, also des Dev-Servers, aber auch die Performance von Produktions-Builds, zum Beispiel für Vercel-Preview-Deployments. Zunächst haben wir damit große Erfolge erzielt, die dann auch in Next.js 12 und Webpack 5 geflossen sind. Allerdings wurde es naturgemäß mit der Zeit immer schwieriger, große Verbesserungen zu erzielen und gleichzeitig rückwärtskompatibel zu bleiben. Es war praktisch unmöglich, die unterliegende Architektur zu ändern.

Webpack ist nun 10 Jahre alt. Ich denke, jeder, der schon einmal an einem so alten Projekt gearbeitet hat, hat schon mal gedacht: "Eigentlich müsste man das alles nochmal ganz von vorne neu implementieren." Meist mangelt es aber an der Zeit oder dem Team, um ein solches Mammutprojekt zu beginnen. Bei Vercel habe ich die Chance gesehen, das wirklich umzusetzen. Zum einen stehen uns hier die Mittel, zum anderen aber auch die Akzeptanz zur Verfügung, so ein lang angelegtes Projekt zu beginnen. Ich denke, nicht jedes Unternehmen würde es akzeptieren, wenn man ein Jahr damit verbringt, alles noch einmal zu implementieren. Zum anderen sah ich auch die notwendigen Talente bei Vercel, die die notwendige Erfahrung mitbringen, um zu einem solchen Projekt beizutragen.

Ich habe ein paar ambitionierte Ideen niedergeschrieben und eine Art Vision geschaffen. Performanz war dabei natürlich ein wichtiger Punkt. Die Vision war und ist immer noch: "Incremental first". Inkrementelles Kompilieren, also erneutes Kompilieren, nachdem ein Teil der Anwendung geändert wurde, sollte eine hohe Priorität, also die beste Performance haben. Des Weiteren sollten möglichst viele Builds inkrementell sein. Das sollte unter anderem durch zwei Maßnahmen erreicht werden: eine neue Architektur für inkrementelle Builds und Caching auf mehreren Ebenen (im Speicher, auf der lokalen Platte und in der Cloud). Ein wichtiges Ziel war: Inkrementelle Builds sollen immer konstante Performanz haben, egal wie groß die Anwendung ist.

Das alles hat mit einem Prototypen dieser Architektur begonnen. Ich hatte erst mit einer Woche gerechnet, allerdings hat es dann doch mehrere Wochen gedauert, um die richtige Architektur zu finden. Ich musste mehrfach alles umkrempeln, denn komplett unabhängig von der Größe der Anwendung zu sein, ist im Detail doch kniffliger als gedacht. Einfaches Caching reicht hier nicht aus, da selbst das Laden von Ergebnissen aus dem Cache für alle Module der Anwendung leider abhängig von der Größe der Anwendung ist.

Vereinfach ausgedrückt, baut die neue Architektur einen Abhängigkeitsgraphen aller aufgerufenen Funktionen beim Kompilieren der Anwendung auf. Beim inkrementellen Bauen der Anwendung kann dann dieser Graph von den geänderten Dateien aus verfolgt werden, um genau die Funktionen neu zu berechnen, die potenziell von den Änderungen betroffen sind. Für nicht geänderte Dateien muss exakt gar nichts getan werden, was es zu erreichen galt. Somit sind inkrementelle Builds nur noch abhängig davon, wieviel tatsächlich geändert werden muss, und nicht mehr von der Gesamtgröße der Anwendung.

Auf dieser Low-Level-Architektur haben wir dann Turbopack aufgebaut. Allerdings unterscheidet sich Turbopack auch in der High-Level-Architektur von Webpack. Hier kann man zwei gravierende Unterschiede hervorheben:

Turbopack wurde von Grund auf als "lazy" angelegt. Das bedeutet, es arbeitet nur, wenn auch nach etwas gefragt wird. Bei Starten der Dev-Server passiert zunächst nicht viel, sondern erst, wenn der Nutzer nach einer HTML-Seite fragt. Dann wird genau so viel verarbeitet, um diese HTML-Seite an den Browser auszuliefern. Tatsächlich wird zum Beispiel der Code für die referenzierten JavaScript-Dateien hier noch nicht generiert. Das geschieht erst, wenn der Browser danach fragt. Dieses Konzept zieht sich dann durch den kompletten Ablauf, zum Beispiel auch für CSS, APIs, Bilder oder SourceMaps. Im Vergleich dazu verarbeitet Webpack die komplette Anwendung gleich zu Beginn und kann erst dann dem Browser antworten, wenn das komplett abgeschlossen ist. Es gibt zwar einige Tricks, wie Webpacks lazyCompilation-Experiment oder Next.js' On-demand-entries-Plug-in, die eine ähnliche Idee verfolgen. Sie beziehen sich allerdings nur auf ausgewählte Elemente, hier nur auf ganze Seiten und dynamische Imports.

Ein weiterer Unterschied besteht darin, wie mit gemischten Anwendungen umgegangen wird. Gemischt bezieht sich hier auf Anwendungen, die Client- und Server-Code enthalten. In Next.js war es dafür nötig, mehrere Webpack-Compiler einzusetzen, um Code für den Client, den Code für serverseitiges Rendering und den Code für die neuen React-Server-Komponenten zu verarbeiten. Das erforderte zusätzlichen Aufwand für die Koordination der verschiedenen Compiler. Turbopack erlaubt es nun, diese verschiedenen Kontexte zu mischen, und baut einen einzigen Graphen für die Anwendung, der ein beliebiges Wechseln zwischen Client und Server etc. erlaubt.

In Details finden sich noch viele weitere Unterscheide. Allerdings gibt es auch sehr viele Gemeinsamkeiten mit Webpack. Vor allem wird sich Turbopack auch an den erfolgreichen Konzepten bezüglich Flexibilität und Erweiterbarkeit an Webpack orientieren.

Wodurch zeichnet sich Turbopack im Vergleich zu ähnlichen Tools aus, die Webpack ebenfalls Konkurrenz machen wollen, beispielsweise das auf den JavaScript- und CSS-Bundler esbuild setzende Build-Tool Vite.js?

Vite.js ist im Vergleich zu Webpack und Turbopack ein sogenannter "Non-Bundler". Für den Nutzer sieht das von außen im Prinzip gleich aus, allerdings unterscheidet es sich hinsichtlich des internen Ablaufs. Vite.js baut auf den vor einigen Jahren eingeführten ECMAScript-Modulen auf, die die Auslieferung von modularem Code direkt an den Browser ermöglichen. Damit ist es nicht notwendig, die Anwendung in ein Bundle zu verpacken. Stattdessen liefert Vite.js einfach jedes Modul einzeln aus und vertraut auf den Browser, diese in der richtigen Reihenfolge auszuführen und zu verbinden. Damit werden inkrementelle Builds praktisch unnötig, da sich die geänderten Module einfach einzeln anfragen lassen. Vite.js hat damit das Performanceproblem existierender Bundler "gelöst", in denen inkrementelle Builds langsamer werden, wenn die Anwendung sehr groß wächst.

Allerdings hat ein Non-Bundler meiner Meinung nach auch einige Einschränkungen verglichen mit einem Bundler: Zum einen verliert man die Kontrolle über das Modulsystem, da nun das Modulsystem des Browsers verwendet wird, was Einschränkungen zum Beispiel für Hot-Module-Replacement (das Austauschen von Modulen der Anwendung ohne deren Neuladen) nach sich zieht. Zum anderen führen größere Anwendungen dazu, dass sehr viele Anfragen vom Browser zum Dev-Server nötig sind. Das reduziert die Performance des initialen Ladens der Anwendung. Das gilt insbesondere dann, wenn die Browser-Dev-Tools geöffnet sind und versuchen, mehrere tausend HTTP-Anfragen dazustellen. Das gleiche Problem macht es auch sehr ineffizient, das "Non-Bundler"-Prinzip für den finalen Produktions-Build einzusetzen, womit dann für diesen doch wieder ein Bundler eingesetzt werden muss. Das führt zu Unterschieden zwischen Entwicklungs- und Produktions-Umgebung, was eine zusätzliche Fehlerquelle darstellt.

Tatsächlich verfolgt Vite.js sogar einen hybriden Ansatz aus Non-Bundler und Bundler. Anwendungscode wird nicht gebundled, Third-Party-Code wird gebundled. Somit kann es je nach Anteil von Anwendungs- zu Third-Party-Code diese Einschränkungen abmildern. Aufgrund dieser Einschränkungen haben wir einen Bundler bevorzugt und das Performanceproblem stattdessen durch die oben genannte Architektur gelöst. Außerdem war es uns wichtig, auch Produktions-Builds zu beschleunigen. Mit Turbopack können wir zwei Probleme mit einer Klappe schlagen.

esbuild kann Produktions-Builds in erstaunlicher Geschwindigkeit durchführen, da es sich auf dieselben fokussiert. Leider wird diese Geschwindigkeit durch Einschränkungen bei der Flexibilität und Erweiterbarkeit erreicht. Wir wollten ein Werkzeug schaffen, das sich mehr an Webpack bezüglich dieser Eigenschaften orientiert. Außerdem lässt sich esbuild schwieriger zur Entwicklungszeit einsetzen, da inkrementelle Builds nur sehr wenig Geschwindigkeitsvorteil gegenüber einem kompletten Build bieten. Das mag für kleinere und mittlere Anwendungen sehr gut funktionieren, wir zielen aber auch auf sehr große Anwendungen ab. Der Plan: ein Werkzeug für die nächsten zehn Jahre zu bauen – und wer weiß, wie groß Webanwendungen in zehn Jahren sein werden.

Es gibt auch noch Rollup.js, das sich vor allem durch seine Fähigkeit, besonders effizient ECMAScript-Module zu bundlen, auszeichnet. Es verwendet ein paar spannende, innovative Konzepte, die voraussichtlich auch in den Produktions-Mode von Turbopack einfließen werden.

Was bedeutet die Einführung von Turbopack für Webentwicklerinnen und Webentwickler, die derzeit Webpack verwenden und künftig zu Turbopack wechseln möchten? Wie können sie deiner Meinung nach eine möglichst reibungslose Umstellung erreichen?

Aktuell ist Turbopack noch im Alpha-Stadium. Da heißt, für Nutzer von Webpack bedeutet es noch sehr wenig, weil wichtige Features noch fehlen, um Webpack zu ersetzen. Zum Beispiel hat Turbopack noch keine Plug-ins und auch keine Möglichkeiten zur Konfiguration, auch wenn es intern schon so angelegt ist, um beides zu ermöglichen. Wir wollten Turbopack allerdings trotzdem so früh öffentlich machen, um das Mitwirken der Community zu ermöglichen. In den nächsten Monaten werden wir daran arbeiten, die fehlenden Elemente zu ergänzen und auch Feedback der Community bezüglich einiger Dinge einholen – wahrscheinlich in Form vom RFCs.

Unser Ziel ist, einen sehr großen Teil der Funktionalität von Webpack in Turbopack zu integrieren. Next.js wird dann eines von hoffentlich vielen Framework-Plug-ins, die Framework-spezifische Funktionalitäten kapseln. Ein Ziel ist es auch, einige Funktionen aus Next.js direkt in Turbopack zu integrieren, um sie auch einfacher für andere Frameworks zur Verfügung zu stellen, zum Beispiel Werkzeuge für die Anzeige von Fehlern und zur Analyse der Performance und Bundle-Größe. Es ist auch geplant, Werkzeuge und Dokumentation für Webpack-Nutzer bereitzustellen, um den Umstieg so einfach wie möglich zu machen. Allerdings sind wir, wie gesagt, noch nicht ganz so weit.

Wie sind die weiteren Pläne für Turbopack und gibt es eine Roadmap, wann die erste Beta-Version und die erste stabile Version erscheinen sollen?

Es war geplant, eine öffentliche Roadmap bereitzustellen, allerdings haben wir das zur Next.js-Konferenz nicht mehr geschafft. Wir werden das in den nächsten Wochen nachholen.

Unser grundsätzlicher Plan wird sich zunächst auf den Entwicklungsmodus (Dev-Server) konzentrieren, bevor wir mit Produktions-Builds beginnen. Wir wollen, dass zumindest für die Entwicklung eine möglichst große Menge an Entwicklerinnen und Entwicklern Turbopack verwenden können. Dazu werden wir die fehlenden Funktionalitäten aus Next.js hinzufügen. Außerdem bedarf es noch Verbesserungen der Stabilität. Parallel dazu werden wir Themen wie Plug-ins, Konfiguration und Caching (auf Festplatte und in der Cloud) erforschen. Auch wollen wir Turbopack mehr öffnen. Die Architektur für inkrementelle Builds soll eine separate OSS-Bibliothek werden und Turbopack soll separat von Next.js verwendbar sein.

Es liegt auf jeden Fall noch viel Arbeit vor uns, aber auch sehr spannende Themen. Ich persönlich finde es toll, mit den Erfahrungen aus zehn Jahren Webpack und Webentwicklung etwas Neues zu gestalten.

Vielen Dank für das Interview!

(mai)