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.
Comments