Desafíos para las Optimizaciones de Producción Incrementales

Rate this content
Bookmark

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.

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.
Dominando tu paso de construcción - Dominando tu código
DevOps.js Conf 2021DevOps.js Conf 2021
28 min
Dominando tu paso de construcción - Dominando tu código
This Talk explores JavaScript code optimization using Rollup, showcasing examples of improved load times and reduced server size. It delves into Rollup customization and plugin development, demonstrating how to write plugins and remove code using hooks. The Talk also covers module code loading, advanced code control, and importing/emitting files with Rollup. Additionally, it highlights the adoption of Rollup's plugin system by other tools and introduces a self-made terminal used in the presentation.