Transcript

00:00:00(música animada) - Bien, gracias a todos, hola.
00:00:07Mi nombre es Luke Sandberg.
00:00:09Soy ingeniero de software en Vercel,
00:00:10trabajando en Turbo Pack.
00:00:12Llevo unos seis meses en Vercel,
00:00:14lo que me ha dado tiempo suficiente para subir al escenario y hablarles de todo el gran trabajo que no hice.
00:00:23Antes de Vercel,
00:00:24estuve en Google,
00:00:25donde trabajé en nuestras cadenas de herramientas web internas e hice cosas raras como construir un compilador de TSX a bytecode de Java y trabajar en el compilador Closure.
00:00:37Así que cuando llegué a Vercel,
00:00:40fue como pisar otro planeta,
00:00:43todo era diferente.
00:00:45Y me sorprendieron bastante todas las cosas que hicimos en el equipo y los objetivos que teníamos.
00:00:50Hoy voy a compartir algunas de las decisiones de diseño que tomamos en Turbo Pack y cómo creo que nos permitirán seguir construyendo sobre el fantástico rendimiento que ya tenemos.
00:01:01Para motivar eso,
00:01:02este es nuestro objetivo de diseño general.
00:01:06De esto,
00:01:06pueden inferir inmediatamente que probablemente tomamos algunas decisiones difíciles.
00:01:14Entonces, ¿qué pasa con las compilaciones en frío?
00:01:17Son importantes,
00:01:18pero una de nuestras ideas es que no deberían experimentarlas en absoluto.
00:01:22Y en eso se va a centrar esta charla.
00:01:24En la presentación principal,
00:01:26escucharon un poco sobre cómo aprovechamos la incrementalidad para mejorar el rendimiento del empaquetado.
00:01:31Una idea clave que tenemos para la incrementalidad es el almacenamiento en caché.
00:01:35Queremos que cada cosa que haga el empaquetador sea cacheable,
00:01:38de modo que cada vez que hagan un cambio,
00:01:40solo tengamos que rehacer el trabajo relacionado con ese cambio.
00:01:43O,
00:01:43quizás,
00:01:44dicho de otra manera,
00:01:45el costo de su compilación debería escalar con el tamaño o la complejidad de su cambio,
00:01:50en lugar de con el tamaño o la complejidad de su aplicación.
00:01:53Y así es como podemos asegurarnos de que Turbo Pack seguirá ofreciendo a los desarrolladores un buen rendimiento,
00:01:59sin importar cuántas bibliotecas de iconos importen.
00:02:01Para ayudar a entender y motivar esa idea,
00:02:04imaginemos el empaquetador más simple del mundo,
00:02:07que quizás se vea así.
00:02:09Aquí está nuestro empaquetador
00:02:12Y esto quizás sea demasiado código para una diapositiva,
00:02:15pero va a empeorar.
00:02:17Aquí analizamos cada punto de entrada.
00:02:20Seguimos sus importaciones,
00:02:21resolvemos sus referencias,
00:02:23de forma recursiva en toda la aplicación para encontrar todo de lo que dependen.
00:02:28Luego,
00:02:28al final,
00:02:29simplemente recopilamos todo lo que depende de cada punto de entrada y lo colocamos en un archivo de salida.
00:02:35¡Así que, hurra, tenemos un empaquetador
00:02:38Obviamente esto es ingenuo,
00:02:40pero si lo pensamos desde una perspectiva incremental,
00:02:43ninguna parte de esto es incremental.
00:02:45Así que definitivamente analizaremos ciertos archivos varias veces,
00:02:49quizás dependiendo de cuántas veces los importen,
00:02:52eso es terrible.
00:02:53Definitivamente resolveremos la importación de React cientos o miles de veces.
00:02:57Así que, saben, ¡ay!
00:03:01Así que si queremos que esto sea al menos un poco más incremental,
00:03:04necesitamos encontrar una forma de evitar el trabajo redundante.
00:03:08Así que, añadamos una caché.
00:03:10Podrían imaginar que esta es nuestra función de análisis.
00:03:15Es bastante simple.
00:03:15Y probablemente sea el
00:03:19Saben, muy simple.
00:03:19Leemos el contenido del archivo,
00:03:22se lo pasamos a SWC para que nos dé un AST.
00:03:25Así que, añadamos una caché.
00:03:27Bien, esto es claramente una buena y simple victoria.
00:03:31Pero,
00:03:32saben,
00:03:32estoy seguro de que algunos de ustedes han escrito código de caché antes.
00:03:36Quizás haya algunos problemas aquí.
00:03:38Como, saben, ¿qué pasa si el archivo cambia?
00:03:41Esto es claramente algo que nos importa.
00:03:46Y,
00:03:46saben,
00:03:47¿qué pasa si el archivo no es realmente un archivo,
00:03:50sino tres enlaces simbólicos disfrazados?
00:03:52Muchos gestores de paquetes organizan las dependencias así.
00:03:55Y estamos usando el nombre del archivo como clave de caché.
00:03:59¿Es suficiente?
00:04:00Como,
00:04:00saben,
00:04:01estamos empaquetando para el cliente y el servidor.
00:04:03Los mismos archivos terminan en ambos.
00:04:04¿Funciona eso?
00:04:05También estamos almacenando el AST y devolviéndolo.
00:04:08Así que ahora tenemos que preocuparnos por las mutaciones.
00:04:11Entonces,
00:04:11saben,
00:04:12y finalmente,
00:04:13¿no es esta una forma realmente ingenua de analizar?
00:04:16Sé que todos tienen configuraciones masivas para el compilador.
00:04:21Como, algo de eso tiene que entrar aquí.
00:04:23Así que, sí, todo esto es una gran retroalimentación.
00:04:27Y este es un enfoque muy ingenuo.
00:04:32Y a eso, por supuesto, diría, sí, esto no funcionará.
00:04:36Entonces, ¿qué hacemos para solucionar estos problemas?
00:04:39Por favor, arréglenlo y no cometan errores.
00:04:44Así que, bien.
00:04:46Así que quizás esto sea un poco mejor.
00:04:49Saben, pueden ver aquí que tenemos algunas transformaciones.
00:04:52Necesitamos hacer cosas personalizadas para cada archivo,
00:04:55como quizás bajar de nivel o implementar el uso de caché.
00:04:58También tenemos alguna configuración.
00:05:00Y así,
00:05:01por supuesto,
00:05:01necesitamos incluir eso en nuestra clave para nuestra caché.
00:05:04Pero quizás de inmediato sospechen.
00:05:08Como, ¿es esto correcto?
00:05:09Como,
00:05:10¿es realmente suficiente identificar una transformación basándose en el nombre?
00:05:13No sé, quizás eso tenga su propia configuración complicada.
00:05:16Y,
00:05:17bien,
00:05:17y como,
00:05:18¿este valor JSON va a capturar realmente todo lo que nos importa?
00:05:24¿Lo mantendrán los desarrolladores?
00:05:26¿Qué tan grandes serán estas claves de caché?
00:05:29¿Cuántas copias de la configuración tendremos?
00:05:31Así que,
00:05:32personalmente he visto código exactamente así,
00:05:34y me resulta casi imposible de entender.
00:05:37Bien,
00:05:37también intentamos solucionar este otro problema relacionado con las invalidaciones.
00:05:43Así que añadimos una API de callback para leer archivos.
00:05:46Esto es genial,
00:05:47así que si el archivo cambia,
00:05:49podemos simplemente eliminarlo de la caché,
00:05:52para no seguir sirviendo contenido obsoleto.
00:05:55Bien,
00:05:55pero esto es bastante ingenuo,
00:05:56porque,
00:05:57claro,
00:05:57necesitamos eliminar nuestra caché,
00:05:59pero nuestro llamador también necesita saber que necesita obtener una nueva copia.
00:06:03Así que, bien, empecemos a encadenar callbacks.
00:06:06Bien, lo hicimos.
00:06:09Encadenamos callbacks a través de la pila.
00:06:12Pueden ver aquí que permitimos a nuestro llamador suscribirse a los cambios.
00:06:16Podemos simplemente volver a ejecutar todo el paquete si algo cambia,
00:06:20y si un archivo cambia,
00:06:21lo llamamos.
00:06:22Genial, tenemos un empaquetador reactivo.
00:06:25Pero esto sigue siendo apenas incremental.
00:06:28Así que si un archivo cambia,
00:06:30necesitamos recorrer todos los módulos de nuevo y producir todos los archivos de salida.
00:06:37Así que,
00:06:38saben,
00:06:38ahorramos mucho trabajo al tener nuestra caché de análisis,
00:06:43pero esto no es realmente suficiente.
00:06:45Y luego, finalmente, hay todo este otro trabajo redundante.
00:06:49Como, definitivamente queremos cachear las importaciones.
00:06:52Podríamos encontrar un archivo muchas veces,
00:06:54y seguimos necesitando sus importaciones,
00:06:55así que queremos poner una caché allí.
00:06:57Y,
00:06:58saben,
00:06:58los resultados de resolución son bastante complicados,
00:07:01así que definitivamente deberíamos cachear eso para poder reutilizar el trabajo que hicimos al resolver React.
00:07:08Pero, bien, ahora tenemos otro problema.
00:07:11Sus resultados de resolución cambian cuando actualizan dependencias o añaden nuevos archivos,
00:07:16así que necesitamos otro callback allí.
00:07:18Y definitivamente también queremos,
00:07:20como,
00:07:20cachear la lógica para producir salidas porque,
00:07:23si lo piensan en una sesión HMR,
00:07:25están editando una parte de la aplicación,
00:07:27entonces ¿por qué estamos reescribiendo todas las salidas cada vez?
00:07:31Y también,
00:07:32podrían,
00:07:32como,
00:07:33eliminar un archivo de salida,
00:07:35así que probablemente deberíamos escuchar los cambios allí también.
00:07:39Bien,
00:07:39quizás resolvimos todas esas cosas,
00:07:42pero todavía tenemos este problema,
00:07:44que es que cada vez que algo cambia,
00:07:46empezamos desde cero.
00:07:48Así que,
00:07:48el flujo de control completo de esta función no funciona porque si un solo archivo cambia,
00:07:53realmente querríamos saltar al medio de ese bucle for.
00:07:56Y luego,
00:07:57finalmente,
00:07:58nuestra API para nuestro llamador también es irremediablemente ingenua.
00:08:03Probablemente quieran saber qué archivo ha cambiado,
00:08:05para poder,
00:08:05como,
00:08:06enviar actualizaciones al cliente.
00:08:07Así que, sí.
00:08:11Así que, este enfoque no funciona realmente.
00:08:13E incluso si de alguna manera encadenáramos todos los callbacks en todos estos lugares,
00:08:19¿creen que podrían mantener este código?
00:08:21¿Creen que podrían, como, añadirle una nueva característica?
00:08:24Yo no.
00:08:25Creo que esto simplemente fracasaría estrepitosamente.
00:08:28Y, saben, a eso, diría, sí.
00:08:34Así que, una vez más, ¿qué deberíamos hacer?
00:08:36Saben,
00:08:36al igual que cuando chatean con un LLM,
00:08:40primero necesitan saber lo que quieren.
00:08:43Y luego tienen que ser extremadamente claros al respecto.
00:08:48Entonces, ¿qué queremos siquiera?
00:08:50Así que,
00:08:50saben,
00:08:51consideramos muchos enfoques diferentes,
00:08:53y muchas personas del equipo tenían mucha experiencia trabajando en empaquetadores.
00:08:59Así que, se nos ocurrieron estos requisitos más o menos.
00:09:02Así que,
00:09:02definitivamente queremos poder cachear cada operación costosa en el empaquetador.
00:09:05Y debería ser realmente fácil hacer esto.
00:09:08Como,
00:09:09no deberían recibir 15 comentarios en su revisión de código cada vez que añaden una nueva caché.
00:09:12Y luego,
00:09:13en realidad no confío en que los desarrolladores escriban claves de caché correctas o rastreen entradas o dependencias manualmente.
00:09:24Así que, deberíamos manejarlo nosotros.
00:09:26Definitivamente deberíamos hacer esto a prueba de errores.
00:09:30A continuación, necesitamos manejar las entradas cambiantes.
00:09:33Esta es una gran idea en HMR, pero incluso entre sesiones.
00:09:36Así que,
00:09:36principalmente serán archivos,
00:09:38pero también podrían ser cosas como configuraciones.
00:09:40Y con la caché del sistema de archivos,
00:09:42en realidad termina siendo cosas como variables de entorno también.
00:09:45Así que, queremos ser reactivos.
00:09:47Queremos poder recalcular las cosas tan pronto como algo cambie,
00:09:51y no queremos encadenar callbacks por todas partes.
00:09:54Finalmente,
00:09:55solo necesitamos aprovechar las arquitecturas modernas y ser multihilo y,
00:10:01en general,
00:10:02rápidos.
00:10:02Así que,
00:10:03quizás estén viendo este conjunto de requisitos,
00:10:06y algunos de ustedes estén pensando,
00:10:09¿qué tiene esto que ver con un empaquetador?
00:10:12Y a eso,
00:10:12diría,
00:10:13por supuesto,
00:10:14mi equipo de gestión está en la sala,
00:10:17así que no necesitamos hablar de eso.
00:10:20Pero en realidad,
00:10:21supongo que muchos de ustedes llegaron a la conclusión mucho más obvia.
00:10:24Esto suena mucho a señales.
00:10:28Y sí, estoy describiendo un sistema que suena a señales.
00:10:31Es una forma de componer cálculos,
00:10:33rastrear dependencias,
00:10:34con cierta cantidad de memoización automática.
00:10:37Y debo señalar que nos inspiramos en todo tipo de sistemas,
00:10:41especialmente en el compilador de Rust y en un sistema llamado Salsa.
00:10:45E incluso hay literatura académica sobre estos conceptos llamada Adaptons,
00:10:50si les interesa.
00:10:51Bien,
00:10:51echemos un vistazo a lo que,
00:10:53veamos cómo se ve esto en la práctica,
00:10:55y luego vamos a dar un salto muy brusco de ejemplos de código en JavaScript a Rust.
00:11:01Aquí tienen un ejemplo de la infraestructura que construimos.
00:11:05Una función TurboTask es una unidad de trabajo cacheada en nuestro compilador.
00:11:12Así que podemos,
00:11:13una vez que anotan una función así,
00:11:16podemos rastrearla,
00:11:17podemos construir una clave de caché a partir de sus parámetros,
00:11:21y eso nos permite tanto cachearla como volver a ejecutarla cuando lo necesitamos.
00:11:28Estos tipos VC aquí,
00:11:30pueden pensarlos como señales,
00:11:34este es un valor reactivo,
00:11:37VC significa
00:11:39Cuando declaran un parámetro así,
00:11:41están diciendo que esto podría cambiar,
00:11:44quiero volver a ejecutarlo cuando cambie.
00:11:47¿Y cómo sabemos eso?
00:11:49Así que leemos estos valores a través de un
00:11:52Una vez que esperan un valor reactivo como este,
00:11:55rastreamos automáticamente la dependencia.
00:11:58Y luego,
00:11:58finalmente,
00:11:59por supuesto,
00:12:00hacemos el cálculo real que queríamos hacer,
00:12:04y lo almacenamos en una celda.
00:12:07Así que,
00:12:07como hemos rastreado automáticamente las dependencias,
00:12:11sabemos que esta función depende tanto del contenido del archivo como del valor de la configuración.
00:12:17Y cada vez que almacenamos un nuevo resultado en la celda,
00:12:21podemos compararlo con el anterior,
00:12:23y luego,
00:12:23si ha cambiado,
00:12:24podemos propagar notificaciones a todos los que han leído ese valor.
00:12:29Así que este concepto de cambio es clave para nuestro enfoque de incrementalidad.
00:12:33Y sí, de nuevo, el caso más simple está aquí.
00:12:37Si el archivo cambia,
00:12:38Turbo Pack lo observará,
00:12:40invalidará la ejecución de esta función y la volverá a ejecutar inmediatamente.
00:12:45Y luego,
00:12:45si por casualidad producimos el mismo AST,
00:12:48nos detendremos allí mismo porque calculamos la misma celda.
00:12:53Ahora,
00:12:54para analizar un archivo,
00:12:55apenas hay ninguna edición que puedan hacer que no cambie realmente el AST.
00:13:00Pero podemos aprovechar la composabilidad fundamental de las funciones de Turbo Pack para llevar esto más lejos.
00:13:07Así que aquí,
00:13:09vemos otra función de caché de Turbo Pack extrayendo importaciones de un módulo.
00:13:15Pueden imaginar que esta es una tarea muy común que tenemos en el Empaquetador.
00:13:20Necesitamos extraer importaciones solo para encontrar realmente todos los módulos en su aplicación.
00:13:25Los aprovechamos para elegir la mejor manera de agrupar módulos en fragmentos.
00:13:29Y,
00:13:29por supuesto,
00:13:30el gráfico de importación es importante para tareas básicas como el
00:13:34Y como hay tantos consumidores diferentes de los datos de importaciones,
00:13:39una caché tiene mucho sentido.
00:13:41Así que esta implementación no es realmente especial.
00:13:44Esto es como lo que encontrarían en cualquier tipo de Empaquetador.
00:13:46Recorremos el AST,
00:13:47recopilamos importaciones en alguna estructura de datos especial que nos gusta,
00:13:53y luego las devolvemos.
00:13:55Pero la idea clave aquí es que los almacenamos en otra celda.
00:13:58Así que si el módulo cambia,
00:14:00sí necesitamos volver a ejecutar esta función porque lo leemos.
00:14:05Pero si piensan en el tipo de cambios que hacen a los módulos,
00:14:09muy pocos de ellos afectan realmente las importaciones.
00:14:12Así que cambian el módulo,
00:14:13actualizan el cuerpo de la función,
00:14:16un literal de cadena,
00:14:17cualquier tipo de detalle de implementación.
00:14:20Invalidará esta función y luego calcularemos el mismo conjunto de importaciones.
00:14:25Y luego no invalidamos nada que haya leído esto.
00:14:29Así que si piensan en esto en una sesión HMR,
00:14:31esto significa que sí necesitamos volver a analizar su archivo,
00:14:35pero realmente ya no necesitamos pensar en cómo tomar decisiones de fragmentación.
00:14:40No necesitamos pensar en ningún tipo de resultado de
00:14:45Así que podemos saltar inmediatamente de analizar el archivo,
00:14:48hacer este análisis simple,
00:14:50y luego saltar directamente a producir salidas.
00:14:53Y esta es una de las formas en que tenemos tiempos de actualización realmente rápidos.
00:14:57Así que esto es bastante imperativo.
00:15:02Otra forma de pensar en esta idea básica es como un gráfico de nodos.
00:15:06Así que aquí a la izquierda,
00:15:08podrían imaginar una compilación en frío.
00:15:12Inicialmente,
00:15:12sí tenemos que leer cada archivo,
00:15:14analizarlos todos,
00:15:15analizar todas las importaciones.
00:15:17Y como efecto secundario de eso,
00:15:18hemos recopilado toda la información de dependencia de su aplicación.
00:15:21Y luego,
00:15:22cuando algo cambia,
00:15:23podemos aprovechar ese gráfico de dependencias que construimos para propagar invalidaciones,
00:15:28volver a la pila y volver a ejecutar las funciones de Turbo Pack.
00:15:32Y así, si producen un nuevo valor, nos detenemos allí.
00:15:35De lo contrario, seguimos propagando la invalidación.
00:15:37Así que, genial.
00:15:41Saben,
00:15:41esto es en realidad una especie de sobresimplificación masiva de lo que estamos haciendo en la práctica,
00:15:46podrían imaginar.
00:15:47Así que en Turbo Pack hoy,
00:15:49hay alrededor de 2.500 funciones Turbo task diferentes.
00:15:53Y en una compilación típica,
00:15:54podríamos tener literalmente millones de tareas diferentes.
00:15:58Así que realmente se ve quizás un poco más así.
00:16:01Ahora, realmente no espero que puedan leer esto.
00:16:04No pude realmente meterlo en la diapositiva.
00:16:06Así que quizás deberíamos alejar el zoom.
00:16:08Bien, eso obviamente no es útil.
00:16:14En realidad,
00:16:15sí tenemos mejores formas de,
00:16:16digamos,
00:16:17rastrear y visualizar lo que está sucediendo dentro de Turbo Pack.
00:16:21Pero fundamentalmente,
00:16:22eso funciona descartando la gran mayoría de la información de dependencia.
00:16:26Y ahora supongo que algunos de ustedes quizás realmente tienen experiencia trabajando con señales,
00:16:32quizás malas experiencias.
00:16:34Saben,
00:16:35a mí,
00:16:35por ejemplo,
00:16:36me gustan los rastreos de pila y poder entrar y salir de funciones en un depurador.
00:16:41Así que quizás sospechen que esta es la panacea completa.
00:16:45Como que obviamente viene con compensaciones.
00:16:47Y sí,
00:16:48y a eso,
00:16:49por supuesto,
00:16:50diría,
00:16:50bueno,
00:16:51saben,
00:16:52lo que realmente diría es que toda la ingeniería de software se trata de gestionar compensaciones.
00:17:01No siempre estamos resolviendo problemas exactamente,
00:17:04pero realmente estamos eligiendo nuevos conjuntos de compensaciones para entregar valor.
00:17:08Así que para lograr nuestros objetivos de diseño en torno a las compilaciones incrementales en Turbo Pack,
00:17:14apostamos,
00:17:14por así decirlo,
00:17:15todas nuestras fichas en este modelo de programación reactiva incremental.
00:17:19Y esto,
00:17:19por supuesto,
00:17:20tuvo algunas consecuencias muy naturales.
00:17:23Así que,
00:17:24saben,
00:17:24quizás realmente sí resolvimos el problema de los sistemas de caché hechos a mano y la lógica de invalidación engorrosa.
00:17:33A cambio,
00:17:34tenemos que gestionar una infraestructura de caché complicada.
00:17:39Y por supuesto,
00:17:40saben,
00:17:40eso me parece una muy buena compensación.
00:17:42Me gusta la infraestructura de caché complicada,
00:17:45pero todos tenemos que vivir con las consecuencias.
00:17:48Así que lo primero,
00:17:49por supuesto,
00:17:50son solo los gastos generales centrales de este sistema.
00:17:54Saben,
00:17:55si lo piensan en una compilación o sesión HMR dada,
00:18:01no están cambiando mucho.
00:18:04Así que rastreamos toda la información de dependencia entre,
00:18:07digamos,
00:18:07cada importación y cada resultado de resolución en su aplicación,
00:18:11pero en realidad solo van a cambiar unos pocos.
00:18:13Así que la mayor parte de la información de dependencia que recopilamos nunca es realmente necesaria.
00:18:16Así que,
00:18:17saben,
00:18:17para gestionar esto,
00:18:19hemos tenido que centrarnos mucho en impulsar,
00:18:21en mejorar el rendimiento de esta capa de caché para reducir los gastos generales y permitir que nuestro sistema escale a aplicaciones cada vez más grandes.
00:18:30Y lo siguiente y más obvio es simplemente la memoria.
00:18:34Saben,
00:18:35las cachés son siempre fundamentalmente una compensación entre tiempo y memoria.
00:18:38Y la nuestra no hace nada realmente diferente allí.
00:18:41Nuestro objetivo simple es que el tamaño de la caché debería escalar linealmente con el tamaño de su aplicación.
00:18:49Pero de nuevo,
00:18:49tenemos que tener cuidado con los gastos generales.
00:18:51Este siguiente es un poco sutil.
00:18:54Así que tenemos muchos algoritmos en el empaquetador,
00:18:57como podrían esperar.
00:18:58Y algunos de ellos,
00:18:59digamos,
00:19:00requieren entender algo global sobre su aplicación.
00:19:03Bueno,
00:19:03eso es un problema porque cada vez que dependen de información global,
00:19:07eso significa que cualquier cambio podría invalidar esa operación.
00:19:10Así que tenemos que tener cuidado con cómo diseñamos estos algoritmos,
00:19:14componer las cosas con cuidado para que podamos preservar la incrementalidad.
00:19:17Y finalmente, este es quizás un poco de una queja personal.
00:19:24Todo es asíncrono en Turbo Pack.
00:19:27Y esto es genial para la escalabilidad horizontal,
00:19:29pero una vez más,
00:19:30daña nuestros objetivos fundamentales de,
00:19:31saben,
00:19:32depuración y perfilado de rendimiento.
00:19:38Así que estoy seguro de que muchos de ustedes tienen experiencia depurando asincronía en,
00:19:43digamos,
00:19:43las herramientas de desarrollo de Chrome.
00:19:46Y esta es generalmente una experiencia bastante agradable.
00:19:48No siempre ideal.
00:19:49Y les aseguro que Rust con LLDB está a años luz de distancia.
00:19:53Así que para gestionar eso,
00:19:55hemos tenido que invertir en herramientas personalizadas de visualización,
00:19:59instrumentación y trazado.
00:20:01Y miren eso,
00:20:02como otro proyecto de infraestructura que no es un empaquetador.
00:20:07Bien,
00:20:07echemos un vistazo y veamos si hicimos la apuesta correcta.
00:20:11Así que en Vercel,
00:20:12tenemos una aplicación de producción muy grande.
00:20:17Creemos que quizás es una de las más grandes del mundo,
00:20:19pero,
00:20:20saben,
00:20:20realmente no lo sabemos.
00:20:21Pero sí tiene alrededor de 80.000 módulos.
00:20:23Así que echemos un vistazo a cómo le va a Turbo Pack con ella.
00:20:26Para la actualización rápida,
00:20:28realmente dominamos lo que Web Pack es capaz de ofrecer.
00:20:32Pero esto es, digamos, una noticia vieja.
00:20:33Turbo Pack para desarrollo ha estado disponible por un tiempo,
00:20:36y realmente espero que todos lo estén usando al menos en desarrollo.
00:20:39Pero,
00:20:39saben,
00:20:40lo nuevo aquí hoy,
00:20:40por supuesto,
00:20:41es que las compilaciones son estables.
00:20:42Así que veamos una compilación.
00:20:44Y aquí pueden ver una victoria sustancial sobre Web Pack para esta aplicación.
00:20:49Esta compilación en particular en realidad se está ejecutando con nuestra nueva capa experimental de caché del sistema de archivos.
00:20:53Así que unos 16 de esos 94 segundos son solo vaciar la caché al final.
00:20:59Y esto es algo en lo que vamos a trabajar para mejorar a medida que el almacenamiento en caché del sistema de archivos se vuelva estable.
00:21:04Pero,
00:21:04por supuesto,
00:21:05lo de las compilaciones en frío es que son frías,
00:21:07nada es incremental.
00:21:07Así que echemos un vistazo a una compilación
00:21:10Así que usando la caché de la compilación en frío,
00:21:13podemos ver esto.
00:21:14Así que esto es solo un vistazo a dónde estamos hoy.
00:21:17Como tenemos este sistema de caché de grano fino,
00:21:19en realidad podemos simplemente escribir la caché en el disco,
00:21:22y luego en la siguiente compilación,
00:21:23leerla de nuevo,
00:21:24averiguar qué cambió y terminar la compilación.
00:21:26Bien,
00:21:27esto se ve bastante bien,
00:21:28pero muchos de ustedes están pensando,
00:21:30bueno,
00:21:30quizás yo personalmente no tengo la aplicación Next.js más grande del mundo.
00:21:34Así que echemos un vistazo a un ejemplo más pequeño.
00:21:37El sitio web react.dev es bastante más pequeño.
00:21:41También es,
00:21:41digamos,
00:21:42interesante porque es un compilador de React.
00:21:44No es sorprendente que sea un adoptador temprano del compilador de React.
00:21:47Y el compilador de React está implementado en Babel.
00:21:49Y esto es,
00:21:50digamos,
00:21:50un problema para nuestro enfoque porque significa que para cada archivo en la aplicación,
00:21:54necesitamos pedirle a Babel que lo procese.
00:21:55Así que,
00:21:55y fundamentalmente,
00:21:56diría que nosotros,
00:21:57o yo,
00:21:58no puedo hacer que el compilador de React sea más rápido.
00:22:01No es mi trabajo.
00:22:02Mi trabajo es Turbo Pack.
00:22:03Pero podemos averiguar exactamente cuándo llamarlo.
00:22:07Así que,
00:22:08mirando los tiempos de actualización rápida,
00:22:10en realidad me decepcionó un poco este resultado.
00:22:13Y resulta que unos 130 de esos 140 milisegundos son del compilador de React.
00:22:18Y tanto Turbo Pack como Web Pack están haciendo eso.
00:22:22Pero con Turbo Pack,
00:22:23podemos,
00:22:23después de que el compilador de React haya procesado este cambio,
00:22:27ver,
00:22:27oh,
00:22:28las importaciones no cambiaron.
00:22:29Lo metemos en la salida y seguimos.
00:22:31Una vez más,
00:22:32en las compilaciones en frío,
00:22:34vemos este tipo de victoria consistente de 3x.
00:22:37Y solo para ser claros, esto es en mi máquina.
00:22:39Pero de nuevo,
00:22:40no hay incrementalidad en una compilación en frío.
00:22:44Y en una compilación
00:22:47Así que de nuevo, con una compilación
00:22:52Todo lo que necesitamos hacer es básicamente,
00:22:54una vez que empezamos,
00:22:55averiguar qué archivos en la aplicación cambian,
00:22:57volver a ejecutar esos trabajos y luego reutilizar todo lo demás de la compilación anterior.
00:23:01Así que la pregunta básica es, ¿ya somos Turbo?
00:23:05Sí.
00:23:06Así que sí,
00:23:06esto se discutió en la presentación principal,
00:23:08por supuesto.
00:23:09Turbo Pack es estable a partir de la versión 16 de Next.
00:23:12E incluso somos el empaquetador predeterminado para Next.
00:23:14Así que, saben, misión cumplida, de nada.
00:23:17Pero. (risas) (aplausos del público)
00:23:23Y si notaron esa cosa de
00:23:30Solo tomó tres intentos.
00:23:31Pero lo que realmente quiero que se lleven,
00:23:34de nuevo,
00:23:35es esto.
00:23:35Saben, porque no hemos terminado.
00:23:37Todavía tenemos mucho que hacer en rendimiento,
00:23:39y dar el toque final a la capa de caché del sistema de archivos.
00:23:42Les sugiero a todos que lo prueben en desarrollo.
00:23:44Y eso es todo.
00:23:46Muchas gracias.
00:23:47Por favor, encuéntrenme, háganme preguntas.
00:23:49(aplausos del público) (música animada) (música animada)

Key Takeaway

Turbo Pack de Vercel revoluciona el empaquetado web mediante un sistema de programación reactiva incremental basado en caché, que reduce drásticamente los tiempos de compilación y actualización al procesar solo los cambios relevantes, a pesar de los desafíos inherentes a la gestión de una infraestructura compleja.

Highlights

Turbo Pack utiliza un modelo de programación reactiva incremental para optimizar el rendimiento del empaquetado, centrándose en la incrementalidad y el almacenamiento en caché.

El objetivo principal es evitar las compilaciones en frío y asegurar que el costo de la compilación escale con el tamaño del cambio, no con la complejidad de la aplicación.

El sistema implementa una caché granular donde cada operación es cacheable y las dependencias se rastrean automáticamente, eliminando la necesidad de gestión manual de claves de caché.

Las funciones `TurboTask` y los valores `VC` (reactivos) son componentes clave que permiten el seguimiento automático de dependencias y la memoización, deteniendo las invalidaciones cuando los resultados no cambian.

Aunque el modelo reactivo resuelve problemas de caché manual, introduce desafíos como la gestión de una infraestructura de caché compleja, el consumo de memoria y la dificultad de depuración en un entorno asíncrono.

Turbo Pack demuestra mejoras significativas de rendimiento sobre Webpack en tiempos de actualización rápida, compilaciones en frío y en caliente, incluso en aplicaciones grandes.

Turbo Pack es ahora estable y el empaquetador predeterminado para Next.js 16, con planes para seguir mejorando el rendimiento y la capa de caché del sistema de archivos.

Timeline

Introducción y Visión General de Turbo Pack

Luke Sandberg, ingeniero de software en Vercel, se presenta y comparte su experiencia previa en Google trabajando con herramientas web internas y compiladores como TSX a bytecode de Java y Closure. Explica cómo su llegada a Vercel y el trabajo en Turbo Pack le sorprendieron por los ambiciosos objetivos del equipo. La charla se centrará en las decisiones de diseño fundamentales que se tomaron en Turbo Pack y cómo estas decisiones son clave para el rendimiento que ya han logrado.

Filosofía de Diseño: Incrementalidad y Caché

El orador introduce el objetivo de diseño fundamental de Turbo Pack: eliminar por completo la experiencia de las compilaciones en frío para el desarrollador. Explica que la clave para lograr esto es la incrementalidad a través del almacenamiento en caché, donde cada operación del empaquetador debe ser cacheable. Esto asegura que el costo de la compilación escale con el tamaño o la complejidad del cambio, no con el tamaño total de la aplicación, garantizando un rendimiento constante sin importar cuántas bibliotecas se importen.

Desafíos del Empaquetador Ingenuo y la Caché Manual

Se presenta un empaquetador simple para ilustrar la falta de incrementalidad en enfoques básicos, donde el trabajo redundante es común, como analizar el mismo archivo o resolver importaciones cientos de veces. Al intentar añadir una caché manual, surgen múltiples problemas como la invalidación por cambios de archivo, la gestión de enlaces simbólicos, la mutación de AST y la complejidad de las configuraciones del compilador. La adición de callbacks para manejar cambios de archivo resulta en un sistema reactivo pero aún no verdaderamente incremental, ya que un solo cambio puede requerir la reejecución de gran parte del proceso, lo que lo hace inmanejable y propenso a errores.

Requisitos para un Sistema de Empaquetado Incremental Eficaz

El orador reflexiona sobre las deficiencias del enfoque ingenuo y establece los requisitos claros para un sistema de empaquetado realmente incremental. Estos incluyen la capacidad de cachear cada operación costosa de manera sencilla, el manejo automático de claves de caché y dependencias sin intervención manual del desarrollador para evitar errores. Además, el sistema debe ser capaz de reaccionar a cambios en las entradas (archivos, configuraciones, variables de entorno) sin encadenar callbacks, y debe aprovechar las arquitecturas modernas para ser multihilo y rápido. Este conjunto de requisitos apunta a un sistema similar a las 'señales'.

Implementación de la Incrementalidad con Señales en Turbo Pack

Se revela que la solución de Turbo Pack se basa en un sistema similar a las 'señales', inspirado en el compilador de Rust, Salsa y conceptos académicos como Adaptons, que permite componer cálculos y rastrear dependencias con memoización automática. Las funciones `TurboTask` actúan como unidades de trabajo cacheables, mientras que los valores `VC` (valores reactivos) permiten el seguimiento automático de dependencias al ser leídos. Si el resultado de una `TurboTask` no cambia después de una reejecución, las invalidaciones se detienen, lo que es crucial para la eficiencia del Hot Module Replacement (HMR) al evitar trabajo redundante y permitir tiempos de actualización rápidos.

Compensaciones y Desafíos del Modelo Reactivo

El orador aborda las compensaciones inherentes al modelo de programación reactiva incremental de Turbo Pack. Aunque resuelve problemas de caché manual, introduce una infraestructura de caché compleja y un considerable gasto general al rastrear millones de dependencias, la mayoría de las cuales no cambian en una sesión típica. Otros desafíos incluyen la gestión de la memoria, el diseño cuidadoso de algoritmos globales para preservar la incrementalidad y la dificultad de depuración en un entorno asíncrono en Rust con LLDB. Esto último requiere una inversión significativa en herramientas personalizadas de visualización, instrumentación y trazado para el equipo.

Resultados de Rendimiento y Conclusión

Se presentan los resultados de rendimiento de Turbo Pack en una aplicación de producción grande de Vercel (80,000 módulos) y en el sitio web react.dev, mostrando victorias sustanciales sobre Webpack. Turbo Pack supera significativamente a Webpack en tiempos de actualización rápida, compilaciones en frío (94s vs 300s+) y compilaciones en caliente (aprox. 10s), gracias a su capa de caché de sistema de archivos y su incrementalidad. El orador concluye que Turbo Pack es estable en Next.js 16 y es el empaquetador predeterminado, invitando a los desarrolladores a probarlo en sus entornos de desarrollo mientras continúan las mejoras en el rendimiento y la capa de caché.

Community Posts

View all posts