Cómo mejorar la experiencia de desarrollo frontend sin un paquete

Un vistazo a cómo puede usar módulos ES6 y Service Workers para iniciar una aplicación web sin Webpack o Rollup.

Ejemplo típico de un desarrollador esperando que el paquete esté listo.

Introducción

También disponible en italiano

La especificación ECMAScript 2015 (ES6) trajo JavaScript y el desarrollo web a una nueva era, hecha de sintaxis limpias, mejor andamiaje de archivos fuente y un conjunto de herramientas de desarrollador disponibles en otros contextos de programación, como análisis de código estático, sistemas de dependencias, autocompletado y más .

Todo esto tuvo un precio: el lanzamiento de una aplicación web en el navegador ahora puede requerir cientos de módulos de nodos, un observador para detectar cambios en los archivos y reconstrucciones de fuentes exhaustivas. Unos pocos segundos perdidos pueden convertirse fácilmente en horas en una semana, una pérdida de espacio en disco, consumo extremo de RAM y CPU y, para computadoras de rendimiento lento, los ventiladores giran como motores aeronáuticos.

Caso de estudio

Como desarrollador, me gustaría crear una aplicación clásica de Lista de tareas, usando una biblioteca de componentes con soporte JSX y ejecutarla en el navegador, sin tener que iniciar un paquete, un transpilador y un observador.

Supongamos que a todos nos encanta Preact (como debería ser): obtener la plantilla (y sus dependencias) solo, significa descargar ~ 1.500 módulos de nodos, ~ 26.000 archivos, y requiere un espacio total en disco de aproximadamente 200 MB. La mayoría de esas dependencias se utilizan para ejecutar un servidor web creado por Webpack.

Pero no queremos nada de esto, por lo que solo debemos confiar en las características de un navegador moderno.

Cómo cargar una aplicación ES6 / 7 / X en el navegador sin un paquete

Desde Chrome 62, Edge 16, Safari 11 y Firefox 54, es posible importar un módulo ES6 en el navegador:

Cuando el analizador HTML cumple con esta etiqueta, obtiene el archivo fuente especificado y resuelve recursivamente todas las declaraciones de importación y exportación.

Aunque esta nueva característica es maravillosa, no es suficiente para iniciar una aplicación web moderna y compleja. Los navegadores solo pueden resolver dependencias relativas y no tienen información sobre las dependencias de NPM. Además, el uso de JSX arrojará múltiples errores de sintaxis, porque no es un estándar del lenguaje JavaScript.

Trabajadores de servicio al rescate

Cuando todo parece perdido, los trabajadores del servicio vienen en nuestra ayuda. Esos trabajadores especiales, una vez registrados, pueden interceptar solicitudes de red y manejar su respuesta.

Entonces, podemos usar SW para:

  • interceptar solicitudes de JavaScript;
  • buscar archivos;
  • busque sentencias de importación no relativa y vuelva a asignarlas a la carpeta node_modules;
  • detectar la sintaxis JSX y transpilar todas las expresiones JSX a JavaScript;
  • devolver archivos modificados al hilo principal.

En este punto, el navegador debería poder manejar los archivos JavaScript y resolver sus dependencias.

Más sobre Service Workers en MDN: Uso de Service Workers

Desarrollando desencadenado

Unchained es una prueba de concepto alojada en GitHub que pone en práctica esta idea. Al igual que los agrupadores y transpiladores que ya utilizamos, fue diseñado para dividir el proceso en pasos y es conectable.

Proporciona algunos ayudantes útiles para registrar Service Workers, un polyfill para admitir la importación dinámica y su núcleo (similar a la API Rollup) puede resolver las importaciones de archivos JavaScript a través de los siguientes pasos:

  • Transformación: para cambiar el código fuente y la transpilación de sintaxis no estándar;
  • Resolución: encontrar y resolver declaraciones de importación;
  • Finalización: devuelve el código generado.

Todas las actualizaciones del paquete final se realizan con Babel Standalone, una distribución de Babel que se ejecuta dentro del navegador y permite que los complementos manejen el Árbol de sintaxis abstracta (AST) del archivo directamente, rastreando cambios y generando el mapa fuente final.

Los siguientes complementos de Unchained ya están disponibles (y bastante básicos en este momento):

  • common: reemplaza las expresiones CommonJS, como require andmodule.exports, con declaraciones ES6;
  • babel: se comunica con Babel Standalone y sus complementos para transpilar sintaxis no estándar como JSX, Flow o Typecript (!);
  • texto: convierte archivos de texto en módulos ES6;
  • json: convierte archivos JSON en módulos ES6;
  • env: realiza la inyección de variables de entorno (pasadas usando un Objeto JavaScript, ya que los navegadores no tienen acceso directo a las variables env reales);
  • resolver: es una implementación parcial del algoritmo de resolución de nodos.

Además, Unchained usa la interfaz de caché del navegador para omitir archivos ya resueltos. Usando el encabezado ETag, también puede detectar cambios, por lo que las recargas de página y las inyecciones de código son muy rápidas.

Haz feliz al desarrollador

Volviendo a nuestro caso, podemos pensar en una solución y, para implementarla, estamos utilizando el ejemplo simple que se muestra en la página de inicio de preactjs.com.

Configuración del proyecto

Al principio, solo necesitamos dos dependencias:

npm init -y
npm install preact unchained-js
# 2 módulos, 65 artículos, <2MB

y los siguientes archivos:

  • index.html: donde se registra el Service Worker y se importan los archivos de la aplicación
  • sw.js: el Service Worker que se registrará, utiliza la biblioteca Unchained para transpilar el código fuente
  • index.js: el archivo JavaScript principal
  • todolist.component.js: la definición de clase de componente TodoList.

Ahora, solo ejecute un servidor local (aquí instalado globalmente):

npm install -g http-server
servidor http.

¡y hecho!

En la consola, Unchained registra todos los archivos importados en el lanzamiento: más tarde, usará el caché para las recargas que siguen.

En cada cambio de archivo, p. si editamos la etiqueta del botón en todolist.component.js y la guardamos, solo el navegador volverá a cargar el archivo.

Un proyecto Git de este ejemplo está disponible aquí.

Conclusiones

A pesar de que el soporte del navegador está limitado a Chrome y Firefox (en cuanto a este, no está disponible de forma predeterminada, pero debe activar el indicador dom.moduleScripts.enabled), creo que este enfoque puede simplificar la estructura del proyecto y su configuración:

  • reduce considerablemente la cantidad de dependencias para instalar;
  • la transpilación no bloquea el hilo principal de la interfaz de usuario, porque todo sucede en el contexto del trabajador del servicio;
  • después del primer lanzamiento, las actualizaciones del código fuente son realmente rápidas;
  • la división de código con la importación dinámica () simplemente funciona;
  • soporte de mapas de origen.

Pero también hay contras:

  • el primer lanzamiento no es eficaz (debido a la falta de acceso directo al sistema de archivos y la necesidad de crear un AST para cada archivo);
  • falta el movimiento de árboles, una característica disponible en Rollup y Webpack 4.

Prueba real

Las cosas siempre son fáciles en una aplicación Lista de tareas, pero ¿qué pasa con los ejemplos del mundo real? Reemplacé Rollup con Unchained en un proyecto complejo en el que estoy trabajando, que presenta muchas dependencias como jQuery, Moment, CKEditor y una biblioteca de componentes, con los siguientes resultados:

Comparación realizada con una MacBook Pro (Retina, 15 pulgadas, mediados de 2015) y Chrome 63.0.3239.84

Futuro

Aunque Unchained cubre todos los aspectos de este simple estudio, me gustaría profundizar en algunos temas y mejorar los resultados:

  • acelerar la primera ejecución utilizando solo transformaciones AST (deshabilitado porque en este momento hay un problema al generar mapas fuente);
  • verificar si es posible integrar Turbo una vez disponible, para mejorar el proceso de resolución de rutas no relativas;
  • extender la compatibilidad a los navegadores que no admiten módulos de Service Workers o ES6;
  • extender el soporte al entorno del nodo para obtener paquetes de aplicaciones que sean 100% compatibles con la vista previa en el navegador;
  • blob (para cargar imágenes y fuentes) y complementos postCSS.
Gracias especiales
Muchísimas gracias a xho y SteRosanelli, por su paciencia al revisar este artículo.