00:00:00(fröhliche Musik) - Okay, vielen Dank, hallo zusammen.
00:00:07Mein Name ist Luke Sandberg.
00:00:09Ich bin Software-Ingenieur bei Vercel und arbeite an Turbo Pack.
00:00:12Ich bin seit etwa sechs Monaten bei Vercel,
00:00:14was mir gerade genug Zeit gab,
00:00:16hier auf die Bühne zu kommen und Ihnen von all der großartigen Arbeit zu erzählen,
00:00:21die ich nicht geleistet habe.
00:00:23Vor meiner Zeit bei Vercel war ich bei Google,
00:00:26wo ich an unseren internen Web-Toolchains arbeiten und seltsame Dinge tun konnte,
00:00:31wie einen TSX-zu-Java-Bytecode-Compiler zu bauen und am Closure Compiler zu arbeiten.
00:00:37Als ich bei Vercel ankam,
00:00:39war es tatsächlich,
00:00:40als würde ich auf einem anderen Planeten landen – alles war anders.
00:00:45Und ich war ziemlich überrascht von all den Dingen,
00:00:47die wir im Team taten,
00:00:48und den Zielen,
00:00:49die wir hatten.
00:00:50Heute werde ich Ihnen einige der Designentscheidungen vorstellen,
00:00:53die wir bei Turbo Pack getroffen haben,
00:00:55und wie ich glaube,
00:00:56dass sie uns ermöglichen werden,
00:00:58auf der bereits fantastischen Leistung aufzubauen.
00:01:01Um das zu verdeutlichen,
00:01:02hier ist unser übergeordnetes Designziel.
00:01:06Daraus lässt sich sofort ableiten,
00:01:08dass wir wahrscheinlich einige schwierige Entscheidungen getroffen haben.
00:01:14Was ist also mit Kalt-Builds?
00:01:17Die sind wichtig,
00:01:18aber eine unserer Ideen ist,
00:01:20dass Sie sie überhaupt nicht erleben sollten.
00:01:22Und darauf wird sich dieser Vortrag konzentrieren.
00:01:24In der Keynote haben Sie ein wenig darüber gehört,
00:01:27wie wir Inkrementalität nutzen,
00:01:29um die Bundling-Leistung zu verbessern.
00:01:31Eine Schlüsselidee,
00:01:32die wir für Inkrementalität haben,
00:01:34ist das Caching.
00:01:35Wir wollen alles,
00:01:36was der Bundler tut,
00:01:37cachebar machen,
00:01:38sodass wir bei jeder Änderung nur die Arbeit wiederholen müssen,
00:01:41die mit dieser Änderung zusammenhängt.
00:01:43Oder anders ausgedrückt: Die Kosten Ihres Builds sollten wirklich mit der Größe oder Komplexität Ihrer Änderung skalieren und nicht mit der Größe oder Komplexität Ihrer Anwendung.
00:01:53Und so können wir sicherstellen,
00:01:55dass Turbo Pack Entwicklern weiterhin eine gute Leistung bietet,
00:01:58egal wie viele Icon-Bibliotheken Sie importieren.
00:02:01Um diese Idee zu verstehen und zu motivieren,
00:02:04stellen wir uns den einfachsten Bundler der Welt vor,
00:02:07der vielleicht so aussieht.
00:02:09Hier ist also unser Baby-Bundler.
00:02:12Und das ist vielleicht etwas zu viel Code für eine Folie,
00:02:15aber es wird noch schlimmer werden.
00:02:17Hier parsen wir also jeden Entry Point.
00:02:20Wir verfolgen ihre Imports,
00:02:21lösen ihre Referenzen rekursiv durch die gesamte Anwendung auf,
00:02:25um alles zu finden,
00:02:26wovon Sie abhängen.
00:02:28Am Ende sammeln wir dann einfach alles,
00:02:30wovon jeder Entry Point abhängt,
00:02:32und werfen es in eine Ausgabedatei.
00:02:35Hurra, wir haben einen Baby-Bundler.
00:02:38Das ist natürlich naiv,
00:02:40aber wenn wir es aus inkrementeller Sicht betrachten,
00:02:43ist kein Teil davon inkrementell.
00:02:45Wir werden also bestimmte Dateien definitiv mehrmals parsen,
00:02:49vielleicht je nachdem,
00:02:50wie oft Sie sie importieren – das ist furchtbar.
00:02:53Wir werden den React-Import definitiv Hunderte oder Tausende Male auflösen.
00:02:57Also, wissen Sie, autsch.
00:03:01Wenn wir das also zumindest ein wenig inkrementeller gestalten wollen,
00:03:04müssen wir einen Weg finden,
00:03:06redundante Arbeit zu vermeiden.
00:03:08Fügen wir also einen Cache hinzu.
00:03:10Man könnte sich vorstellen,
00:03:12dass dies unsere Parse-Funktion ist.
00:03:15Sie ist ziemlich einfach.
00:03:15Und sie ist wahrscheinlich so etwas wie das Arbeitstier unseres Bundlers.
00:03:19Wissen Sie, sehr einfach.
00:03:19Wir lesen den Dateiinhalt,
00:03:21übergeben ihn an SWC,
00:03:23um einen AST zu erhalten.
00:03:25Fügen wir also einen Cache hinzu.
00:03:27Okay, das ist eindeutig ein schöner, einfacher Gewinn.
00:03:31Aber,
00:03:32wissen Sie,
00:03:32ich bin sicher,
00:03:33einige von Ihnen haben schon einmal Caching-Code geschrieben.
00:03:36Vielleicht gibt es hier einige Probleme.
00:03:38Zum Beispiel, was ist, wenn sich die Datei ändert?
00:03:41Das ist eindeutig etwas, das uns wichtig ist.
00:03:46Und,
00:03:46wissen Sie,
00:03:47was ist,
00:03:47wenn die Datei gar keine echte Datei ist,
00:03:50sondern drei Symlinks in einem Trenchcoat?
00:03:52Viele Paketmanager organisieren Abhängigkeiten so.
00:03:55Und wir verwenden den Dateinamen als Cache-Schlüssel.
00:03:59Ist das genug?
00:04:00Zum Beispiel, wir bündeln für den Client und den Server.
00:04:03Dieselbe Dateien landen in beiden.
00:04:04Funktioniert das?
00:04:05Wir speichern auch den AST und geben ihn zurück.
00:04:08Jetzt müssen wir uns also um Mutationen kümmern.
00:04:11Also,
00:04:11wissen Sie,
00:04:12und schließlich,
00:04:13ist das nicht eine wirklich naive Art zu parsen?
00:04:16Ich weiß,
00:04:16dass jeder massive Konfigurationen für den Compiler hat.
00:04:21Einiges davon muss hier rein.
00:04:23Also, ja, das ist alles großartiges Feedback.
00:04:27Und das ist ein sehr naiver Ansatz.
00:04:32Und dazu würde ich natürlich sagen: Ja,
00:04:34das wird nicht funktionieren.
00:04:36Was tun wir also, um diese Probleme zu beheben?
00:04:39Bitte beheben Sie das und machen Sie keine Fehler.
00:04:44Also, okay.
00:04:46Also, vielleicht ist das ein bisschen besser.
00:04:49Wissen Sie,
00:04:49Sie können hier sehen,
00:04:50dass wir einige Transforms haben.
00:04:52Wir müssen angepasste Dinge für jede Datei tun,
00:04:54wie zum Beispiel Down-Leveling oder die Implementierung von `use cache`.
00:04:58Wir haben auch eine Konfiguration.
00:05:00Und so müssen wir das natürlich in unseren Schlüssel für unseren Cache aufnehmen.
00:05:04Aber vielleicht sind Sie sofort misstrauisch.
00:05:08Zum Beispiel, ist das korrekt?
00:05:09Ist es zum Beispiel tatsächlich ausreichend,
00:05:11einen Transform anhand des Namens zu identifizieren?
00:05:13Ich weiß nicht,
00:05:13vielleicht hat das eine eigene komplizierte Konfiguration.
00:05:16Und,
00:05:17okay,
00:05:17und wird dieser JSON-Wert tatsächlich alles erfassen,
00:05:22was uns wichtig ist?
00:05:24Werden die Entwickler es pflegen?
00:05:26Wie groß werden diese Cache-Schlüssel sein?
00:05:29Wie viele Kopien der Konfiguration werden wir haben?
00:05:31Ich habe persönlich schon genau solchen Code gesehen,
00:05:34und ich finde es fast unmöglich,
00:05:36ihn zu durchschauen.
00:05:37Okay,
00:05:37wir haben auch versucht,
00:05:39dieses andere Problem mit Invalidierungen zu beheben.
00:05:43Also haben wir eine Callback-API zum Lesen von Dateien hinzugefügt.
00:05:46Das ist großartig,
00:05:47denn wenn sich die Datei ändert,
00:05:49können wir sie einfach aus dem Cache löschen,
00:05:51damit wir keine veralteten Inhalte mehr bereitstellen.
00:05:55Okay,
00:05:55aber das ist eigentlich ziemlich naiv,
00:05:57denn klar,
00:05:57wir müssen unseren Cache löschen,
00:05:59aber unser Aufrufer muss auch wissen,
00:06:01dass er eine neue Kopie erhalten muss.
00:06:03Also, okay, fangen wir an, Callbacks durchzureichen.
00:06:06Okay, wir haben es geschafft.
00:06:09Wir haben Callbacks durch den Stack nach oben gereicht.
00:06:12Sie können hier sehen,
00:06:13dass wir unserem Aufrufer erlauben,
00:06:15Änderungen zu abonnieren.
00:06:16Wir können das gesamte Bundle einfach erneut ausführen,
00:06:19wenn sich etwas ändert,
00:06:20und wenn sich eine Datei ändert,
00:06:21rufen wir es auf.
00:06:22Großartig, wir haben einen reaktiven Bundler.
00:06:25Aber das ist immer noch kaum inkrementell.
00:06:28Wenn sich also eine Datei ändert,
00:06:31müssen wir alle Module erneut durchlaufen und alle Ausgabedateien erzeugen.
00:06:37Also,
00:06:38wissen Sie,
00:06:38wir haben eine Menge Arbeit gespart,
00:06:41indem wir unseren Parse-Cache hatten,
00:06:43aber das ist nicht wirklich genug.
00:06:45Und dann schließlich gibt es all diese andere redundante Arbeit.
00:06:49Zum Beispiel wollen wir die Imports definitiv cachen.
00:06:52Wir finden eine Datei vielleicht viele Male,
00:06:53und wir brauchen immer wieder ihre Imports,
00:06:55also wollen wir dort einen Cache einrichten.
00:06:57Und,
00:06:58wissen Sie,
00:06:58Resolve-Ergebnisse sind tatsächlich ziemlich kompliziert,
00:07:01also sollten wir das definitiv cachen,
00:07:02damit wir die Arbeit,
00:07:03die wir beim Auflösen von React geleistet haben,
00:07:06wiederverwenden können.
00:07:08Aber, okay, jetzt haben wir ein weiteres Problem.
00:07:11Ihre Resolve-Ergebnisse ändern sich,
00:07:13wenn Sie Abhängigkeiten aktualisieren oder neue Dateien hinzufügen,
00:07:16also brauchen wir dort einen weiteren Callback.
00:07:18Und wir wollen definitiv auch die Logik zum Erzeugen von Outputs cachen,
00:07:22denn wenn man es in einer HMR-Sitzung betrachtet,
00:07:25bearbeitet man einen Teil der Anwendung,
00:07:28warum schreiben wir also jedes Mal alle Outputs neu?
00:07:31Und außerdem könnten Sie eine Ausgabedatei löschen,
00:07:34also sollten wir wahrscheinlich auch dort auf Änderungen achten.
00:07:39Okay,
00:07:39vielleicht haben wir all diese Dinge gelöst,
00:07:42aber wir haben immer noch dieses Problem,
00:07:44dass wir jedes Mal,
00:07:45wenn sich etwas ändert,
00:07:47von vorne anfangen.
00:07:48Also,
00:07:48der gesamte Kontrollfluss dieser Funktion funktioniert nicht,
00:07:51denn wenn sich eine einzelne Datei ändert,
00:07:53würden wir wirklich gerne mitten in diese For-Schleife springen.
00:07:56Und dann schließlich ist unsere API für unseren Aufrufer auch hoffnungslos naiv.
00:08:03Sie wollen wahrscheinlich tatsächlich wissen,
00:08:04welche Datei sich geändert hat,
00:08:05damit sie Updates an den Client pushen können.
00:08:07Also, ja.
00:08:11Dieser Ansatz funktioniert also nicht wirklich.
00:08:13Und selbst wenn wir irgendwie alle Callbacks an all diesen Stellen durchgereicht hätten,
00:08:18glauben Sie,
00:08:19dass Sie diesen Code tatsächlich pflegen könnten?
00:08:21Glauben Sie, Sie könnten eine neue Funktion hinzufügen?
00:08:24Ich nicht.
00:08:25Ich denke, das würde einfach abstürzen und scheitern.
00:08:28Und, wissen Sie, dazu würde ich sagen: Ja.
00:08:34Also, noch einmal, was sollen wir tun?
00:08:36Wissen Sie,
00:08:37genau wie beim Chatten mit einem LLM müssen Sie zuerst wissen,
00:08:42was Sie wollen.
00:08:43Und dann müssen Sie es extrem klar formulieren.
00:08:48Also, was wollen wir überhaupt?
00:08:50Also,
00:08:50wissen Sie,
00:08:51wir haben viele verschiedene Ansätze in Betracht gezogen,
00:08:54und viele Leute im Team hatten tatsächlich viel Erfahrung mit Bundlern.
00:08:59Also haben wir diese Art von groben Anforderungen entwickelt.
00:09:02Wir wollen also definitiv jede teure Operation im Bundler cachen können.
00:09:05Und das sollte wirklich einfach sein.
00:09:08Zum Beispiel sollten Sie nicht 15 Kommentare zu Ihrem Code-Review erhalten,
00:09:11jedes Mal,
00:09:11wenn Sie einen neuen Cache hinzufügen.
00:09:12Und dann vertraue ich Entwicklern eigentlich nicht wirklich zu,
00:09:17korrekte Cache-Schlüssel zu schreiben oder Inputs oder Abhängigkeiten manuell zu verfolgen.
00:09:24Also sollten wir uns darum kümmern.
00:09:26Wir sollten das definitiv narrensicher machen.
00:09:30Als Nächstes müssen wir mit sich ändernden Inputs umgehen.
00:09:33Das ist eine große Idee in HMR,
00:09:34aber auch über Sitzungen hinweg.
00:09:36Also,
00:09:36meistens werden das Dateien sein,
00:09:38aber es könnten auch Dinge wie Konfigurationseinstellungen sein.
00:09:40Und mit dem Dateisystem-Cache sind es am Ende tatsächlich auch Dinge wie Umgebungsvariablen.
00:09:45Wir wollen also reaktiv sein.
00:09:47Wir wollen Dinge neu berechnen können,
00:09:49sobald sich etwas ändert,
00:09:51und wir wollen nicht überall Callbacks durchreichen.
00:09:54Schließlich müssen wir einfach moderne Architekturen nutzen und Multi-Threaded und generell schnell sein.
00:10:02Also,
00:10:03vielleicht schauen Sie sich diese Anforderungen an,
00:10:07und einige von Ihnen denken: Was hat das mit einem Bundler zu tun?
00:10:12Und dazu würde ich sagen,
00:10:13natürlich ist mein Management-Team im Raum,
00:10:16also müssen wir darüber nicht wirklich sprechen.
00:10:20Aber wirklich,
00:10:21ich schätze,
00:10:21viele von Ihnen sind zu der viel offensichtlicheren Schlussfolgerung gesprungen.
00:10:24Das klingt sehr nach Signals.
00:10:28Und ja, ich beschreibe ein System, das nach Signals klingt.
00:10:31Es ist eine Möglichkeit,
00:10:32Berechnungen zu komponieren,
00:10:33Abhängigkeiten zu verfolgen,
00:10:35mit einem gewissen Grad an automatischer Memoization.
00:10:37Und ich sollte anmerken,
00:10:39dass wir uns von allen möglichen Systemen inspirieren ließen,
00:10:42insbesondere vom Rust-Compiler und einem System namens Salsa.
00:10:45Und es gibt sogar eine akademische Literatur zu diesen Konzepten,
00:10:48die Adaptons genannt werden,
00:10:49falls Sie interessiert sind.
00:10:51Okay,
00:10:51schauen wir uns an,
00:10:53wie das in der Praxis aussieht,
00:10:55und dann machen wir einen sehr abrupten Sprung von Codebeispielen in JavaScript zu Rust.
00:11:01Hier ist also ein Beispiel für die Infrastruktur,
00:11:03die wir gebaut haben.
00:11:05Eine TurboTask-Funktion ist eine gecachte Arbeitseinheit in unserem Compiler.
00:11:12Sobald Sie eine Funktion so annotieren,
00:11:15können wir sie verfolgen,
00:11:17einen Cache-Schlüssel aus ihren Parametern konstruieren,
00:11:20und das ermöglicht es uns,
00:11:22sie sowohl zu cachen als auch bei Bedarf erneut auszuführen.
00:11:28Diese VC-Typen hier können Sie sich wie Signals vorstellen,
00:11:32das ist ein reaktiver Wert,
00:11:34VC steht für Value Cell,
00:11:35aber Signal wäre vielleicht ein etwas besserer Name.
00:11:39Wenn Sie einen Parameter so deklarieren,
00:11:41sagen Sie: Das könnte sich ändern,
00:11:44ich möchte es erneut ausführen,
00:11:46wenn es sich ändert.
00:11:47Und woher wissen wir das?
00:11:49Wir lesen diese Werte also über ein Await.
00:11:52Sobald Sie einen reaktiven Wert wie diesen awaiten,
00:11:55verfolgen wir die Abhängigkeit automatisch.
00:11:58Und dann schließlich führen wir natürlich die eigentliche Berechnung durch,
00:12:03die wir wollten,
00:12:04und speichern sie in einer Zelle.
00:12:07Da wir Abhängigkeiten automatisch verfolgt haben,
00:12:10wissen wir,
00:12:11dass diese Funktion sowohl vom Inhalt der Datei als auch vom Wert der Konfiguration abhängt.
00:12:17Und jedes Mal,
00:12:18wenn wir ein neues Ergebnis in der Zelle speichern,
00:12:20können wir es mit dem vorherigen vergleichen,
00:12:23und wenn es sich geändert hat,
00:12:24können wir Benachrichtigungen an alle weitergeben,
00:12:27die diesen Wert gelesen haben.
00:12:29Dieses Konzept der Änderung ist also entscheidend für unseren Ansatz zur Inkrementalität.
00:12:33Und ja, der einfachste Fall ist wieder hier.
00:12:37Wenn sich die Datei ändert,
00:12:39wird Turbo Pack das bemerken,
00:12:40diese Funktionsausführung invalidieren und sie sofort erneut ausführen.
00:12:45Und wenn wir dann zufällig denselben AST erzeugen,
00:12:48hören wir einfach dort auf,
00:12:50weil wir dieselbe Zelle berechnen.
00:12:53Nun,
00:12:54beim Parsen einer Datei gibt es kaum eine Änderung,
00:12:56die Sie daran vornehmen können,
00:12:58die den AST nicht tatsächlich ändert.
00:13:00Aber wir können die grundlegende Komponierbarkeit von Turbo Pack-Funktionen nutzen,
00:13:05um dies weiter voranzutreiben.
00:13:07Hier sehen wir also eine weitere Turbo Pack Cache-Funktion,
00:13:12die Imports aus einem Modul extrahiert.
00:13:15Man kann sich vorstellen,
00:13:17dass dies eine sehr häufige Aufgabe ist,
00:13:19die wir im Bundler haben.
00:13:20Wir müssen Imports extrahieren,
00:13:22nur um tatsächlich alle Module in Ihrer Anwendung zu finden.
00:13:25Wir nutzen sie,
00:13:25um den besten Weg zu finden,
00:13:27Module zu Chunks zusammenzufassen.
00:13:29Und natürlich ist der Import-Graph wichtig für grundlegende Aufgaben wie Tree Shaking.
00:13:34Und da es so viele verschiedene Konsumenten der Importdaten gibt,
00:13:39macht ein Cache viel Sinn.
00:13:41Diese Implementierung ist also nicht wirklich besonders.
00:13:44Das ist so,
00:13:44wie man es in jeder Art von Bundler finden würde.
00:13:46Wir durchlaufen den AST,
00:13:48sammeln Imports in einer speziellen Datenstruktur,
00:13:51die uns gefällt,
00:13:52und geben sie dann zurück.
00:13:55Aber die Schlüsselidee hier ist,
00:13:56dass wir sie in einer anderen Zelle speichern.
00:13:58Wenn sich das Modul also ändert,
00:14:00müssen wir diese Funktion erneut ausführen,
00:14:03weil wir sie gelesen haben.
00:14:05Aber wenn Sie über die Art von Änderungen nachdenken,
00:14:08die Sie an Modulen vornehmen,
00:14:09betreffen nur sehr wenige davon tatsächlich die Imports.
00:14:12Sie ändern also das Modul,
00:14:14aktualisieren den Funktionskörper,
00:14:16ein String-Literal,
00:14:17jede Art von Implementierungsdetail.
00:14:20Es wird diese Funktion invalidieren und dann werden wir dieselbe Menge an Imports berechnen.
00:14:25Und dann invalidieren wir nichts, was dies gelesen hat.
00:14:29Wenn Sie also darüber in einer HMR-Sitzung nachdenken,
00:14:32bedeutet das,
00:14:32dass wir Ihre Datei neu parsen müssen,
00:14:34aber wir müssen uns wirklich nicht mehr Gedanken darüber machen,
00:14:38wie Chunking-Entscheidungen getroffen werden.
00:14:40Wir müssen uns keine Gedanken über Tree Shaking-Ergebnisse machen,
00:14:43weil wir wissen,
00:14:44dass sich diese nicht geändert haben.
00:14:45Wir können also sofort vom Parsen der Datei,
00:14:48dieser einfachen Analyse,
00:14:50direkt zur Erzeugung von Outputs springen.
00:14:53Und das ist eine der Möglichkeiten,
00:14:55wie wir wirklich schnelle Refresh-Zeiten haben.
00:14:57Das ist also ziemlich imperativ.
00:15:02Eine andere Möglichkeit,
00:15:03diese Grundidee zu betrachten,
00:15:05ist als Graph von Knoten.
00:15:06Hier auf der linken Seite könnte man sich einen Kalt-Build vorstellen.
00:15:12Anfangs müssen wir tatsächlich jede Datei lesen,
00:15:14sie alle parsen,
00:15:15alle Imports analysieren.
00:15:17Und als Nebeneffekt davon haben wir alle Abhängigkeitsinformationen aus Ihrer Anwendung gesammelt.
00:15:21Und wenn sich dann etwas ändert,
00:15:23können wir diesen aufgebauten Abhängigkeitsgraphen nutzen,
00:15:26um Invalidierungen zurück durch den Stack zu propagieren und Turbo Pack-Funktionen erneut auszuführen.
00:15:32Und wenn sie einen neuen Wert erzeugen, hören wir dort auf.
00:15:35Andernfalls propagieren wir die Invalidierung weiter.
00:15:37Also großartig.
00:15:41Wissen Sie,
00:15:41das ist eigentlich eine massive Vereinfachung dessen,
00:15:44was wir in der Praxis tun,
00:15:45wie Sie sich vorstellen können.
00:15:47In Turbo Pack gibt es heute etwa 2.500 verschiedene Turbo-Task-Funktionen.
00:15:53Und in einem typischen Build könnten wir buchstäblich Millionen verschiedener Aufgaben haben.
00:15:58Es sieht also vielleicht ein bisschen mehr so aus.
00:16:01Nun, ich erwarte nicht wirklich, dass Sie das lesen können.
00:16:04Konnte es nicht wirklich auf die Folie passen.
00:16:06Also vielleicht sollten wir herauszoomen.
00:16:08Okay, das ist offensichtlich nicht hilfreich.
00:16:14In Wirklichkeit haben wir bessere Wege,
00:16:17um zu verfolgen und zu visualisieren,
00:16:19was in Turbo Pack passiert.
00:16:21Aber im Grunde funktionieren diese,
00:16:22indem sie die überwiegende Mehrheit der Abhängigkeitsinformationen verwerfen.
00:16:26Und jetzt vermute ich,
00:16:27dass einige von Ihnen vielleicht tatsächlich Erfahrung mit Signals haben,
00:16:32vielleicht schlechte Erfahrungen.
00:16:34Wissen Sie,
00:16:35ich persönlich mag Stack-Traces und die Möglichkeit,
00:16:38in einem Debugger in und aus Funktionen zu springen.
00:16:41Vielleicht sind Sie also misstrauisch,
00:16:43dass dies das komplette Allheilmittel ist.
00:16:45Es kommt offensichtlich mit Kompromissen.
00:16:47Und ja,
00:16:48dazu würde ich natürlich sagen,
00:16:50nun,
00:16:51wissen Sie,
00:16:52was ich eigentlich sagen würde,
00:16:54ist,
00:16:55dass es in der gesamten Softwareentwicklung darum geht,
00:16:59Kompromisse zu managen.
00:17:01Wir lösen Probleme nicht immer exakt,
00:17:04sondern wählen wirklich neue Kompromiss-Sets,
00:17:07um Wert zu liefern.
00:17:08Um unsere Designziele bezüglich inkrementeller Builds in Turbo Pack zu erreichen,
00:17:13haben wir sozusagen alles auf dieses inkrementelle reaktive Programmiermodell gesetzt.
00:17:19Und das hatte natürlich einige sehr natürliche Konsequenzen.
00:17:23Also,
00:17:24wissen Sie,
00:17:25vielleicht haben wir das Problem von handgeschriebenen Caching-Systemen und umständlicher Invalidierungslogik tatsächlich gelöst.
00:17:33Im Gegenzug müssen wir eine komplizierte Caching-Infrastruktur verwalten.
00:17:39Und natürlich,
00:17:39wissen Sie,
00:17:40das klingt für mich nach einem wirklich guten Kompromiss.
00:17:42Ich mag komplizierte Caching-Infrastruktur,
00:17:44aber wir alle müssen mit den Konsequenzen leben.
00:17:48Das erste ist natürlich einfach der Kern-Overhead dieses Systems.
00:17:54Wissen Sie,
00:17:55wenn Sie es in einem bestimmten Build oder einer HMR-Sitzung betrachten,
00:18:01ändern Sie nicht wirklich sehr viel.
00:18:04Wir verfolgen also alle Abhängigkeitsinformationen zwischen jedem Import und jedem Resolve-Ergebnis in Ihrer Anwendung,
00:18:10aber Sie werden tatsächlich nur wenige davon ändern.
00:18:13Die meisten der von uns gesammelten Abhängigkeitsinformationen werden also nie tatsächlich benötigt.
00:18:16Wissen Sie,
00:18:17um dies zu bewältigen,
00:18:18mussten wir uns stark darauf konzentrieren,
00:18:21die Leistung dieser Caching-Schicht zu verbessern,
00:18:24um den Overhead zu reduzieren und unser System auf immer größere Anwendungen zu skalieren.
00:18:30Und das nächste und offensichtlichste ist einfach der Speicher.
00:18:34Wissen Sie,
00:18:35Caches sind immer grundsätzlich ein Kompromiss zwischen Zeit und Speicher.
00:18:38Und unser macht da eigentlich nichts anders.
00:18:41Unser einfaches Ziel ist,
00:18:43dass die Cache-Größe linear mit der Größe Ihrer Anwendung skalieren sollte.
00:18:49Aber auch hier müssen wir vorsichtig mit dem Overhead sein.
00:18:51Dieser nächste Punkt ist etwas subtil.
00:18:54Wir haben also viele Algorithmen im Bundler,
00:18:57wie Sie vielleicht erwarten.
00:18:58Und einige davon erfordern eine Art globales Verständnis Ihrer Anwendung.
00:19:03Nun,
00:19:03das ist ein Problem,
00:19:04denn jedes Mal,
00:19:05wenn Sie von globalen Informationen abhängen,
00:19:07bedeutet das,
00:19:08dass jede Änderung diese Operation invalidieren könnte.
00:19:10Wir müssen also vorsichtig sein,
00:19:12wie wir diese Algorithmen entwerfen,
00:19:14Dinge sorgfältig zusammensetzen,
00:19:15damit wir die Inkrementalität bewahren können.
00:19:17Und schließlich,
00:19:19das hier ist vielleicht ein persönlicher Kritikpunkt.
00:19:24Alles ist asynchron in Turbo Pack.
00:19:27Und das ist großartig für die horizontale Skalierbarkeit,
00:19:29aber wieder einmal schadet es unseren grundlegenden Zielen wie Debugging,
00:19:32Performance-Profiling.
00:19:38Ich bin sicher,
00:19:39viele von Ihnen haben Erfahrung mit dem Debuggen von Async in den Chrome Dev Tools.
00:19:46Und das ist im Allgemeinen eine ziemlich gute Erfahrung.
00:19:48Nicht immer ideal.
00:19:49Und ich versichere Ihnen,
00:19:51Rust mit LLDB ist Lichtjahre zurück.
00:19:53Um das zu bewältigen,
00:19:55mussten wir in benutzerdefinierte Visualisierungs-,
00:19:58Instrumentierungs- und Tracing-Tools investieren.
00:20:01Und sehen Sie mal,
00:20:02wieder ein Infrastrukturprojekt,
00:20:04das kein Bundler ist.
00:20:07Okay,
00:20:07schauen wir mal,
00:20:08ob wir die richtige Wette eingegangen sind.
00:20:11Bei Vercel haben wir also eine sehr große Produktionsanwendung.
00:20:17Wir denken,
00:20:17es ist vielleicht eine der größten der Welt,
00:20:19aber,
00:20:19wissen Sie,
00:20:20wir wissen es nicht wirklich.
00:20:21Aber es hat etwa 80.000 Module.
00:20:23Schauen wir uns also an, wie Turbo Pack damit umgeht.
00:20:26Für Fast Refresh dominieren wir wirklich das,
00:20:30was Web Pack liefern kann.
00:20:32Aber das sind alte Nachrichten.
00:20:33Turbo Pack für die Entwicklung ist schon eine Weile draußen,
00:20:36und ich hoffe wirklich,
00:20:37dass es jeder zumindest in der Entwicklung verwendet.
00:20:39Aber,
00:20:39wissen Sie,
00:20:40das Neue hier heute ist natürlich,
00:20:41dass Builds stabil sind.
00:20:42Schauen wir uns also einen Build an.
00:20:44Und hier sehen Sie einen erheblichen Gewinn gegenüber Web Pack für diese Anwendung.
00:20:49Dieser spezielle Build läuft tatsächlich mit unserer neuen experimentellen Dateisystem-Caching-Schicht.
00:20:53Etwa 16 dieser 94 Sekunden sind nur das Leeren des Caches am Ende.
00:20:59Und daran werden wir arbeiten,
00:21:00um es zu verbessern,
00:21:01wenn das Dateisystem-Caching stabil wird.
00:21:04Aber natürlich ist die Sache mit Kalt-Builds,
00:21:05dass sie kalt sind,
00:21:06nichts ist inkrementell.
00:21:07Schauen wir uns also einen echten Warm-Build an.
00:21:10Mit dem Cache vom Kalt-Build können wir das sehen.
00:21:14Das ist also nur ein kleiner Einblick, wo wir heute stehen.
00:21:17Da wir dieses feingranulare Caching-System haben,
00:21:19können wir den Cache tatsächlich einfach auf die Festplatte schreiben und dann beim nächsten Build wieder einlesen,
00:21:23herausfinden,
00:21:24was sich geändert hat,
00:21:25und den Build abschließen.
00:21:26Okay,
00:21:27das sieht ziemlich gut aus,
00:21:28aber viele von Ihnen denken vielleicht: Nun,
00:21:30ich persönlich habe vielleicht nicht die größte Next.js-Anwendung der Welt.
00:21:34Schauen wir uns also ein kleineres Beispiel an.
00:21:37Die react.dev-Website ist ein gutes Stück kleiner.
00:21:41Es ist auch irgendwie interessant,
00:21:42weil es ein React-Compiler ist.
00:21:44Es ist wenig überraschend ein früher Anwender des React-Compilers.
00:21:47Und der React-Compiler ist in Babel implementiert.
00:21:49Und das ist eine Art Problem für unseren Ansatz,
00:21:51denn es bedeutet,
00:21:52dass wir für jede Datei in der Anwendung Babel bitten müssen,
00:21:54sie zu verarbeiten.
00:21:55Also,
00:21:55und grundsätzlich würde ich sagen,
00:21:57wir,
00:21:58oder ich,
00:21:58ich kann den React-Compiler nicht schneller machen.
00:22:01Das ist nicht meine Aufgabe.
00:22:02Meine Aufgabe ist Turbo Pack.
00:22:03Aber wir können genau herausfinden,
00:22:05wann wir es aufrufen müssen.
00:22:07Wenn man sich also die Fast Refresh-Zeiten ansieht,
00:22:10war ich tatsächlich ein wenig enttäuscht von diesem Ergebnis.
00:22:13Und es stellt sich heraus,
00:22:15dass etwa 130 dieser 140 Millisekunden der React-Compiler sind.
00:22:18Und sowohl Turbo Pack als auch Web Pack tun das.
00:22:22Aber mit Turbo Pack können wir,
00:22:23nachdem der React-Compiler diese Änderung verarbeitet hat,
00:22:26sehen: Oh,
00:22:27Imports haben sich nicht geändert.
00:22:29Werfen Sie es in den Output und machen Sie weiter.
00:22:31Wieder einmal sehen wir bei Kalt-Builds diesen konsistenten 3-fachen Gewinn.
00:22:37Und nur um das klarzustellen, das ist auf meinem Rechner.
00:22:39Aber wieder, keine Inkrementalität in einem Kalt-Build.
00:22:44Und in einem Warm-Build sehen wir diese viel bessere Zeit.
00:22:47Also wieder,
00:22:47bei einem Warm-Build haben wir den Cache bereits auf der Festplatte.
00:22:52Alles,
00:22:52was wir tun müssen,
00:22:53ist im Grunde,
00:22:54sobald wir starten,
00:22:55herauszufinden,
00:22:55welche Dateien in der Anwendung sich ändern,
00:22:57diese Jobs erneut auszuführen und dann alles andere vom vorherigen Build wiederzuverwenden.
00:23:01Die grundlegende Frage ist also: Sind wir schon Turbo?
00:23:05Ja.
00:23:06Also ja, das wurde natürlich in der Keynote besprochen.
00:23:09Turbo Pack ist ab Next 16 stabil.
00:23:12Und wir sind sogar der Standard-Bundler für Next.
00:23:14Also, wissen Sie, Mission erfüllt, gern geschehen.
00:23:17Aber. (lacht) (Publikum applaudiert)
00:23:23Und wenn Sie diese Revert-Sache in der Keynote bemerkt haben,
00:23:26das war ich,
00:23:27als ich versucht habe,
00:23:28Turbo Pack zum Standard zu machen.
00:23:30Es hat nur drei Versuche gebraucht.
00:23:31Aber was ich Ihnen wirklich noch einmal mitgeben möchte,
00:23:35ist das hier.
00:23:35Wissen Sie, denn wir sind noch nicht fertig.
00:23:37Wir haben noch viel in Bezug auf die Leistung zu tun und müssen die Dateisystem-Caching-Schicht fertigstellen.
00:23:42Ich schlage vor,
00:23:43Sie alle probieren es in der Entwicklung aus.
00:23:44Und das war's.
00:23:46Vielen Dank.
00:23:47Bitte sprechen Sie mich an, stellen Sie mir Fragen.
00:23:49(Publikum applaudiert) (fröhliche Musik) (fröhliche Musik)