Vite.js: Rasantes Build-Tool aus der Vue.js-Schmiede

Seite 2: Alle Zeichen stehen auf Geschwindigkeit

Inhaltsverzeichnis

Vite strebt im Kern dasselbe an wie webpack (nämlich die Entwicklung von JavaScript-Projekten mit einem produktiven Build am Ende), allerdings verfolgt es eine andere Philosophie. Entwickler verwenden im Gegensatz zu vielen Endkunden moderne Browser, Vites mitgelieferter Development-Server macht sich das zunutze. So lassen sich ECMAScript-Module direkt per ‹script type="module"› -Tag einbinden und jeder zeitgemäße Browser mit dynamic import-Unterstützung kann sie laden, interpretieren und ausführen. Jedes import im JavaScript-Quellcode wandelt der Browser in eine HTTP-Anfrage um, die dann direkt beim Development-Server landet. Über das --debug-Flag gibt Vite einen genauen Log aus, der unter anderem auch die Auflösung (vite:resolve) von HTTP-Anfragen zeigt:

vite:resolve 0ms   react -> C:/dev/vite-example/node_modules/.vite/react.js?v=f9c662d4&es-interop +800ms
vite:resolve 0ms   react-dom -> C:/dev/vite-example/node_modules/.vite/react-dom.js?v=f9c662d4&es-interop +9ms
vite:resolve 1ms   ./index.css -> C:/dev/vite-example/src/index.css +3ms
vite:resolve 1ms   ./App -> C:/dev/vite-example/src/App.jsx +2ms
vite:resolve 0ms   /src/App.jsx -> C:/dev/vite-example/src/App.jsx +1ms
vite:load 7ms   [fs] /node_modules/.vite/react.js?v=f9c662d4 +782ms
vite:resolve 0ms   ./logo.svg -> C:/dev/vite-example/src/logo.svg +117ms
vite:resolve 0ms   ./App.css -> C:/dev/vite-example/src/App.css +1ms

Fragt der Browser jede Datei einzeln an, führt das auch bei steigender Dateianzahl zu reduzierter Geschwindigkeit. Um das zu vermeiden, verfügt Vite über die Fähigkeit, unabhängige Einstiegspunkte zu ermitteln und so Code-Splitting zu praktizieren. Taucht ein JavaScript-Modul beispielsweise auf einer Unterseite auf, ist es auch erst beim Betreten dieser Unterseite erforderlich – nur im Bedarfsfall bearbeitet Vite das Modul und schickt es zum Browser. Das spart vor allem dadurch Zeit, dass die Module on demand von Vite geparst und kompiliert werden, also nicht wie bei webpack gesammelt vorab:

Name Pfad Protokoll Größe Zeit
localhost / http/1.1 145 B 12 ms
client /@vite/client http/1.1 17.2 kB 11 ms
main.jsx /src/main.jsx http/1.1 1.6 kB 7 ms
react.js?v=2a1b6b4a /node_modules/.vite/react.js http/1.1 850 B 9 ms
react-dom.js?v=2a1b6b4a /node_modules/.vite/react-dom.js http/1.1 2.4 MB 105 ms
index.css /src/index.css http/1.1 1.1 kB 63 ms
App.jsx /src/App.jsx http/1.1 6.5 kB 70 ms
localhost / ws 0 B 3 ms

Ein weiteres Problem ist das Wasserfallszenario: Mit jeder angeforderten Datei kommt im schlimmsten Fall eine Vielzahl weiterer Module hinzu, die der Browser dann anfordern muss. Selbst wenn er diese Anfragen nahezu sofort beantwortet, sorgt vor allem der allgemeine HTTP-Overhead für Zeitverlust (s. obiges Listing). Zudem ist die Anzahl laufender Anfragen browserseitig begrenzt. Clevere Optimierungen wie das zuvor erwähnte Code-Splitting können das verhindern, indem sie die Browser-Caching-Funktionalität verwenden und die Dependencies beim Start des Servers in einer einzigen Datei zusammenfassen. So führt der Browser je Dependency nur noch genau eine Anfrage durch.

In der JavaScript-Welt sind ECMAScript-Module nicht die einzige Möglichkeit, Dateien in einer anderen Datei zu nutzen. Alternativ gibt es Methoden wie das synchrone, auf JavaScript-Backends beschränkte CommonJS (via require(...) und module.exports) oder die sogenannte Universal Module Definition, die andere Methoden vereinheitlichen soll und im Browser wie auch im Backend funktioniert. Aufgrund der Standardisierung durch das ECMA-Komitee gelten die ECMAScript-Module jedoch als zukunftsweisend. Um dennoch Kompatibilität für Dependencies jeglicher Art in einem Vite-Projekt sicherzustellen, müssen JavaScript-Entwickler sie anpassen und optimieren. Vite führt die Anpassungen allerdings ausschließlich bei einer veränderten package.json- oder Lock-Datei aus, um Zeit zu sparen.

Optimiert werden somit nicht nur alle Abhängigkeiten, die keine ECMAScript-Module exportieren, sondern auch jene, deren Einstiegspunkte import-Anweisungen in tiefere Untermodule oder andere Dependencies aufweisen. Beispielsweise fasst der Optimierungsprozess von Vite alle JavaScript-Dateien von Lodash – eine Utility-Sammlung mit zahlreichen Funktionen, die jeweils in eigenen Untermodulen liegen – für die Entwicklung in eine einzige Datei zusammen. Während des Builds findet korrektes Code-Splitting statt: Nur die tatsächlich genutzten Untermodule von Lodash erscheinen auch im Kompilat.

Der Development-Server von Vite liefert die eigenen Projektdateien bei einer Anfrage nicht genauso aus, wie sie auf der Festplatte liegen. Denn es gilt nicht nur, einige Randfälle zu behandeln, die die ECMAScript-Module noch nicht unterstützen, sondern auch eine Möglichkeit zu schaffen, dass Plug-ins – eingebaut oder aus der Community – die Dateien verändern können. Ein Beispiel für ein solches Plug-in ist der TypeScript-Support: Fordert der Browser eine .ts- oder .tsx-Datei an, wird diese in JavaScript kompiliert und zurückgesendet. Bemerkenswert ist, dass Vite esbuild zum Übersetzen von TypeScript in JavaScript nutzt. Dieses Tool ist als einer der schnellsten JavaScript-Bundler und -Transpiler in der Lage, TypeScript zwanzig- bis dreißigmal so schnell zu kompilieren wie der offizielle Compiler tsc – allerdings zulasten einiger exotischerer Sprachfeatures (s. Abb. 1). Diese werden beim Build teils falsch interpretiert, verursachen teils aber auch Fehler. Esbuild beschreibt allerdings, wie man die tsconfig-Datei konfigurieren muss, um das zu vermeiden. Zusätzlich liefert Vite in seinen TypeScript-Templates solch eine Konfiguration direkt mit aus.

Nach Messungen des esbuild-Autors Evan Wallace ist die Performance dieses JavaScript-Bundlers bis zu 100-mal schneller als die von Alternativen (Abb. 1).

(Bild: GitHub, Evan Wallace)

Des Weiteren werden Dependencies in import-Anweisungen von Browsern nicht nativ unterstützt. Denn dort erscheint hinter dem import kein absoluter oder relativer Dateipfad, sondern lediglich der Name des Pakets. Auch hier greift eine Optimierung, sodass der Pfad für den Browser lesbar wird:

- import { helloWorld } from 'dependency-a'
// Die Version entspricht der installierten Paket-Version
+ import { helloWorld } from '/node_modules/dependency-a/dist/dependency-a.js?v=1.2.1'

Daneben bietet Vite schon jetzt zahlreiche weitere Features wie JSX-Unterstützung (JavaScript XML), die Integration zahlreicher CSS-Präprozessoren wie SASS oder PostCSS und die Möglichkeit von WebAssembly-Modulen. Neben den offiziellen Erweiterungen für Vue oder React hält die stetig wachsende Menge der Plug-ins aus der Community zusätzliche Funktionen bereit.