Vite#
Frontend Building Systems#
Los desarrolladores escriben JavaScript; los navegadores ejecutan JavaScript. Fundamentalmente, no es necesario un paso de compilación en el desarrollo frontend. Entonces, ¿por qué tenemos un paso de compilación en el frontend moderno?
A medida que los proyectos de frontend crecen y la ergonomía del desarrollador se vuelve más importante, enviar el código fuente de JavaScript directamente al cliente conduce a dos problemas principales:
Características del Lenguaje No Soportadas: Debido a que JavaScript se ejecuta en el navegador, y hay muchos navegadores con distintas versiones, cada característica del lenguaje que se utiliza reduce la cantidad de clientes que pueden ejecutar ese JavaScript. Además, extensiones del lenguaje como JSX no son JavaScript válido y no se ejecutarán en ningún navegador.
Rendimiento: El navegador debe solicitar cada archivo JavaScript individualmente. En un código grande, esto puede resultar en miles de solicitudes HTTP para renderizar una sola página. En el pasado, antes de
HTTP/2
, esto también resultaba en miles de intercambios TLS.
Además, pueden ser necesarios varios viajes de red secuenciales antes de que todo el JavaScript esté cargado. Por ejemplo, si index.js importa page.js y page.js importa button.js, se necesitan tres viajes de red secuenciales para cargar completamente el JavaScript. Esto se llama el problema del “waterfall”.
Los archivos fuente también pueden ser innecesariamente grandes debido a nombres de variables largos y caracteres de indentación de espacio en blanco, aumentando el uso de ancho de banda y el tiempo de carga de la red.
Los sistemas de construcción frontend procesan el código fuente y emiten uno o más archivos JavaScript optimizados para enviarlos al navegador. El resultado distribuible suele ser ilegible para los humanos.
Los sistemas de construcción frontend generalmente incluyen tres pasos: transpilación
, empaquetado
y minificación
.
Algunas aplicaciones no necesitan los tres pasos. Por ejemplo, las bases de código más pequeñas pueden omitir el empaquetado o la minificación, y los servidores de desarrollo pueden saltarse estos pasos por motivos de rendimiento. Se pueden agregar pasos personalizados adicionales.
Algunas herramientas implementan múltiples pasos de construcción, como los empaquetadores, que a menudo realizan los tres pasos, siendo suficientes para aplicaciones simples. Las aplicaciones complejas pueden requerir herramientas especializadas para cada paso.
Transpilación#
La transpilación convierte el JavaScript moderno a una versión más antigua para resolver problemas de compatibilidad con navegadores. Un objetivo común es ES6/ES2015.
Herramientas y frameworks pueden agregar pasos de transpilación. Por ejemplo, JSX debe transpilarsе a JavaScript. Si una biblioteca ofrece un plugin de Babel, generalmente requiere un paso de transpilación. Además, lenguajes como TypeScript, CoffeeScript y Elm deben transpilarsе a JavaScript.
Los transpiladores comunes son Babel, SWC y TypeScript Compiler.
Babel (2014): Transpilador estándar, escrito en JavaScript, pero lento y difícil de depurar.
SWC (2020): Transpilador rápido, multi-hilo, escrito en Rust, 20 veces más rápido que Babel, soporta TypeScript y JSX.
TypeScript Compiler (tsc): Implementación de referencia de TypeScript, soporta TypeScript y JSX, pero es muy lento.
Se puede omitir la transpilación si el código es JavaScript puro y usa ES6 Modules.
Una alternativa para características no soportadas es el uso de polyfills, que se ejecutan en tiempo de ejecución para implementar características faltantes, aunque esto tiene un coste en rendimiento y no todas las características pueden ser polyfilled.
Todos los empaquetadores también son transpiladores, ya que analizan múltiples archivos fuente JavaScript y emiten un nuevo archivo empaquetado. Algunos pueden analizar TypeScript y JSX, eliminando la necesidad de un transpilador separado para necesidades de transpilación sencillas.
Empaquetado (bundle)#
El empaquetado resuelve la necesidad de múltiples solicitudes de red y el problema de cascada. Los empaquetadores combinan múltiples archivos fuente JavaScript en un único archivo, llamado bundle, que se carga eficientemente en una sola solicitud de red.
Los empaquetadores más usados hoy en día son Webpack, Parcel, Rollup, esbuild y Turbopack.
Webpack (2014): Popular desde 2016, permite transformar archivos fuente mediante “loaders”. Es lento y monohilo, pero altamente configurable.
Rollup (2016): Optimiza el soporte de ES6 Modules y permite “tree shaking”. Genera tamaños de bundle menores y es ligeramente más rápido que Webpack.
Parcel (2018): De configuración baja y listo para usar. Es multihilo y más rápido que Webpack y Rollup. Parcel 2 usa SWC.
esbuild (2020): Escrita en Go, es decenas de veces más rápida que Webpack, Rollup y Parcel. Incluye un transpilador y minificador básicos.
Turbopack (2022): Escrita en Rust, soporta reconstrucciones incrementales y es actualmente desarrollada por Vercel.
Se puede omitir el empaquetado si se tienen pocos módulos o baja latencia de red (e.g. localhost).
Un bundle se compone de múltiples módulos, cada uno con una o más exportaciones. A menudo, un bundle solo utiliza un subconjunto de estas exportaciones. El proceso de “tree shaking” elimina las exportaciones no utilizadas, optimizando el tamaño del bundle y mejorando los tiempos de carga y análisis.
El “tree shaking” se basa en el análisis estático de los archivos fuente, y su eficiencia se ve afectada por dos factores principales:
Sistema de Módulos: Los módulos ES6 tienen exportaciones e importaciones estáticas, mientras que los módulos CommonJS tienen exportaciones e importaciones dinámicas. Los bundlers pueden ser más eficientes al hacer “tree shaking” con módulos ES6.
Efectos Secundarios: La propiedad
sideEffects
depackage.json
declara si un módulo tiene efectos secundarios al importarse. Si existen efectos secundarios, los módulos y exportaciones no utilizados pueden no ser eliminados debido a las limitaciones del análisis estático.
Recursos estáticos
Los recursos estáticos como CSS, imágenes y fuentes se añaden al distribuidor durante la etapa de agrupamiento y pueden ser optimizados en la etapa de minificación.
Antes de Webpack, los recursos estáticos se construían por separado del código fuente como una tarea independiente. La aplicación tenía que referenciar estos recursos mediante su ruta final en el distribuidor, organizándolos cuidadosamente alrededor de una convención de URL (por ejemplo, /assets/css/banner.jpg
y /assets/fonts/Inter.woff2
).
Los “loaders” de Webpack permitieron importar recursos estáticos desde JavaScript, unificando código y recursos estáticos en un solo grafo de dependencias. Durante el agrupamiento, Webpack reemplaza la importación del recurso estático con su ruta final en el distribuidor, permitiendo que los recursos se organicen junto con sus componentes asociados en el código fuente y habilitando nuevos análisis estáticos, como la detección de recursos inexistentes.
Es importante notar que la importación de recursos estáticos no es parte del lenguaje JavaScript y requiere un agrupador configurado para soportar ese tipo de recurso. Afortunadamente, los agrupadores posteriores a Webpack también adoptaron el patrón de “loaders”, haciendo común esta característica.
Minimización#
La minificación resuelve el problema de archivos innecesariamente grandes. Los minificadores reducen el tamaño de los archivos sin afectar su funcionamiento. Para el código JavaScript y los recursos CSS, los minificadores pueden acortar variables, eliminar espacios en blanco y comentarios, eliminar código muerto y optimizar el uso de las características del lenguaje. Para otros recursos estáticos, los minificadores pueden realizar optimización del tamaño del archivo. Los minificadores generalmente se ejecutan en un paquete al final del proceso de construcción.
Los minificadores de JavaScript más utilizados hoy en día son Terser, esbuild y SWC.
Los minificadores de CSS más comunes hoy en día son cssnano, csso y Lightning CSS.
Herramientas de Desarrollo#
El pipeline básico de construcción de frontend descrito anteriormente es suficiente para crear una distribución de producción optimizada. Sin embargo, existen varias clases de herramientas que mejoran la experiencia del desarrollador.
Meta-Frameworks
El espacio de frontend es conocido por la dificultad de elegir los paquetes “correctos” para usar. Los meta-frameworks ofrecen conjuntos de paquetes ya seleccionados que se complementan y permiten paradigmas de aplicación especializados. En el caso de Vite, este proporciona sistemas de construcción tanto de desarrollo como para producción y no fuerza a un paradigma concreto, como en el caso de Angular.
Sourcemaps
Los sourcemaps resuelven el problema de depurar código ilegible en la distribución de producción, al mapear el código distribuido a su ubicación original en el código fuente.
Recarga en Caliente
Los servidores de desarrollo a menudo ofrecen la función de recarga en caliente, que reconstruye automáticamente un nuevo paquete al cambiar el código fuente y recarga el navegador.
¿Por qué un Dev Server?#
Un servidor de desarrollo (Dev Server) es fundamental para mejorar la experiencia de desarrollo. Algunas de las ventajas clave incluyen:
🚀 Mejora en la Experiencia de Desarrollo: Proporciona una experiencia de desarrollo fluida y rápida.
🔄 Carga de Módulos en Tiempo Real: Permite la recarga de módulos sin necesidad de recargar toda la página.
🌐 Soporte ESM (ECMAScript Modules): Facilita el uso de módulos ECMAScript nativos.
🛠️ Entorno de Desarrollo Rápido: Acelera el ciclo de desarrollo con herramientas modernas y eficientes.
Los bundlers, como Vite, agrupan y gestionan múltiples archivos JavaScript, CSS y otros recursos, permitiendo una mejor organización y mantenimiento. Vite utiliza Rollup, en comparación con otra alternativa famosa como esbuild.
Vite reduce el tamaño total de los archivos mediante técnicas como la minificación y la compresión.
Ofrece soporte para funcionalidades modernas con compatibilidad en navegadores antiguos.
Automatiza tareas como la transpilación de TypeScript a JavaScript, la transpilación de SCSS o SASS a CSS, minificación, ofuscación y la generación de rutas de archivos.
Configuración#
Para iniciar un proyecto con Vite:
Crear un nuevo proyecto:
$ npm create vite@latest my-app -- --template vanilla
Instalar las dependencias:
$ cd my-app $ npm install
Ejecutar el servidor de desarrollo:
$ npm run dev
Mover los archivos y las importaciones innecesarias al directorio ./src/
permite mantener una estructura de proyecto limpia y organizada.
Importación de Bibliotecas#
Para importar bibliotecas en Vite, se usa npm install
:
$ npm install lodash
Luego, en tu archivo JavaScript:
import _ from "lodash";
_.join([1, 2]);
Vite utiliza una técnica llamada “bare module imports”, que funciona también en TypeScript y Angular, lo cual elimina la necesidad de importar desde ../node_modules…
y de añadir .js
al final del archivo.
Importar CSS e Imágenes#
Para importar CSS y trabajar con imágenes en Vite:
import './style.css';
import javascriptLogo from './javascript.svg';
import viteLogo from '/vite.svg';
const logoHtml = `<img src="${viteLogo}" class="logo" alt="Vite logo" />`;
document.body.innerHTML += logoHtml;
El CSS se incluye directamente en el bundle, mientras que las imágenes no se importan directamente, pero sus nombres son modificados en el build. Trabajar con variables permite modificar dinámicamente el nombre de las imágenes.
Vite también permite importar JSON y otros tipos de archivos.
Vite + Bootstrap#
Para usar Bootstrap con Vite, sigue los pasos proporcionados en la documentación oficial de Bootstrap. Puede que no necesites seguir todos los pasos si ya tienes un proyecto en marcha.
Build for Production#
Para crear una versión de producción del proyecto:
$ vite build
Si necesitas desplegar tu aplicación en una subcarpeta:
$ vite build --base=/my/public/path/
Lecturas: https://sunsetglow.net/posts/frontend-build-systems.html