Transcript

00:00:00(musique entraînante) Bonjour à tous, merci.
00:00:07Je m'appelle Luke Sandberg.
00:00:09Je suis ingénieur logiciel chez Vercel,
00:00:10et je travaille sur Turbo Pack.
00:00:12Je suis chez Vercel depuis environ six mois,
00:00:15ce qui m'a donné juste assez de temps pour monter sur scène et vous parler de l'excellent travail que je n'ai pas fait.
00:00:23Avant Vercel,
00:00:24j'étais chez Google,
00:00:25où j'ai eu l'occasion de travailler sur nos chaînes d'outils web internes et de faire des choses étranges,
00:00:31comme créer un compilateur TSX vers bytecode Java et travailler sur le compilateur Closure.
00:00:37Alors,
00:00:37quand je suis arrivé chez Vercel,
00:00:40c'était un peu comme atterrir sur une autre planète,
00:00:43tout était différent.
00:00:45J'ai été assez surpris par tout ce que nous faisions dans l'équipe et par nos objectifs.
00:00:50Aujourd'hui,
00:00:51je vais partager quelques-uns des choix de conception que nous avons faits pour Turbo Pack et comment,
00:00:56selon moi,
00:00:56ils nous permettront de continuer à bâtir sur les performances fantastiques que nous avons déjà.
00:01:01Pour vous donner une idée,
00:01:02voici notre objectif de conception global.
00:01:06De là,
00:01:06vous pouvez immédiatement en déduire que nous avons probablement fait des choix difficiles.
00:01:14Alors, qu'en est-il des builds à froid ?
00:01:17Ils sont importants,
00:01:18mais l'une de nos idées est que vous ne devriez pas en rencontrer du tout.
00:01:22Et c'est ce sur quoi cette présentation va se concentrer.
00:01:24Lors de la keynote,
00:01:25vous avez entendu parler de la façon dont nous tirons parti de l'incrémentalité pour améliorer les performances de bundling.
00:01:31Une idée clé que nous avons pour l'incrémentalité concerne la mise en cache.
00:01:35Nous voulons que chaque action du bundler soit cachable,
00:01:38de sorte que chaque fois que vous faites une modification,
00:01:40nous n'ayons à refaire que le travail lié à ce changement.
00:01:43Ou peut-être,
00:01:44pour le dire autrement,
00:01:45le coût de votre build devrait vraiment évoluer avec la taille ou la complexité de votre modification plutôt qu'avec la taille ou la complexité de votre application.
00:01:53Et c'est ainsi que nous pouvons nous assurer que Turbo Pack continuera à offrir de bonnes performances aux développeurs,
00:01:58quel que soit le nombre de bibliothèques d'icônes que vous importez.
00:02:01Pour aider à comprendre et à motiver cette idée,
00:02:04imaginons le bundler le plus simple du monde,
00:02:07qui pourrait ressembler à ceci.
00:02:09Voici notre petit bundler.
00:02:12Et c'est peut-être un peu trop de code à mettre sur une diapositive,
00:02:16mais ça va empirer.
00:02:17Ici, nous analysons chaque point d'entrée.
00:02:20Nous suivons leurs importations,
00:02:21résolvons leurs références,
00:02:23de manière récursive dans toute l'application pour trouver tout ce dont vous dépendez.
00:02:28Ensuite,
00:02:28à la fin,
00:02:29nous collectons simplement tout ce dont chaque point d'entrée dépend et le plaçons dans un fichier de sortie.
00:02:35Hourra, nous avons un petit bundler.
00:02:38C'est évidemment naïf,
00:02:39mais si nous l'examinons d'un point de vue incrémental,
00:02:43aucune partie de cela n'est incrémentale.
00:02:45Nous allons certainement analyser certains fichiers plusieurs fois,
00:02:49peut-être selon le nombre d'importations,
00:02:52ce qui est terrible.
00:02:53Nous allons certainement résoudre l'importation de React des centaines,
00:02:56voire des milliers de fois.
00:02:57Alors, aïe.
00:03:01Donc,
00:03:01si nous voulons que cela soit au moins un peu plus incrémental,
00:03:05nous devons trouver un moyen d'éviter le travail redondant.
00:03:08Alors, ajoutons un cache.
00:03:10Vous pouvez imaginer que c'est notre fonction d'analyse.
00:03:15C'est assez simple.
00:03:15Et c'est probablement un peu le cheval de bataille de notre bundler.
00:03:19Très simple.
00:03:19Nous lisons le contenu du fichier,
00:03:22le transmettons à SWC pour obtenir un AST.
00:03:25Alors, ajoutons un cache.
00:03:27D'accord, c'est clairement une belle victoire simple.
00:03:31Mais,
00:03:32vous savez,
00:03:32je suis sûr que certains d'entre vous ont déjà écrit du code de mise en cache.
00:03:36Peut-être qu'il y a des problèmes ici.
00:03:38Par exemple, que se passe-t-il si le fichier change ?
00:03:41C'est clairement quelque chose qui nous tient à cœur.
00:03:46Et,
00:03:46vous savez,
00:03:47que se passe-t-il si le fichier n'est pas vraiment un fichier,
00:03:50mais trois liens symboliques dans un imperméable ?
00:03:52Beaucoup de gestionnaires de paquets organisent les dépendances de cette façon.
00:03:55Et nous utilisons le nom du fichier comme clé de cache.
00:03:59Est-ce suffisant ?
00:04:00Par exemple,
00:04:01nous faisons du bundling pour le client et le serveur.
00:04:03Les mêmes fichiers se retrouvent dans les deux.
00:04:04Cela fonctionne-t-il ?
00:04:05Nous stockons également l'AST et le retournons.
00:04:08Nous devons donc nous soucier des mutations.
00:04:11Alors,
00:04:11vous savez,
00:04:12et enfin,
00:04:13n'est-ce pas une façon vraiment naïve d'analyser ?
00:04:16Je sais que tout le monde a des configurations massives pour le compilateur.
00:04:21Une partie de cela doit être incluse ici.
00:04:23Oui, ce sont tous d'excellents retours.
00:04:27Et c'est une approche très naïve.
00:04:32Et à cela,
00:04:33bien sûr,
00:04:33je dirais,
00:04:34oui,
00:04:34cela ne fonctionnera pas.
00:04:36Alors, que faisons-nous pour résoudre ces problèmes ?
00:04:39Corrigez, et sans faire d'erreurs.
00:04:44Alors, d'accord.
00:04:46Alors, c'est peut-être un peu mieux.
00:04:49Vous savez,
00:04:49vous pouvez voir ici que nous avons des transformations.
00:04:52Nous devons appliquer des traitements personnalisés à chaque fichier,
00:04:55comme la rétrogradation ou l'implémentation de l'utilisation du cache.
00:04:58Nous avons aussi de la configuration.
00:05:00Et donc,
00:05:01bien sûr,
00:05:01nous devons l'inclure dans notre clé pour notre cache.
00:05:04Mais peut-être que tout de suite vous êtes méfiant.
00:05:08Par exemple, est-ce correct ?
00:05:09Par exemple,
00:05:10est-ce vraiment suffisant d'identifier une transformation basée sur le nom ?
00:05:13Je ne sais pas,
00:05:13peut-être que cela a sa propre configuration compliquée.
00:05:16Et,
00:05:17d'accord,
00:05:18cette valeur JSON va-t-elle vraiment capturer tout ce qui nous importe ?
00:05:24Les développeurs vont-ils le maintenir ?
00:05:26Quelle sera la taille de ces clés de cache ?
00:05:29Combien de copies de la configuration aurons-nous ?
00:05:31J'ai personnellement vu du code exactement comme ça,
00:05:34et je trouve qu'il est presque impossible de le comprendre.
00:05:37D'accord,
00:05:37nous avons aussi essayé de résoudre cet autre problème concernant les invalidations.
00:05:43Nous avons donc ajouté une API de rappel pour lire les fichiers.
00:05:46C'est génial,
00:05:47donc si le fichier change,
00:05:48nous pouvons simplement le supprimer du cache,
00:05:51afin de ne pas continuer à servir du contenu obsolète.
00:05:55D'accord,
00:05:55mais c'est en fait assez naïf,
00:05:57car,
00:05:57bien sûr,
00:05:58nous devons vider notre cache,
00:05:59mais notre appelant doit aussi savoir qu'il doit obtenir une nouvelle copie.
00:06:03Alors, d'accord, commençons à propager les rappels.
00:06:06D'accord, nous l'avons fait.
00:06:09Nous avons propagé les rappels à travers la pile.
00:06:12Vous pouvez voir ici que nous permettons à notre appelant de s'abonner aux changements.
00:06:16Nous pouvons simplement relancer l'ensemble du bundle si quelque chose change,
00:06:20et si un fichier change,
00:06:21nous l'appelons.
00:06:22Super, nous avons un bundler réactif.
00:06:25Mais ce n'est toujours guère incrémental.
00:06:28Donc,
00:06:29si un fichier change,
00:06:30nous devons parcourir tous les modules à nouveau et produire tous les fichiers de sortie.
00:06:37Alors,
00:06:38vous savez,
00:06:38nous avons économisé beaucoup de travail grâce à notre cache d'analyse,
00:06:43mais ce n'est pas vraiment suffisant.
00:06:45Et puis finalement, il y a tout ce travail redondant.
00:06:49Par exemple,
00:06:49nous voulons absolument mettre en cache les importations.
00:06:52Nous pourrions trouver un fichier plusieurs fois,
00:06:54et nous avons constamment besoin de ses importations,
00:06:56donc nous voulons y mettre un cache.
00:06:57Et,
00:06:58vous savez,
00:06:58les résultats de résolution sont en fait assez compliqués,
00:07:01nous devrions donc absolument les mettre en cache afin de pouvoir réutiliser le travail que nous avons fait pour résoudre React.
00:07:08Mais, d'accord, maintenant nous avons un autre problème.
00:07:11Vos résultats de résolution changent lorsque vous mettez à jour les dépendances ou ajoutez de nouveaux fichiers,
00:07:16nous avons donc besoin d'un autre rappel là-bas.
00:07:18Et nous voulons aussi absolument,
00:07:19par exemple,
00:07:20mettre en cache la logique de production des sorties,
00:07:23car si vous y pensez lors d'une session HMR,
00:07:25vous modifiez une partie de l'application,
00:07:27alors pourquoi réécrivons-nous toutes les sorties à chaque fois ?
00:07:31Et aussi,
00:07:32vous pourriez,
00:07:32par exemple,
00:07:33supprimer un fichier de sortie,
00:07:35nous devrions donc probablement écouter les changements là aussi.
00:07:39D'accord,
00:07:39nous avons peut-être résolu toutes ces choses,
00:07:42mais nous avons toujours ce problème,
00:07:44c'est-à-dire que chaque fois que quelque chose change,
00:07:46nous repartons de zéro.
00:07:48Donc,
00:07:48en quelque sorte,
00:07:49tout le flux de contrôle de cette fonction ne fonctionne pas parce que si un seul fichier change,
00:07:54nous voudrions vraiment sauter au milieu de cette boucle for.
00:07:56Et puis,
00:07:57enfin,
00:07:58notre API à notre appelant est aussi désespérément naïve.
00:08:03Ils veulent probablement savoir quel fichier a changé,
00:08:05afin de pouvoir,
00:08:05par exemple,
00:08:06pousser les mises à jour vers le client.
00:08:07Alors, oui.
00:08:11Donc, cette approche ne fonctionne pas vraiment.
00:08:13Et même si nous réussissions d'une manière ou d'une autre à propager tous les rappels à tous ces endroits,
00:08:18pensez-vous que vous pourriez réellement maintenir ce code ?
00:08:21Pensez-vous que vous pourriez,
00:08:22par exemple,
00:08:23y ajouter une nouvelle fonctionnalité ?
00:08:24Moi non.
00:08:25Je pense que cela échouerait lamentablement.
00:08:28Et, vous savez, à cela, je dirais, oui.
00:08:34Alors, encore une fois, que devrions-nous faire ?
00:08:36Vous savez,
00:08:37tout comme lorsque vous discutez avec un LLM,
00:08:40vous devez d'abord savoir ce que vous voulez.
00:08:43Et ensuite, vous devez être extrêmement clair à ce sujet.
00:08:48Alors, que voulons-nous vraiment ?
00:08:50Alors,
00:08:50vous savez,
00:08:51nous avons envisagé de nombreuses approches différentes,
00:08:54et de nombreuses personnes de l'équipe avaient en fait beaucoup d'expérience dans le travail sur les bundlers.
00:08:59Nous avons donc élaboré ces sortes d'exigences générales.
00:09:02Nous voulons absolument pouvoir mettre en cache chaque opération coûteuse dans le bundler.
00:09:05Et cela devrait être très facile à faire.
00:09:08Par exemple,
00:09:09vous ne devriez pas recevoir 15 commentaires sur votre revue de code chaque fois que vous ajoutez un nouveau cache.
00:09:12Et puis,
00:09:13je ne fais pas vraiment confiance aux développeurs pour écrire des clés de cache correctes ou suivre les entrées ou les dépendances à la main.
00:09:24Donc, nous devrions nous en occuper.
00:09:26Nous devrions absolument rendre cela infaillible.
00:09:30Ensuite, nous devons gérer les entrées changeantes.
00:09:33C'est une grande idée dans HMR,
00:09:34mais même entre les sessions.
00:09:36Donc,
00:09:36il s'agira principalement de fichiers,
00:09:38mais cela pourrait aussi être des choses comme les paramètres de configuration.
00:09:40Et avec le cache du système de fichiers,
00:09:42cela finit par être aussi des choses comme les variables d'environnement.
00:09:45Donc, nous voulons être réactifs.
00:09:47Nous voulons pouvoir recalculer les choses dès que quelque chose change,
00:09:51et nous ne voulons pas propager des rappels partout.
00:09:54Enfin,
00:09:55nous devons simplement tirer parti des architectures modernes,
00:09:59être multithreadé et généralement rapide.
00:10:02Alors,
00:10:03peut-être que vous regardez cet ensemble d'exigences,
00:10:06et certains d'entre vous se disent,
00:10:09qu'est-ce que cela a à voir avec un bundler ?
00:10:12Et à cela,
00:10:12je dirais,
00:10:13bien sûr,
00:10:14mon équipe de direction est dans la salle,
00:10:16donc nous n'avons pas vraiment besoin d'en parler.
00:10:20Mais en réalité,
00:10:21je suppose que beaucoup d'entre vous ont sauté à la conclusion beaucoup plus évidente.
00:10:24Cela ressemble beaucoup à des signaux.
00:10:28Et oui, je décris un système qui ressemble à des signaux.
00:10:31C'est une façon de composer des calculs,
00:10:33de suivre les dépendances,
00:10:34avec une certaine quantité de mémoïsation automatique.
00:10:37Et je dois noter que nous nous sommes inspirés de toutes sortes de systèmes,
00:10:42en particulier du compilateur Rust et d'un système appelé Salsa.
00:10:45Et il existe même une littérature académique sur ces concepts appelés Adaptons,
00:10:50si cela vous intéresse.
00:10:51D'accord,
00:10:51alors jetons un coup d'œil à ce que,
00:10:53voyons à quoi cela ressemble en pratique,
00:10:55et ensuite nous allons faire un saut très brutal des exemples de code en JavaScript vers Rust.
00:11:01Voici donc un exemple de l'infrastructure que nous avons construite.
00:11:05Une fonction TurboTask est une unité de travail mise en cache dans notre compilateur.
00:11:12Ainsi,
00:11:13une fois que vous annotez une fonction comme celle-ci,
00:11:16nous pouvons la suivre,
00:11:17nous pouvons construire une clé de cache à partir de ses paramètres,
00:11:21et cela nous permet à la fois de la mettre en cache et de la réexécuter lorsque nous en avons besoin.
00:11:28Ces types VC ici,
00:11:29vous pouvez les considérer comme des signaux,
00:11:32c'est une valeur réactive,
00:11:34VC signifie 'value cell',
00:11:35mais 'signal' pourrait être un nom un peu meilleur.
00:11:39Lorsque vous déclarez un paramètre comme celui-ci,
00:11:42vous dites que cela pourrait changer,
00:11:45je veux réexécuter quand cela change.
00:11:47Et comment le savons-nous ?
00:11:49Nous lisons donc ces valeurs via un 'await'.
00:11:52Une fois que vous attendez une valeur réactive comme celle-ci,
00:11:55nous suivons automatiquement la dépendance.
00:11:58Et puis finalement,
00:11:59bien sûr,
00:12:00nous effectuons le calcul que nous voulions faire,
00:12:04et nous le stockons dans une cellule.
00:12:07Ainsi,
00:12:07parce que nous avons automatiquement suivi les dépendances,
00:12:11nous savons que cette fonction dépend à la fois du contenu du fichier et de la valeur de la configuration.
00:12:17Et chaque fois que nous stockons un nouveau résultat dans la cellule,
00:12:21nous pouvons le comparer avec le précédent,
00:12:23et si cela a changé,
00:12:24nous pouvons propager des notifications à tous ceux qui ont lu cette valeur.
00:12:29Ce concept de changement est donc essentiel à notre approche de l'incrémentalité.
00:12:33Et oui, encore une fois, le cas le plus simple est ici.
00:12:37Si le fichier change,
00:12:38Turbo Pack l'observera,
00:12:40invalidera l'exécution de cette fonction et la réexécutera immédiatement.
00:12:45Et si nous produisons le même AST,
00:12:47nous nous arrêterons là parce que nous calculons la même cellule.
00:12:53Maintenant,
00:12:54pour l'analyse d'un fichier,
00:12:55il n'y a pratiquement aucune modification que vous puissiez y apporter qui ne change pas réellement l'AST.
00:13:00Mais nous pouvons tirer parti de la composabilité fondamentale des fonctions Turbo Pack pour aller plus loin.
00:13:07Ici,
00:13:08nous voyons une autre fonction de cache Turbo Pack extrayant les importations d'un module.
00:13:15Vous pouvez imaginer que c'est une tâche très courante que nous avons dans le Bundler.
00:13:20Nous devons extraire les importations juste pour trouver tous les modules de votre application.
00:13:25Nous les utilisons pour choisir la meilleure façon de regrouper les modules en chunks.
00:13:29Et bien sûr,
00:13:29le graphe d'importation est important pour des tâches de base comme le tree shaking.
00:13:34Et comme il y a tellement de consommateurs différents des données d'importation,
00:13:39un cache a beaucoup de sens.
00:13:41Donc, cette implémentation n'est pas vraiment spéciale.
00:13:44C'est ce que vous trouveriez dans n'importe quel type de Bundler.
00:13:46Nous parcourons l'AST,
00:13:47collectons les importations dans une structure de données spéciale que nous aimons,
00:13:53puis nous les retournons.
00:13:55Mais l'idée clé ici est que nous les stockons dans une autre cellule.
00:13:58Donc,
00:13:58si le module change,
00:14:00nous devons relancer cette fonction parce que nous l'avons lue.
00:14:05Mais si vous pensez au type de modifications que vous apportez aux modules,
00:14:09très peu d'entre elles affectent réellement les importations.
00:14:12Donc vous changez le module,
00:14:14vous mettez à jour le corps de la fonction,
00:14:16un littéral de chaîne,
00:14:18tout type de détail d'implémentation.
00:14:20Cela invalidera cette fonction et ensuite nous calculerons le même ensemble d'importations.
00:14:25Et ensuite, nous n'invalidons rien qui a lu cela.
00:14:29Donc,
00:14:29si vous y pensez dans une session HMR,
00:14:31cela signifie que nous devons reparser votre fichier,
00:14:34mais nous n'avons vraiment plus besoin de réfléchir à la façon de prendre des décisions de découpage en chunks.
00:14:40Nous n'avons pas besoin de penser à aucun type de résultats de tree shaking parce que nous savons qu'ils n'ont pas changé.
00:14:45Nous pouvons donc passer immédiatement de l'analyse du fichier,
00:14:49à cette simple analyse,
00:14:50puis directement à la production des sorties.
00:14:53Et c'est l'une des façons dont nous obtenons des temps de rafraîchissement très rapides.
00:14:57C'est donc assez impératif.
00:15:02Une autre façon de penser à cette idée de base est comme un graphe de nœuds.
00:15:06Ici, à gauche, vous pourriez imaginer un build à froid.
00:15:12Initialement,
00:15:12nous devons en fait lire chaque fichier,
00:15:14les analyser tous,
00:15:15analyser toutes les importations.
00:15:17Et comme effet secondaire de cela,
00:15:18nous avons collecté toutes les informations de dépendance de votre application.
00:15:21Et puis quand quelque chose change,
00:15:23nous pouvons tirer parti de ce graphe de dépendances que nous avons construit pour propager les invalidations,
00:15:29remonter la pile et réexécuter les fonctions Turbo Pack.
00:15:32Et donc s'ils produisent une nouvelle valeur,
00:15:34nous nous arrêtons là.
00:15:35Sinon, nous continuons à propager l'invalidation.
00:15:37C'est super.
00:15:41Vous savez,
00:15:41c'est en fait une simplification massive de ce que nous faisons en pratique,
00:15:46vous pouvez l'imaginer.
00:15:47Donc,
00:15:48dans Turbo Pack aujourd'hui,
00:15:49il y a environ 2 500 fonctions Turbo task différentes.
00:15:53Et dans un build typique,
00:15:54nous pourrions avoir littéralement des millions de tâches différentes.
00:15:58Donc, cela ressemble peut-être un peu plus à ceci.
00:16:01Maintenant,
00:16:02je ne m'attends pas vraiment à ce que vous puissiez lire ceci.
00:16:04Je n'ai pas vraiment pu le faire tenir sur la diapositive.
00:16:06Alors, peut-être devrions-nous dézoomer.
00:16:08D'accord, ce n'est évidemment pas utile.
00:16:14En réalité,
00:16:15nous avons de meilleures façons de suivre et de visualiser ce qui se passe à l'intérieur de Turbo Pack.
00:16:21Mais fondamentalement,
00:16:22cela fonctionne en éliminant la grande majorité des informations de dépendance.
00:16:26Et maintenant,
00:16:27je suppose que certains d'entre vous ont peut-être déjà travaillé avec des signaux,
00:16:32peut-être de mauvaises expériences.
00:16:34Vous savez,
00:16:35moi,
00:16:35j'aime les traces de pile et pouvoir entrer et sortir des fonctions dans un débogueur.
00:16:41Alors,
00:16:41peut-être êtes-vous méfiant que ce soit la panacée complète.
00:16:45Comme cela vient évidemment avec des compromis.
00:16:47Et oui,
00:16:48à cela je dirais bien sûr,
00:16:50eh bien,
00:16:51vous savez,
00:16:52ce que je dirais en fait,
00:16:54c'est que toute l'ingénierie logicielle consiste à gérer les compromis.
00:17:01Nous ne résolvons pas toujours les problèmes exactement,
00:17:04mais nous choisissons de nouveaux ensembles de compromis pour apporter de la valeur.
00:17:08Donc,
00:17:09pour atteindre nos objectifs de conception concernant les builds incrémentaux dans Turbo Pack,
00:17:14nous avons misé tous nos jetons sur ce modèle de programmation réactive incrémentale.
00:17:19Et cela a bien sûr eu des conséquences très naturelles.
00:17:23Alors,
00:17:24vous savez,
00:17:25peut-être avons-nous réellement résolu le problème des systèmes de mise en cache faits à la main et de la logique d'invalidation fastidieuse.
00:17:33En échange,
00:17:34nous devons gérer une infrastructure de mise en cache compliquée.
00:17:39Et bien sûr,
00:17:39vous savez,
00:17:40cela me semble être un très bon compromis.
00:17:42J'aime l'infrastructure de mise en cache compliquée,
00:17:45mais nous devons tous vivre avec les conséquences.
00:17:48Le premier est bien sûr juste les surcoûts fondamentaux de ce système.
00:17:54Vous savez,
00:17:55si vous y pensez dans un build donné ou une session HMR,
00:18:00vous ne changez pas vraiment grand-chose.
00:18:04Nous suivons donc toutes les informations de dépendance entre chaque importation et chaque résultat de résolution dans votre application,
00:18:10mais vous n'allez en fait en changer que quelques-unes.
00:18:13La plupart des informations de dépendance que nous collectons ne sont donc jamais réellement nécessaires.
00:18:16Alors,
00:18:17vous savez,
00:18:18pour gérer cela,
00:18:18nous avons dû beaucoup nous concentrer sur l'amélioration des performances de cette couche de mise en cache pour réduire les surcoûts et permettre à notre système de s'adapter à des applications de plus en plus grandes.
00:18:30Et le suivant et le plus évident est simplement la mémoire.
00:18:34Vous savez,
00:18:35les caches sont toujours fondamentalement un compromis temps/mémoire.
00:18:38Et le nôtre ne fait rien de vraiment différent à cet égard.
00:18:41Notre objectif simple est que la taille du cache devrait évoluer linéairement avec la taille de votre application.
00:18:49Mais encore une fois,
00:18:49nous devons faire attention aux surcoûts.
00:18:51Celui-ci est un peu subtil.
00:18:54Nous avons donc beaucoup d'algorithmes dans le bundler,
00:18:57comme vous pouvez vous y attendre.
00:18:58Et certains d'entre eux nécessitent en quelque sorte de comprendre quelque chose de global sur votre application.
00:19:03Eh bien,
00:19:03c'est un problème car chaque fois que vous dépendez d'informations globales,
00:19:07cela signifie que tout changement pourrait invalider cette opération.
00:19:10Nous devons donc être prudents quant à la façon dont nous concevons ces algorithmes,
00:19:14composer les choses avec soin afin de pouvoir préserver l'incrémentalité.
00:19:17Et enfin,
00:19:18celui-ci est peut-être un peu une plainte personnelle.
00:19:24Tout est asynchrone dans Turbo Pack.
00:19:27Et donc c'est excellent pour la scalabilité horizontale,
00:19:29mais encore une fois,
00:19:30cela nuit à nos objectifs fondamentaux,
00:19:31comme,
00:19:31vous savez,
00:19:32le débogage et le profilage des performances.
00:19:38Je suis donc sûr que beaucoup d'entre vous ont de l'expérience dans le débogage asynchrone,
00:19:43par exemple dans les outils de développement de Chrome.
00:19:46Et c'est généralement une expérience assez agréable.
00:19:48Pas toujours idéal.
00:19:49Et je vous assure que Rust avec LLDB est à des années-lumière derrière.
00:19:53Donc,
00:19:54pour gérer cela,
00:19:55nous avons dû investir dans des outils de visualisation,
00:19:58d'instrumentation et de traçage personnalisés.
00:20:01Et regardez ça,
00:20:02encore un projet d'infrastructure qui n'est pas un bundler.
00:20:07D'accord,
00:20:07alors jetons un coup d'œil et voyons si nous avons fait le bon pari.
00:20:11Chez Vercel,
00:20:12nous avons une très grande application de production.
00:20:17Nous pensons que c'est peut-être l'une des plus grandes au monde,
00:20:19mais,
00:20:19vous savez,
00:20:20nous ne le savons pas vraiment.
00:20:21Mais elle contient environ 80 000 modules.
00:20:23Alors, voyons comment Turbo Pack se comporte avec elle.
00:20:26Pour le rafraîchissement rapide,
00:20:28nous dominons vraiment ce que Web Pack est capable de livrer.
00:20:32Mais c'est un peu une vieille nouvelle.
00:20:33Turbo Pack pour le développement est sorti depuis un certain temps,
00:20:36et j'espère vraiment que tout le monde l'utilise au moins en développement.
00:20:39Mais vous savez,
00:20:39la nouveauté ici aujourd'hui,
00:20:40bien sûr,
00:20:41c'est que les builds sont stables.
00:20:42Alors, regardons un build.
00:20:44Et ici,
00:20:45vous pouvez voir une victoire substantielle sur Web Pack pour cette application.
00:20:49Ce build particulier fonctionne en fait avec notre nouvelle couche de cache de système de fichiers expérimentale.
00:20:53Donc,
00:20:54environ 16 de ces 94 secondes consistent juste à vider le cache à la fin.
00:20:59Et c'est quelque chose que nous allons nous efforcer d'améliorer à mesure que la mise en cache du système de fichiers deviendra stable.
00:21:04Mais bien sûr,
00:21:04le problème avec les builds à froid,
00:21:06c'est qu'ils sont froids,
00:21:06rien n'est incrémental.
00:21:07Alors, jetons un coup d'œil à un véritable build à chaud.
00:21:10Donc,
00:21:11en utilisant le cache du build à froid,
00:21:13nous pouvons voir ceci.
00:21:14C'est donc juste un aperçu de là où nous en sommes aujourd'hui.
00:21:17Parce que nous avons ce système de mise en cache à grain fin,
00:21:19nous pouvons en fait simplement écrire le cache sur le disque,
00:21:22puis lors du prochain build,
00:21:23le relire,
00:21:24déterminer ce qui a changé et terminer le build.
00:21:26D'accord,
00:21:27cela semble plutôt bien,
00:21:28mais beaucoup d'entre vous se disent,
00:21:29eh bien,
00:21:30peut-être que je n'ai pas personnellement la plus grande application Next.js au monde.
00:21:34Alors, jetons un coup d'œil à un exemple plus petit.
00:21:37Le site web react.dev est un peu plus petit.
00:21:41C'est aussi intéressant car il utilise un compilateur React.
00:21:44Il est sans surprise un des premiers à adopter le compilateur React.
00:21:47Et le compilateur React est implémenté en Babel.
00:21:49Et c'est un peu un problème pour notre approche car cela signifie que pour chaque fichier de l'application,
00:21:53nous devons demander à Babel de le traiter.
00:21:55Fondamentalement,
00:21:56je dirais que nous,
00:21:57ou plutôt moi,
00:21:58ne pouvons pas rendre le compilateur React plus rapide.
00:22:01Ce n'est pas mon travail.
00:22:02Mon travail, c'est Turbo Pack.
00:22:03Mais nous pouvons déterminer exactement quand l'appeler.
00:22:07Alors,
00:22:08en regardant les temps de rafraîchissement rapide,
00:22:11j'ai été en fait un peu déçu de ce résultat.
00:22:13Et il s'avère qu'environ 130 de ces 140 millisecondes sont dues au compilateur React.
00:22:18Et Turbo Pack et Web Pack font cela.
00:22:22Mais avec Turbo Pack,
00:22:23après que le compilateur React ait traité ce changement,
00:22:26nous pouvons voir,
00:22:27oh,
00:22:27les importations n'ont pas changé.
00:22:29L'envoyer vers la sortie et continuer.
00:22:31Encore une fois,
00:22:32sur les builds à froid,
00:22:34nous voyons ce genre de gain constant de 3x.
00:22:37Et juste pour être clair, c'est sur ma machine.
00:22:39Mais encore une fois,
00:22:41pas d'incrémentalité dans un build à froid.
00:22:44Et dans un build à chaud,
00:22:45nous voyons ce temps bien meilleur.
00:22:47Donc encore une fois,
00:22:48avec un build à chaud,
00:22:49nous avons déjà le cache sur le disque.
00:22:52Tout ce que nous devons faire est,
00:22:53une fois que nous commençons,
00:22:55de déterminer quels fichiers de l'application ont changé,
00:22:57de réexécuter ces tâches,
00:22:58puis de réutiliser tout le reste du build précédent.
00:23:01Alors la question fondamentale est : sommes-nous déjà Turbo ?
00:23:05Oui.
00:23:06Alors oui, cela a été discuté lors de la keynote, bien sûr.
00:23:09Turbo Pack est stable à partir de Next 16.
00:23:12Et nous sommes même le bundler par défaut pour Next.
00:23:14Alors, vous savez, mission accomplie, de rien.
00:23:17Mais. (rires) (applaudissements du public)
00:23:23Et si vous avez remarqué ce retour en arrière lors de la keynote,
00:23:27c'était moi qui essayais de faire de Turbo Pack le bundler par défaut.
00:23:30Cela n'a pris que trois tentatives.
00:23:31Mais ce que je veux vraiment vous laisser,
00:23:34encore une fois,
00:23:35c'est ceci.
00:23:35Vous savez, parce que nous n'avons pas fini.
00:23:37Nous avons encore beaucoup à faire sur les performances,
00:23:39et à achever le développement de la couche de cache du système de fichiers.
00:23:42Je vous suggère à tous de l'essayer en développement.
00:23:44Et c'est tout.
00:23:46Merci beaucoup.
00:23:47N'hésitez pas à venir me voir, à me poser des questions.
00:23:49(applaudissements du public) (musique entraînante) (musique entraînante)

Key Takeaway

Turbo Pack révolutionne le bundling web en adoptant un modèle de programmation réactive incrémentale basé sur des "signaux" et un cache granulaire, permettant des performances de build et de rafraîchissement exceptionnelles en ne recalculant que ce qui est strictement nécessaire.

Highlights

Turbo Pack utilise un modèle de programmation réactive incrémentale pour des performances de bundling optimisées.

Chaque opération coûteuse dans le bundler est mise en cache automatiquement, réduisant le travail redondant.

Le système suit les dépendances et propage les invalidations de manière sélective, permettant des mises à jour ultra-rapides.

Turbo Pack surpasse Webpack en termes de rafraîchissement rapide et de temps de build, même pour de très grandes applications.

Le modèle de conception vise à ce que le coût du build évolue avec la taille de la modification, et non avec la taille de l'application.

Des compromis existent, notamment la complexité de l'infrastructure de cache et les défis de débogage asynchrone, nécessitant des outils personnalisés.

Turbo Pack est désormais stable et le bundler par défaut pour Next.js 16+, avec des améliorations continues prévues.

Timeline

Introduction et Contexte de Turbo Pack

Luke Sandberg, ingénieur logiciel chez Vercel, se présente et explique son rôle dans le développement de Turbo Pack. Il partage son expérience précédente chez Google, où il a travaillé sur des compilateurs internes comme TSX vers bytecode Java et Closure Compiler, soulignant les différences avec l'environnement Vercel. Cette section pose les bases de la présentation en introduisant l'objectif de Turbo Pack : améliorer les performances de bundling grâce à des choix de conception spécifiques. L'orateur promet de détailler ces choix et leur impact sur les performances futures.

Objectif de Conception : Incrémentalité et Mise en Cache

L'objectif principal de Turbo Pack est d'éliminer les "cold builds" en rendant chaque action du bundler entièrement cachable. L'idée est que le coût d'un build doit dépendre de la taille ou de la complexité de la modification, et non de celle de l'application entière. L'orateur illustre ce concept avec un exemple de bundler naïf qui analyse et résout les dépendances de manière récursive, montrant que cette approche est inefficace car elle répète le travail (par exemple, analyser React des centaines de fois). Ce contraste met en évidence la nécessité d'une approche incrémentale pour garantir des performances durables.

Défis des Approches de Cache Naïves

L'ajout d'un simple cache à une fonction d'analyse révèle rapidement de nombreux problèmes, tels que la gestion des changements de fichiers, des liens symboliques, des configurations complexes de compilateur et des mutations d'AST. L'orateur explique que l'inclusion de transformations et de configurations dans la clé de cache devient rapidement ingérable et sujette aux erreurs. La gestion des invalidations via des rappels se propage à travers la pile, conduisant à des re-builds complets et non incrémentaux. Il souligne la difficulté de maintenir un tel code et la nécessité de mettre en cache de nombreuses autres opérations (importations, résolutions, sorties) avec leurs propres mécanismes d'invalidation, ce qui rend le système impraticable.

Exigences pour un Système de Bundling Incrémental Efficace

Face aux limites des approches naïves, l'équipe de Vercel a défini des exigences claires pour Turbo Pack. Ils veulent pouvoir cacher chaque opération coûteuse facilement, sans que les développeurs aient à gérer manuellement les clés de cache ou les dépendances. Le système doit gérer les entrées changeantes (fichiers, configurations, variables d'environnement) de manière réactive, sans propagation de rappels. Enfin, il doit tirer parti des architectures modernes pour être multithreadé et rapide. L'orateur compare ces exigences à celles des "signaux" ou de la programmation réactive, s'inspirant de systèmes comme le compilateur Rust et Salsa.

Le Modèle de Programmation Réactive Incrémentale de Turbo Pack

Cette section détaille l'implémentation de Turbo Pack basée sur les fonctions `TurboTask` et les "Value Cells" (VC). Une `TurboTask` est une unité de travail cachable dont les dépendances sont automatiquement suivies lorsqu'elle "attend" une valeur réactive (VC). Si une VC change, la `TurboTask` est réexécutée, et son nouveau résultat est comparé au précédent pour propager les invalidations uniquement si nécessaire. L'orateur donne l'exemple de l'analyse de fichier et de l'extraction d'importations : si le corps d'une fonction change mais pas ses importations, seules les tâches affectées par le corps sont réexécutées, permettant des rafraîchissements HMR ultra-rapides en évitant le re-chunking ou le tree shaking.

Graphe de Dépendances et Compromis du Système

Le système est conceptualisé comme un graphe de nœuds où un build à froid construit toutes les dépendances, et un build à chaud utilise ce graphe pour propager sélectivement les invalidations. Avec environ 2 500 fonctions `TurboTask` et des millions d'exécutions, le système est complexe. L'orateur aborde les compromis de cette approche : bien qu'elle résolve les problèmes de cache manuel, elle introduit une infrastructure de cache complexe avec des surcoûts fondamentaux et une consommation de mémoire. La dépendance à des informations globales et la nature asynchrone du système (excellente pour la scalabilité mais difficile pour le débogage) nécessitent des outils de visualisation et de traçage personnalisés.

Résultats de Performance et Conclusion

L'orateur présente les performances de Turbo Pack sur des applications réelles. Pour l'application de production de Vercel (80 000 modules), Turbo Pack surpasse largement Webpack en rafraîchissement rapide et réduit considérablement les temps de build à froid (94s contre 300s). Même avec des applications plus petites comme react.dev, qui utilise le compilateur React (implémenté en Babel et ajoutant un surcoût), Turbo Pack maintient un gain constant de 3x sur les builds à froid et des temps de rafraîchissement rapides grâce à son cache incrémental. La présentation conclut que Turbo Pack est stable, le bundler par défaut pour Next.js 16+, et invite les développeurs à l'essayer en développement, tout en soulignant que des améliorations sont encore à venir.

Community Posts

View all posts