Tamaños que no importan: tree-shakeables
He estado aprovechando los tiempos muertos (a.k.a todo el día) para desarrollar una biblioteca que permita señalar elementos en una aplicación web de manera sencilla y de paso hacer experimentos para arrojar algo de luz sobre las herramientas de tree shaking actuales (spoiler: son lo mismo que hace 8 años pero con esteroides).
Aunque lo que comenzó como toy/pet project para un caso de uso sencillo, al final se ha convertido en un lanzamiento oficial con su documentación y todo. Durante el desarrollo me he obsesionado con el tamaño del paquete 🌞. No pretendo ser casposo, esa frase va a seguir sonando turbia por mucho que la modifique.
Esperaba que Point it out no pasara de los 5 KB gzipped, lo que para una biblioteca que tendrá capacidad para generar distintos tipos de imágenes SVG procedimentalmente es más que aceptable, pero siempre hay margen para mejorar.
🍂Tree Shaking
Estaba acordándome de la vieja Lodash. Era Es una famosa biblioteca que brinda utilidades para aplicar programación funcional a muchas partes de JavaScript.
Algunas cositas siguen siendo muy útiles, como las funciones de throttling y debouncing, pero, ¿instalarías tremenda biblioteca con un paquete de unos 25 KB (min + gzipped) por unas pocas de sus decenas de características (muchas ya obsoletas)?
Para el desarrollador no supone gran problema descargar todo un paquete más, es algo que haces una vez. Lo que realmente preocupa es que cada usuario tuviera que descargar eso, perjudicando la carga del sitio.
Antes no era tan problemático porque estas bibliotecas eran muy comunes y porque se distribuían principalmente por CDN. Con muy alta probabilidad, algún usuario que pasara por tu sitio ya habría pasado anteriormente por otro que usara la misma biblioteca, teniéndola en caché de su navegador.
Pero ahora tampoco es un problema, porque tenemos bibliotecas tree-shakeables (en realidad desde hace como 9 años, pero ahora es cuando más compatibilidad con los módulos ES tenemos). De hecho Lodash tiene una versión tree-shakeable en ESM.
En qué consiste el tree shaking y los tree-shakeables
Cuando generas el bundle de tu web, aplicación, librería o lo que sea que estés bundleando, los bundlers modernos tiran de herramientas como rollup para minificar el resultado y de paso realizar “dead code elimination”.
La “dead code elimination” es autoexplicativa. Elimina el código que nunca es accedido por evaluación de referencias y otras técnicas. Nada nuevo, esta es una de las responsabilidades típicas de los optimizadores en compiladores. Lo que es relativamente nuevo (en el ecosistema web), es la cultura de construir bibliotecas modulares orientadas a esto.
En el mundo de JavaScript y aplicado especialmente a dependencias externas, es decir, a las bibliotecas que utilizas, el dead code elimination se conoce como “tree shaking”. Es un concepto bastante gráfico si piensas en un árbol de dependencias que sacudes y del que caen todas las hojas y ramas muertas. Por tanto se extrae solo aquellas partes necesarias en lugar de mandarle al usuario todo el tocho.
Diseño, DX y Tree Shaking
No todos los módulos ES son automáticamente tree-shakeables, deben separar muy bien sus características y aplicar correctamente SRP para que tenga sentido.
Creí que mi biblioteca no sería apta para tree-shaking. La idea era hacer una única función punto de entrada muy bien adaptada tirando de tipado e ideas de DX opinionadas a más no poder. En un IDE con capacidad para manejar un language-server de TypeScript (casi todos hoy día), al empezar a escribir el primer parámetro de la función create (en la versión 0.1.11 al menos), una string, te daría dos opciones:
Esto ya me permite un mecanismo que me evite crear dos funciones por separado como createArrow y createRect. A lo mejor te preguntas cosas como, “¿Y eso qué tiene de malo?”, “¿no sería lo correcto cumpliendo con el principio de responsabilidad única (SRP)?“.
Internamente no se está rompiendo el SRP, existen las dos funciones (en realidad peor, existen 2 clases distintas) con implementaciones bien separadas. Una cosa es la API y otra la implementación. A diferencia de la UX, la DX puede ser algo más opinionada y relacionada al estilo de un equipo. Me gusta que solo exista una sola función para crear y que sea lo más guiada posible. Pocas funciones muy configurables con muy pocas opciones obligatorias.
El segundo parámetro de la función son sus opciones. Tanto arrow como rect son los dos primeros tipos de punteros disponibles en la librería, y ambos son SVGs, por tanto tienen opciones comunes por ser punteros y por ser SVGs:
Vemos que la única opción obligatoria es target, por eso aparece la primera, las otras están en orden alfabético. Si en lugar de ‘rect’ hubiéramos escrito ‘arrow’, las opciones serían:
Nótese que han desaparecido las opciones de rect y han aparecido otras exclusivas de arrow. Solo por haber cambiado una string en el primer parámetro.
Esto no es un caso avanzado de TypeScript, en realidad es una definición muy simple de momento:
interface PointerOptions {
rect: CommonOptions & SVGOptions & RectOptions
arrow: CommonOptions & SVGOptions & ArrowOptions
}
Espera… ¿y el tree shaking cómo se lleva con esto?
Y ahí es a donde quería llegar. Creí que no tendría más remedio que pasar por el aro y volver a la tradición.
En algún punto de mi librería hay arcaicos if que evalúan la string del tipo, una comparación cutre para decidir qué clase usar. ¿Serían las herramientas de eliminación de código tan avanzadas como para detectar eso?, si no ejecutan el código, y al fin y al cabo TypeScript “no existe”.
Pues resulta que he creado un proyecto de JavaScript vanilla con Vite, instalado Point it out, creado un rect, hecho una build, y en el .js bundleado no haber ni rastro de arrows, ni de sus propiedades ni su clase ni na’. Repito el proceso con solo arrows y veo que ni rastro de rect.
Así que funciona asombrosamente bien, parece que esto es más avanzado de lo que creía. No tengo ni idea de qué estrategia seguirá, pero de momento el poder continuar con mi diseño sin consecuencias técnicas es una muy buena noticia.