Desafíos para las Optimizaciones de Producción Incrementales

This ad is not shown to multipass and full ticket holders
React Summit US
React Summit US 2025
November 18 - 21, 2025
New York, US & Online
The biggest React conference in the US
Learn More
In partnership with Focus Reactive
Upcoming event
React Summit US 2025
React Summit US 2025
November 18 - 21, 2025. New York, US & Online
Learn more
Bookmark
Rate this content

Analizaremos los optimizadores habituales aplicados en las compilaciones de producción y cómo se pueden combinar con compilaciones incrementales.

This talk has been presented at JSNation 2024, check out the latest edition of this JavaScript Conference.

Tobias Koppers
Tobias Koppers
32 min
13 Jun, 2024

Comments

Sign in or register to post your comment.
Video Summary and Transcription
TurboPack es un nuevo paquete similar a Webpack, enfocado en compilaciones incrementales para hacerlas lo más rápidas posible. Los desafíos en las compilaciones de producción incluyen el almacenamiento en caché persistente, algoritmos incrementales y la optimización del uso de exportaciones. El proceso de compilación se puede dividir en análisis y transformación de módulos, y agrupación del grafo de módulos. TurboPack tiene como objetivo lograr compilaciones de producción más rápidas a través de la optimización incremental y la eficiencia. Se está considerando la colaboración y compatibilidad con otros ecosistemas, junto con el diseño de una interfaz de complemento y la optimización de eliminación de código no utilizado.

1. Introducción a TurboPack y Incremental Builds

Short description:

Trabajo en TurboPack, un paquete nuevo similar a Webpack pero diseñado desde cero. Nuestra misión es centrarnos en las compilaciones incrementales, haciéndolas lo más rápidas posible. Queremos que los desarrolladores dediquen su tiempo a las compilaciones incrementales y solo tengan que hacer la compilación inicial una vez. Esta charla cubre los desafíos únicos de las compilaciones en producción.

Entonces, mi nombre es Tobias Koppers y trabajo en Vercel y trabajo en TurboPack. TurboPack es un paquete nuevo en el que estamos trabajando, similar a Webpack, pero lo estamos diseñando desde cero. Lo estamos utilizando para Next.js y nuestra misión desde el principio con TurboPack fue centrarnos en las compilaciones incrementales. Queremos hacer que las compilaciones incrementales sean lo más rápidas posible, incluso si tenemos que hacer concesiones en las compilaciones iniciales, porque creemos que la mayoría de los desarrolladores tienden a esperar en las compilaciones incrementales a menudo, porque eso es lo que haces mientras desarrollas.

Por otro lado, también intentamos hacer que cada compilación sea incremental. Intentamos hacer que solo tengas que hacer tu compilación inicial una vez y luego dedicar el tiempo restante solo a las incrementables. Esto también significa que si actualizas la versión de Next.js o la versión de TurboPack, no queremos que pierdas tu caché, o si actualizas los paréntesis, y esto también incluye las compilaciones en producción, por lo que también queremos centrarnos en las compilaciones en producción. De eso trata esta charla. Las compilaciones en producción tienen algunos desafíos únicos que quiero cubrir y revisar un poco.

2. Desafíos y Optimizaciones en las Compilaciones en Producción

Short description:

Existen varias optimizaciones comunes en las compilaciones en producción para los empaquetadores, incluyendo tree-shaking, export mangling, module IDs, chunking, scope hoisting, eliminación de código muerto, minificación y hashing de contenido. Estas optimizaciones presentan desafíos diferentes cuando se trata de compilaciones incrementales y optimizaciones de aplicaciones completas. Para las compilaciones incrementales en producción, necesitamos tener al menos dos ingredientes.

Entonces, si observamos ambos lados, por un lado tenemos TurboPack, que se centra en las compilaciones incrementales, y por otro lado tenemos las compilaciones en producción que están altamente optimizadas, y eso no parece estar en oposición, pero de hecho, existen algunos desafíos asociados con estas optimizaciones que generalmente hacemos en las compilaciones en producción. Porque las optimizaciones a menudo, en el desarrollo, podemos centrarnos en hacerlo lo más rápido posible, incluso si sacrificamos el tamaño del paquete o hacemos que las compilaciones sean un poco más grandes, o hacemos algo de estos sacrificios, pero en las compilaciones en producción no queremos hacer estos sacrificios. Queremos que las compilaciones en producción sean lo más optimizadas posible, y básicamente tienes que hacer un equilibrio con el rendimiento en ese sentido. Unir ambos aspectos, como las compilaciones incrementales y las optimizaciones en producción, es un poco desafiante.

Entonces, veamos algunas optimizaciones comunes en las compilaciones en producción para los empaquetadores. La que probablemente conozcas es llamada tree-shaking. Básicamente se trata de tener tu repositorio con muchos archivos, y en tu aplicación solo quieres incluir los archivos que realmente estás utilizando en las páginas, y los empaquetadores generalmente lo hacen siguiendo un grafo de dependencias, siguiendo tus importaciones y solo incluyendo los archivos que realmente estás referenciando. Pero va más allá. Cada archivo generalmente tiene múltiples exportaciones, y tal vez tengas alguna biblioteca de utilidades donde tienes muchas declaraciones de funciones, y tree-shaking también analiza esto y busca en tu código fuente qué exportaciones se utilizan realmente en toda tu aplicación, e incluye solo las que necesitas en tu paquete, y básicamente descarta las demás. Ese es en realidad el primer desafío.

Tenemos que analizar toda tu aplicación para determinar qué exportaciones se utilizan en tu aplicación, y analizar toda la aplicación es básicamente lo opuesto a hacerlo incremental, donde las compilaciones incrementales generalmente quieren analizar una pieza a la vez, y si algo cambia, quieres minimizar los cambios, los efectos en eso. Básicamente, esta optimización de toda la aplicación es un poco opuesta a eso. La siguiente optimización se llama export mangling. Ahora, como buen desarrollador, has creado esta declaración de función y les has dado buenos nombres, nombres largos y significativos para hacer cosas geniales y dar una buena explicación a tus compañeros de trabajo, y en producción no quieres que estos nombres largos se filtren en tus paquetes. Quieres que el empaquetador o la herramienta lo optimice, y generalmente los empaquetadores lo hacen renombrando cosas como A, B y C, algo así, para que sea más optimizado. Pero también hay un problema con eso. Si renombras estas exportaciones en un archivo, también tienes que renombrarlas en el lado de la importación, por lo que cada módulo que importa ese módulo con las declaraciones debe hacer referencia no a los nombres largos sino a los nombres renombrados, por lo que básicamente tienes este efecto donde cambias un módulo o tu optimización cambia un módulo y eso afecta a muchos otros módulos, y esto también es un poco desafiante para las compilaciones incrementales, porque luego cambias una cosa y se propaga a múltiples cambios adicionales, y sí, no es realmente incremental. Otra optimización se llama module IDs, donde generalmente algunos módulos en tu aplicación deben ser direccionables en un momento dado, por lo que es posible que desees obtener alguna exportación de ese módulo y eso, y podrías simplemente darles un nombre en ese momento, tendrías que darles el camino completo como nombre, pero es muy largo y verboso, y para las compilaciones en producción queremos algo más corto, similar al export mangling, por lo que generalmente en Webpack les damos solo números, números cortos, y los referenciamos por eso, y el problema con eso es que ahora le das a cada módulo un número, pero debes asegurarte de que este número sea único en toda tu aplicación, y la unicidad es nuevamente un problema, porque debes analizar toda tu aplicación para determinar si este nombre ya está tomado, si hay un conflicto, por lo que esto también es una optimización de toda la aplicación.

Otra optimización es el chunking, por lo que generalmente tu empaquetador no coloca todos tus módulos en un solo archivo y lo sirve para todas las páginas, porque terminaría con megabytes enormes de un paquete, por lo que lo dividimos, o la división de código, lo dividimos en paquetes por página, pero también hacemos algo como un fragmento común o una optimización de módulos compartidos, donde determinamos si hay módulos compartidos entre tus múltiples páginas o múltiples fragmentos, y luego los colocamos en un archivo compartido para no tener que cargar los mismos módulos múltiples veces, básicamente cargar el módulo compartido una vez, y encontrar módulos compartidos también requiere mirar múltiples páginas al mismo tiempo, por lo que esto también es una optimización de toda la aplicación. Hay una optimización llamada scope hoisting, donde básicamente no queremos, como buenos desarrolladores, escribir muchos módulos pequeños, porque eso organiza bien tu código, y básicamente no queremos que esta abstracción de módulos se filtre en tiempo de ejecución, por lo que queremos deshacernos de muchos módulos pequeños a la vez y básicamente hacer solo lo que realmente necesitamos en ese momento.

Entonces, la optimización de scope hoisting básicamente fusiona módulos juntos bajo ciertas condiciones, y lo complicado son las condiciones en este caso. Tenemos que determinar qué módulos se ejecutan siempre en toda la aplicación, siempre en el mismo orden, en este orden, y luego podemos fusionarlos porque sabemos que este es el orden en el que se ejecutan. Básicamente, encontrar este tipo de coincidencia de esta condición, encontrar algo que sucede siempre en toda tu aplicación, es nuevamente una optimización de toda la aplicación. Luego hay algunas optimizaciones simples como la eliminación de código muerto, que simplemente omite el código que no se utiliza, o la minificación, que simplemente escribe tu código de manera más eficiente, omite los espacios en blanco y ese tipo de cosas. Luego, la última optimización es el hashing de contenido, donde básicamente agregamos un hash al final de cada nombre de archivo para que sea almacenable en caché a largo plazo, lo que básicamente significa que puedes enviar una cabecera de caché inmutable y luego la caché del navegador puede almacenar en caché eso. Y sí, esas son básicamente las ocho optimizaciones que se me ocurren.

Entonces, si resumimos eso, en esta tabla, puedes ver que muchas de estas optimizaciones, o la mitad de ellas, requieren optimizaciones de toda la aplicación, y muchas de ellas también tienen efectos donde cambias algo y luego el lado del importador de eso cambia. Estas son un poco complicadas para las compilaciones incrementales. Pero lo veremos más adelante. En general, para las compilaciones incrementales en producción, lo que necesitamos hacer es tener al menos dos ingredientes, o al menos dos ingredientes.

3. Caché Persistente y Confiabilidad

Short description:

En las compilaciones en producción, necesitamos una caché persistente para garantizar la confiabilidad y la invalidación correcta de la caché. TurboFact construye un grafo de dependencias para rastrear cambios y determinar qué necesita ser recalculado. El enfoque se centra en la persistencia confiable de la caché y evitar las no determinismos.

Entonces, una cosa en las compilaciones en producción, lo que suele suceder es que las compilaciones en producción generalmente se ejecutan en un contenedor nuevo, en un clon nuevo, una nueva instalación de NPM. Por lo tanto, necesitamos algo en lo que básicamente no podamos confiar en tener una caché en memoria con todos los datos de caché, por lo que necesitamos algún tipo de persistencia de caché, y eso se llama básicamente caché persistente. Y lo que hace TurboFact es básicamente, durante la ejecución, construye un grafo de todas las dependencias de nuestras sub-ejecuciones. Y hacemos eso para rastrear las dependencias entre múltiples subtareas, y básicamente lo hacemos para saber que si algo cambia, podemos propagar los cambios al grafo y saber qué tenemos que recalcular realmente. Básicamente, necesitamos persistir este grafo en algún tipo de caché persistente. Y lo más importante para este tipo de ingrediente, no quiero entrar en detalles sobre cómo funciona realmente la caché persistente, porque eso probablemente sea un tema diferente, pero lo más importante es la confiabilidad de esta caché persistente. Por lo tanto, queremos centrarnos en una invalidación de caché realmente correcta, porque si eso no funciona y las personas están eliminando su caché porque no funciona como se esperaba o tiene no determinismos o algo así, entonces básicamente no cumplimos nuestro objetivo de hacer que cada compilación sea incremental. Terminamos, como, si eliminas la caché, terminas con una compilación no incremental nueva, y queremos evitar eso. Por lo tanto, la confiabilidad es realmente clave aquí.

4. Algoritmos Incrementales y División del Trabajo

Short description:

Los algoritmos incrementales son esenciales para garantizar una computación eficiente con cambios pequeños. Se proponen tres ideas: utilizar diferencias de importaciones para calcular un nuevo resultado, aprovechar las sub-tareas en caché y dividir el trabajo en sub-tareas más pequeñas. El último enfoque, dividir el trabajo en sub-tareas pequeñas y calcular solo las que han cambiado, es el preferido. Se proporcionan ejemplos para optimizaciones de protección, como la eliminación de código no utilizado y la minificación, que se pueden dividir ejecutando cada archivo de salida individualmente.

Y otro ingrediente es que necesitamos algoritmos incrementales para eso. Un algoritmo incremental es básicamente algo en lo que, si algo cambia pequeño, la computación también es pequeña, y si algo cambia grande, puede haber una computación más grande. Básicamente, queremos que el cambio y el costo de la computación sean de alguna manera linealmente dependientes entre sí. Y eso generalmente funciona, pero este tipo de problemas de los que hablé antes, como las optimizaciones de toda la aplicación y los efectos del sitio de importación, básicamente rompen esta promesa, porque luego tienes algo en lo que tienes que mirar toda la aplicación para tomar algunas decisiones, o algo así, un módulo afecta a todos sus importadores y básicamente son un poco desafiantes para esta dependencia lineal entre la computación y el cambio. Sí, así que, y básicamente quiero centrarme en los aspectos de los algoritmos incrementales porque la caché persistente es demasiado para cubrir.

Entonces, para los algoritmos incrementales, se me ocurrieron tres ideas de cómo hacerlo. La primera idea es básicamente tomar el resultado anterior, el que calculamos en la compilación inicial, y hacer una diferencia de las importaciones y luego a partir de eso escribir un algoritmo para calcular un nuevo resultado a partir de eso. Eso es lo primero que se me ocurrió. Y la siguiente idea fue hacer algo con las sub-tareas en caché, por lo que tenemos esta gran tarea de cálculo que realiza el cálculo y luego generamos sub-tareas más pequeñas y básicamente almacenamos en caché estas sub-tareas para que en las compilaciones incrementales no tengamos que volver a ejecutarlas, y básicamente en este sentido las compilaciones incrementales son más rápidas con eso. O el segundo enfoque fue simplemente dividir todo, evitar esta gran tarea principal, en su lugar dividir todo el trabajo en muchas, muchas tareas pequeñas y cada una de ellas puede ser almacenada en caché por sí misma y básicamente resolverlo de esta manera.

Y visualicé este tipo de ideas para hacerlo más visual porque lo visual siempre es genial en las charlas. Entonces, en la fila superior se ve la compilación inicial donde se obtienen las entradas, se realiza algún cálculo, se tiene una implementación y luego se calcula el resultado, y una compilación incremental básicamente toma las entradas y también toma las entradas antiguas y básicamente tiene una implementación diferente para las compilaciones incrementales y luego a partir de eso y del resultado anterior se calcula un nuevo resultado. Pero realmente no me gusta ese enfoque porque tienes que hacer dos implementaciones para las compilaciones incrementales y las compilaciones iniciales, por lo que es propenso a errores si cometes errores, y también agrega cierto grado de no determinismo al sistema. Tal vez tu compilación ahora dependa de lo que fue la compilación anterior o básicamente puedas arruinar algo en la compilación incremental, así que sí, el no determinismo no es algo que me guste y hablé antes sobre por qué no me gusta, así que esto está básicamente descartado. El enfoque de las sub-tareas en caché es mejor porque básicamente tienes una única implementación pero luego, tal vez en las compilaciones incrementales, omites algunas de las sub-tareas.

Entonces eso funcionaría, pero aún tienes el problema de tener esta tarea principal más grande donde puede ocultar algún trabajo que no se realiza cada vez, incluso en las compilaciones incrementales. Tal vez recorras todos los módulos y esas cosas. Así que eso tampoco es algo que realmente me guste. Realmente quiero ir con el último enfoque, dividir todo el trabajo en sub-tareas realmente pequeñas y luego solo calcular las sub-tareas que han cambiado, las que están en caché y no han cambiado. Y parece que hay algo al respecto. Entonces, si volvemos a mirar nuestra tabla y tal vez hagamos algunos ejemplos de cómo podemos hacer eso para las optimizaciones de protección, comienzo con las más fáciles porque no requieren conocimiento de toda la aplicación y no tienen efectos en el lado de las importaciones, que son la eliminación de código no utilizado y la minificación. Y lo bueno de estas optimizaciones de implementación es que básicamente las ejecutamos en cada archivo de salida, y lo bueno es que emitimos muchos archivos de salida, por lo que simplemente podemos dividirlo ejecutando cada archivo de salida por separado. Y básicamente se ve así, donde dividimos el trabajo simplemente ejecutándolo en cada archivo de salida. Y en las compilaciones incrementales solo tenemos que volver a ejecutarlo cuando cambia un archivo de salida. Así que esto es fácil. Problema resuelto. El siguiente es un poco más complicado porque requieren conocimiento de toda la aplicación. Permítanme explicar cómo funciona. Tenemos esta eliminación de código innecesario que básicamente consta de dos pasos, uno es este grafo de módulos donde seguimos las importaciones para encontrar los módulos que necesitas en tu aplicación. Y luego el segundo paso, por lo que el primer paso no necesita realmente conocimiento de toda la aplicación, simplemente puedes seguir el grafo y hacerlo siguiendo el grafo.

5. Optimización del Uso de Exportaciones y Fases de Compilación

Short description:

Para optimizar el proceso de determinar qué exportaciones se utilizan en una aplicación, se puede utilizar un nuevo algoritmo que utiliza un enfoque de grafo con las exportaciones de módulos como la unidad más pequeña. Esto permite una mejor optimización al dividir los módulos en diferentes fragmentos y aislar la funcionalidad deseada. Sin embargo, existen ciertas optimizaciones, como los IDs de módulos y la división en fragmentos, donde aún no se ha logrado encontrar un nuevo algoritmo. El proceso de compilación se puede dividir en dos fases: análisis y transformación de módulos para construir un grafo de módulos, y división del grafo de módulos en varios archivos para la generación de código.

Pero el segundo paso fue determinar qué exportaciones se utilizan en tu aplicación, por lo que debemos analizar todas las formas en que se utiliza el módulo en la aplicación y determinar qué exportaciones se utilizan. Pero ¿es necesario hacer eso? Podemos optimizarlo mediante el desarrollo de un nuevo algoritmo. Entonces, ¿por qué no utilizar un enfoque de grafo también para las exportaciones? Resulta que podemos hacerlo utilizando un grafo con las exportaciones de módulos como la unidad más pequeña en lugar de los módulos. Luego, simplemente seguimos el grafo hasta las exportaciones. Y luego podemos utilizar esta idea previa con el tree shaking, donde simplemente seguimos el grafo para encontrar los módulos, también lo hacemos para encontrar las exportaciones. Y luego solo incluimos las exportaciones que realmente necesitamos.

Si observamos un uso más práctico, se vería así. Primero tomamos un módulo y lo dividimos en múltiples fragmentos, emparejando cada exportación. Pero también hay casos en los que tienes más fragmentos además de las exportaciones, también tienes fragmentos internos donde tal vez hay un estado compartido entre las exportaciones y otras cosas. Básicamente, lo dividimos en múltiples fragmentos de módulo y luego resolvemos, y si tenemos una importación, seguimos la importación, no hasta el módulo, sino hasta el fragmento de módulo correspondiente. Luego construimos un grafo con los fragmentos de módulo y solo incluimos eso. Y esto es aún mejor porque resulta ser una mejor optimización, ya que ahora puedes dividir los módulos en diferentes fragmentos y dividirlos entre diferentes páginas, y una página no filtrará una exportación utilizada a otra página, lo que básicamente aísla lo que deseas. Y resulta que si creamos fragmentos de módulo con un solo par de exportaciones, también podemos resolver fácilmente el enmascaramiento de las exportaciones, ya que si es una sola exportación, simplemente siempre la llamamos A y luego la llamamos enmascarada. Y esto ya no afecta al tipo de importación, porque si siempre se llama A, entonces el tipo de importación nunca cambia, ya que siempre se llama A. Y esto resuelve básicamente estos dos problemas. Y se resuelven eliminando la necesidad de conocer toda la aplicación y los problemas de tipo de importación.

6. Optimización de la Compilación y Procesamiento Incremental

Short description:

Para optimizar el proceso de compilación, podemos dividirlo en dos fases. La primera fase implica analizar y transformar los módulos para construir un grafo de módulos, mientras que la segunda fase implica dividir el grafo de módulos y generar código. Ejecutando la primera fase para cada página o ruta en la aplicación y agregando los grafos de módulos, podemos extraer el conocimiento necesario de toda la aplicación y utilizarlo en la segunda fase. Sin embargo, todavía hay un paso de cálculo global que se puede dividir en piezas más pequeñas, haciéndolo más incremental y intensivo en cálculos.

Pero hay algunas optimizaciones, como los IDs de módulos y la división en fragmentos, donde en realidad no podemos encontrar un nuevo algoritmo, o al menos aún no lo he hecho, o no hemos descubierto cómo hacerlo, donde no podemos eliminar este problema de información de toda la aplicación. Así que también necesitamos resolver ese problema. Y tampoco es muy complicado porque básicamente podemos dividir nuestra compilación en dos fases.

La primera fase donde realmente analizamos los módulos y los transformamos y construimos un grafo de módulos, y luego la segunda fase donde tomamos este grafo de módulos, lo dividimos en varios archivos y realizamos el paso de generación de código y obtenemos los archivos de salida. Y si observamos de cerca, solo necesitamos el conocimiento de toda la aplicación de la primera fase y solo lo consumimos en la fase posterior. Entonces lo que podemos hacer es ejecutar esta primera fase para cada página en tu aplicación o cada ruta en tu aplicación, y luego tomar todos los grafos de módulos, agregar esto en un paso global, y luego básicamente generar o extraer este conocimiento de toda la aplicación y alimentarlo en la segunda fase y luego ejecutar la segunda fase para cada página en tu aplicación. La parte por página aún es incremental, y luego hay esta parte no incremental donde agregamos esa información y luego la alimentamos en la segunda fase, que es incremental nuevamente. Y si lo construimos en este grafo con un gran bloque de conocimiento de toda la aplicación, eso es un poco problemático porque ahora tu conocimiento de toda la aplicación a menudo cambia porque algo cambió. Y luego invalidaría, calculamos todo el paso de generación de código de todas las fases. Entonces lo que realmente queremos hacer es dividir este bloque de información de toda la aplicación en muchos, muchos bloques pequeños donde realmente podemos consumir solo las partes del conocimiento de toda la aplicación que realmente necesitamos para la generación de código. Y podemos mostrar eso en este ejemplo. Básicamente también intentamos empujar la mayor parte del trabajo a la parte incremental. En este caso, el ID del módulo funciona obteniendo todos los identificadores de tus módulos, de todos los módulos en la aplicación, luego los hasheamos y básicamente a partir de este hash calculamos el número que se utiliza para el ID del módulo. Y luego hay un paso adicional, como si hay conflictos de hash, que son probables porque intentamos hacer que el hash sea bastante corto, entonces básicamente eliminamos el conflicto usando otro ID, básicamente volviéndolo a hashear hasta que encaje.

7. Optimización Incremental y Eficiencia

Short description:

Para optimizar el proceso de compilación, podemos extraer los IDs de los módulos por página y agregarlos en una lista global. Al hacer un hash de estos IDs, podemos alimentar selectivamente el paso de generación de código. Para mejorar aún más la eficiencia, podemos dividir el paso de cálculo en piezas más pequeñas, utilizando IDs de módulos pre-hasheados y manejando conflictos. El mismo enfoque se aplica a la división en fragmentos, donde utilizamos la coloración de gráficos para determinar los módulos compartidos entre los fragmentos. Al optimizar incrementalmente el proceso, podemos lograr compilaciones de producción más rápidas.

Para hacer esto de manera incremental, primero podemos extraer de cada página el grafo de módulos y obtener todos los IDs de los módulos, creando una lista de todos los identificadores de módulos por página. Luego, agregamos esta lista a una lista grande de todos los identificadores de módulos, donde la cantidad define la longitud del ID que necesitamos, y luego calculamos los IDs mediante un hash. Luego, tenemos una asignación, como todos los IDs de módulos que necesitamos en la aplicación, y sabemos que emparejan con un módulo. Y para no conectar directamente los IDs de los módulos con la generación de código, básicamente queremos seleccionar un solo ID de módulo, seleccionar todos los IDs de módulos, un solo ID de módulo de eso, y luego alimentar solo los IDs de módulos que el paso de generación de código realmente necesita en ese paso. Tal vez solo importe estos tres módulos, y solo queremos seleccionar estos tres IDs de módulos de nuestro conocimiento global de la aplicación. Y de esta manera, también es incremental en el sentido de que la generación de código solo se recalcula si se actualiza el ID de módulo que consume.

Pero aún es un poco problemático porque todavía tenemos este gran paso de cálculo que se ejecuta globalmente, como calcular longitudes de ID e IDs, así que también podemos pensar en ¿podemos dividirlo aún más? Y resulta que realmente podemos hacerlo haciéndolo más complicado, dividiéndolo en piezas más pequeñas. Primero, a partir de los identificadores de ID de módulo, calculamos las longitudes de ID en el paso global, y luego calculamos las longitudes de ID, y eso es realmente desafiante. Es la única oportunidad si agregas un montón de nuevos módulos, eliminas muchos módulos. Entonces las longitudes de ID realmente están cambiando. Entonces podemos retroalimentar las longitudes de ID nuevamente en la ejecución incremental por página, por página, y ejecutamos el primer intento del hash, por lo que tenemos algún tipo de IDs de módulos pre-hasheados, pre-hasheos del módulo, y luego lo consumimos en este paso global de cálculo de IDs de módulos. Entonces no tenemos que hacer mucho trabajo de hash, solo tenemos que resolver conflictos y volver a hashear para el módulo en conflicto. Así que eso es más pequeño y hace que los trabajos sean menos intensivos en cálculos, lo hace más incremental, y el resto sigue siendo igual.

Entonces eso es todo en términos de métrica, y si observamos la división en fragmentos, básicamente se comporta exactamente igual. Básicamente, para la división en fragmentos, necesitamos hacer alguna coloración de gráficos donde determinamos qué módulos son compartidos entre los fragmentos, por lo que es básicamente el mismo enfoque, solo que por página hacemos la coloración de gráficos, y luego tenemos colores de módulos precalculados, luego lo agregamos en un paso global donde fusionamos los colores para crear un objeto global de colores de módulos con todo este conocimiento de la aplicación, luego seleccionamos el color de un módulo nuevamente y básicamente este color de módulo se alimenta en el trabajo real de división en fragmentos que luego consume solo los colores que necesita. Si lo observamos en detalle, es básicamente lo mismo. Así que no hay métrica detrás de eso. Si observamos de cerca, la mayoría de las optimizaciones, si dedicas un poco de tiempo a pensar en ello, puedes encontrar un enfoque que haga la mayor cantidad de trabajo incremental posible, y a largo plazo, habrá muchos beneficios de tener algoritmos un poco diferentes para hacerlo más incremental, y al final, podríamos lograr compilaciones de producción incrementales, y eso sería genial.

8. Preparación para la producción de TurboPack

Short description:

Eso fue todo lo que tenía que hablar. Espero que haya sido útil. ¿Cuándo estará TurboPack listo para producción? Funciona muy bien para el desarrollo, pero las compilaciones de producción tomarán un poco más de tiempo. También hay una compilación de producción experimental, pero tiene algunos problemas. Actualmente, puedes usar Next Dev para el desarrollo y Webpack para las compilaciones de producción.

Eso fue todo lo que tenía que hablar. Espero que haya sido útil. A menudo no es ...

Hay una pregunta de la que estoy seguro de que ya te están acosando mucho al principio de esto. Voy a responderla de inmediato. ¿Cuándo estará TurboPack listo para producción? Deberías probarlo. Si quieres probarlo, pruébalo definitivamente. Funciona muy bien. Lo hemos estado usando durante meses en vessel.com y funciona bien, y probablemente haya algunos errores, pero si no lo pruebas, nos ayudará si lo intentas. ¿Solo quieres encontrar los errores ahora? Quieres encontrar los errores. Es realmente estable para el desarrollo, pero estaba hablando de las compilaciones de producción. Eso tomará un poco de tiempo, y también tenemos la suite de pruebas de Next ejecutándose contra TurboPack definitivamente que está pasando todo, y creo que hay un 70 por ciento de aprobación para las compilaciones de producción, así que eso tomará un poco de tiempo para las compilaciones de producción, y también necesitamos almacenamiento en caché persistente, en lo que aún no hemos trabajado, pero estaremos allí. Deberías comenzar a usarlo para el desarrollo ahora. ¿Qué hay de los proyectos de producción que son proyectos secundarios? Probablemente podamos arriesgarnos. Sí, no es ... Hay una compilación de producción experimental que está un poco oculta detrás de un ... Hicimos la implementación de eso, pero no funciona para todas las funciones. Hay algunos problemas. No está optimizado muy bien. Faltan todas estas optimizaciones. Está minificado, pero son bastante ... De lo contrario, hacemos fragmentación de desarrollo que no es tan eficiente para las compilaciones de producción. Actualmente, puedes usar Next Dev para el desarrollo y Webpack con compilaciones de producción que funcionan. Existe el riesgo de tener diferencias entre ambos, y eso se resolverá una vez que TurboPack esté listo para ... Entonces, hay diferencias para el desarrollo que queremos acelerar la mayor parte del tiempo de todos modos? Sí. Ahí es donde realmente marca la diferencia. Sí. Por eso comenzamos con el desarrollo, porque queríamos solucionar ... Porque la gente se quejaba mucho de la velocidad de desarrollo con Webpack, y ahí es donde se establece el enfoque, y ahora también queremos abordar las compilaciones de producción. Genial.

9. Colaboración, Primitivas y Plug-ins

Short description:

Tuvimos salas separadas para los oradores porque Evan y Tobias no podían estar en la misma sala. No vamos a colaborar en el corto plazo, pero estamos pensando en diseñar una interfaz de plug-in compatible con otros ecosistemas. No compartimos primitivas con Vite y OXC, pero compartimos el analizador y algunas transformaciones con SWC. Aún no hemos comenzado a escribir plug-ins para TurboPack para evitar conflictos con Webpack.

Este siguiente es muy divertido. Mucha gente puede que no lo sepa, pero tuvimos que tener salas separadas para los oradores porque Evan y Tobias no podían estar en la misma sala porque pelearían. No, pero la sugerencia es, ¿por qué no combinas esfuerzos con Evan en un ecosistema simplificado con TurboVitePack? O VitePack. TurboVitePack. Disculpa. Me refiero a que ambos nos beneficiemos mucho de otros ecosistemas. Creo que hay mucho... Aprendemos el uno del otro. Creo - ¿Cuánta mala sangre hay? Eso es lo que queremos saber. No creo que haya mala sangre. No, está bien. Sí. Entonces, ¿no van a colaborar en el corto plazo? ¿Qué? No van a colaborar en el corto plazo para fusionar todos los esfuerzos? Aún no hemos planeado nada al respecto, pero creo que todavía estamos pensando en cómo diseñar la interfaz de plug-in, y tal vez podamos llegar a algo similar a la interfaz de plug-in de Vite o Loops, y luego tal vez eso sea más compatible entre sí. Fantástico. Muy bien. Veamos qué tenemos aquí.

Este también es interesante y me gusta. ¿Vas a compartir primitivas con Vite y OXC? ¿Qué? ¿Qué es OXC? Vite y OXC. Yo tampoco sé qué es OXC. Yo tampoco. Supongo que eso es un no para OXC. Entonces no compartimos primitivas, y también ves que Vite y Tobac son cosas completamente diferentes. Vite es más un no-bundler, y Tobac es un bundler, así que incluso desde ese lado es realmente diferente. Puede haber alguna superposición en las transformaciones, y técnicamente también estamos compartiendo primitivas con SWC, que es un analizador que ambos usamos. Así que estamos compartiendo el analizador y también compartiendo algunas transformaciones de SWC. Sí. Genial.

Oh, este es divertido. ¿Cuándo podemos esperar comenzar a escribir plug-ins para TurboPack? Aún no. Tengo un poco de miedo de comenzar a diseñar una interfaz de plug-in porque no quiero arruinarla con Webpack, así que realmente lo estoy retrasando hasta que tenga que escribir eso.

10. Interfaz de Plug-ins y Tree-Shaking

Short description:

Diseñar una interfaz de plug-ins es difícil y requiere un manejo cuidadoso de los cambios que pueden romper la compatibilidad. Puede seguir un sistema de versiones similar a las Ediciones de Rust. El nuevo enfoque de tree-shaking permite una división de módulos más granular. Marcar los paquetes como libres de efectos secundarios y evitar los archivos barrel puede mejorar la eficiencia del tree-shaking.

Y sí, no estoy seguro. Aún no. ¿Algún problema por resolver? Probablemente comience después de que las compilaciones de producción funcionen sin plug-ins, y luego comienzo con eso. Genial. Es difícil porque es realmente difícil diseñar una interfaz de plug-in, o APIs o interfaces de plug-in porque luego terminas necesitando hacer cambios que rompen la compatibilidad una vez que te das cuenta de que fue un error de diseño o algo así. Sí, probablemente lo esté retrasando un poco por esa razón. Sí. Así evitamos tantos cambios que rompen la compatibilidad por ahora. Sí, si no tienes una API o una interfaz de plug-in, no puedes hacer cambios que rompan la compatibilidad. Eso es genial. Por ahora, está bien. Pero al final tendremos que hacerlo. Así que necesitamos una interfaz de plug-in. Inevitable en algún momento. Es inevitable. Supongo que probablemente terminará siendo algún tipo de... Realmente me gusta lo de la Edición de Rust, donde tienes, como, esta es la Edición 2022 o algo así, y luego tienes cambios que rompen la compatibilidad cuando pasas a las ediciones. Pero al final, básicamente tienes algún tipo de versionamiento de la interfaz, para que puedas versionar tu proyecto o tu módulo o tal vez el paquete, y luego esto funciona con esta interfaz de plug-in, y la próxima edición funciona con el plug-in. Puedes mezclarlo en múltiples plug-ins, así que tal vez eso sea algo útil. Pero tienes que investigar eso en detalle. Sí. Estoy seguro de que tienes otros 100 problemas que aún estás resolviendo. Sí. A mucha gente le gusta esto, así que a muchas personas les interesa esto.

¿Las funciones estáticas de una clase se pueden tree-shakear? Si no, ¿qué recomendarías para agrupar funciones? Sí, este nuevo enfoque de tree-shaking hace que más cosas se puedan tree-shakear, porque podemos dividir los módulos de manera más granular. Eso realmente ayuda. Y en general, también ayuda si marcas tu paquete como libre de efectos secundarios, porque si según la especificación de ECMAScript, todo puede tener efectos secundarios. Si importas algo, básicamente tienes que incluirlo o ejecutarlo de alguna manera, porque según la especificación, podría tener efectos secundarios, y no siempre se pueden extraer los efectos secundarios de JavaScript al analizar el código desde la perspectiva de un bundler. Entonces, si tú, como autor del módulo, marcas tus módulos como libres de efectos secundarios, podemos razonar más sobre si esto no tiene que incluirse porque no lo usas, y eso lo hace más eficiente y más fácil de tree-shakear. Y también ayuda evitar los archivos barrel, que son realmente problemáticos para el tree-shaking, no en el sentido de que no se puedan eliminar, pero siempre se tiene que calcular todo, porque si exportas cosas desde algo, tenemos que mirar todos estos módulos para ver qué exportaciones se incluyen.

11. Optimizando Paquetes y Compartición de Árbol

Short description:

Reexportar con exportaciones nombradas y evitar la pre-bundling puede optimizar los paquetes. El enfoque de compartición de árbol en TurboPack permite incluir exportaciones solo en las páginas que se están utilizando.

Realmente ayuda si reexportas con exportaciones nombradas, así que si no usas export star, sino que usas export y los nombres y luego from. Las exportaciones nombradas son una ganancia fácil para la optimización. Iba a preguntar egoístamente, ¿cuáles son las cosas fáciles para optimizar nuestros paquetes? Efectos secundarios y exportaciones nombradas, eso es lo que se me ocurre.

Eso es muy interesante. Ahí es donde voy a ir. Exportar un solo objeto con todas las cosas, usando estas exportaciones, ayuda a ASM exports. Muy bien. Creo que podemos tener tiempo para otra pregunta. Veamos lo que tenemos. Tenemos una larga lista de cosas aquí. La pre-bundling de tu biblioteca dificulta la optimización, porque si pre-bundleas tu biblioteca, es mejor que se mantenga como múltiples modules en tu biblioteca, y si la pre-bundleas eso es realmente difícil de optimizar porque luego solo ves un solo archivo que es un montón de cosas, y eso generalmente es más difícil de optimizar que tener múltiples archivos.

Esto es interesante porque supongo que esto es más como, algo así como equilibrar la carga. Si un componente se usa en el 90 por ciento de las páginas pero el otro 10 por ciento tiene el 80 por ciento de el tráfico, ¿tienes una opción donde se tenga en cuenta el tráfico de las páginas, en lugar de eso? En Webpack, actualmente, eso es un problema porque luego terminas incluyendo la exportación en la página que también tiene mucho tráfico, pero en TurboPack, básicamente un nuevo enfoque de compartición de árbol que mencioné, básicamente resuelve ese problema. Ahora, básicamente lo divide en estas son las exportaciones y esa exportación, y puede incluirlo solo en las páginas que estás utilizando realmente.

¿Entonces puedes elegirlo a nivel de página? Sí, es básicamente a nivel de exportación, compartición de árbol. No, como, otras páginas no influyen, como, las páginas no influyen en las otras formas como las exportaciones utilizadas. Así que eso está resuelto. Ahí lo tienes. Fácil. Ahora solo tienes que implementarlo, todos. Tan fácil como eso. Fantástico. Creo que vamos a terminar esta línea de preguntas porque vas a estar afuera respondiendo más preguntas para todos, estoy seguro. Otro gran aplauso para Tobias. Gracias un millón por volver. Siempre es bueno verte. Gracias por recibirme.

Check out more articles and videos

We constantly think of articles and videos that might spark Git people interest / skill us up or help building a stellar career

El Núcleo de Turbopack Explicado (Codificación en Vivo)
JSNation 2023JSNation 2023
29 min
El Núcleo de Turbopack Explicado (Codificación en Vivo)
Tobias Koppers introduces TurboPack and TurboEngine, addressing the limitations of Webpack. He demonstrates live coding to showcase the optimization of cache validation and build efficiency. The talk covers adding logging and memorization, optimizing execution and tracking dependencies, implementing invalidation and watcher, and storing and deleting invalidators. It also discusses incremental compilation, integration with other monorepo tools, error display, and the possibility of a plugin system for Toolpag. Lastly, the comparison with Bunn's Builder is mentioned.
Rome, ¡una cadena de herramientas moderna!
JSNation 2023JSNation 2023
31 min
Rome, ¡una cadena de herramientas moderna!
Top Content
Rome is a toolchain built in Rust that aims to replace multiple tools and provide high-quality diagnostics for code maintenance. It simplifies tool interactions by performing all operations once, generating a shared structure for all tools. Rome offers a customizable format experience with a stable formatter and a linter with over 150 rules. It integrates with VCS and VLSP, supports error-resilient parsing, and has exciting plans for the future, including the ability to create JavaScript plugins. Rome aims to be a top-notch toolchain and welcomes community input to improve its work.
Componentes de Servidor con Bun
Node Congress 2023Node Congress 2023
7 min
Componentes de Servidor con Bun
Top Content
Bun is a modern JavaScript runtime environment that combines a bundler, transpiler, package manager, and runtime. It offers faster installation of NPM packages and execution of package.json scripts. Bun introduces a new JavaScript and TypeScript bundler with built-in support for server components, enabling easy RPC with the client. This allows for code splitting and running code that streamingly renders React or any other library from the server and mixes it with client code, resulting in less JavaScript sent to the client.
Parcel 2: el Empaquetador Automágico
DevOps.js Conf 2021DevOps.js Conf 2021
8 min
Parcel 2: el Empaquetador Automágico
Parcel 2 is a ground-up rewrite of Parcel 1, a fast and scalable zero-configuration web application bundler used by large companies like Atlassian and Adobe. It offers a zero-config approach with good defaults, making it production-ready out of the box. The new features include a revamped plugin system, a configuration file, transformers for file conversion, optimizers for code compression, target support for different browsers, diagnostics for error debugging, and named pipelines for data and JavaScript in different formats. Parcel 2 also supports different import scenarios, such as importing JSON files with named pipelines and using query parameters for image optimization. It includes various performance improvements, stable caches, optimized data structures, enhanced code splitting and bundling, improved scope hosting, and better support for monorepos and libraries. A React example is provided to showcase the simplicity of Parcel and how to use it with React.
Bundlers: Una Profundización en las Herramientas de Construcción Modernas de JavaScript
JSNation 2025JSNation 2025
20 min
Bundlers: Una Profundización en las Herramientas de Construcción Modernas de JavaScript
Edoardo, DevRel at Storyblok, explains the importance of JavaScript bundlers and discusses Storyblok's migration to Vite. Challenges with old JavaScript applications are illustrated, emphasizing issues with global variables and dependency control. Optimizing JavaScript module loading through ES modules is discussed, highlighting browser compatibility and performance concerns. The process of creating and structuring JavaScript bundles is detailed, focusing on dependency graphs and module organization. Techniques for managing bundle execution, utilizing abstract syntax trees for code parsing, and implementing optimization strategies are explored, with a specific emphasis on Vite, hot module replacement, and development enhancements.
Rspack Recientemente Fue Premiado como Avance del Año en JSNation
JSNation US 2024JSNation US 2024
31 min
Rspack Recientemente Fue Premiado como Avance del Año en JSNation
Today's Talk discussed RSPack, a Rust rewrite of Webpack that won Breakthrough of the Year at JS Nation. RSPack was developed at ByteDance to address the complexities of their WebInfra and provide a suitable bundler for native and exotic environments. The development of RSPack focused on improving on ES Build's capabilities, reducing CI wait times, and maximizing product velocity. ESBuild and Webpack had various weaknesses and limitations, leading to the decision to create a new bundler architecture. RSPack aimed to be language-agnostic and prioritize artifact integrity, performance, productivity, and business value. API design emphasized a balance between performance and versatility, tailored for larger businesses. RSPack achieved significant reductions in cloud costs and build times, and future ideas include TypeScript optimization and remote caching. For smaller companies, considerations when choosing a bundler include performance, chunking, reliability, and user experience.