Por qué compilar vence a un runtime: sin VDOM ni diffing
La economía que separa a Solid de React. Por qué un framework de runtime con virtual DOM paga en cada actualización un coste proporcional al tamaño de la vista —reconstruir el árbol, diferenciarlo y parchear—, mientras que Solid, al mover ese análisis al build, paga un coste proporcional al tamaño del cambio. Por qué desaparecen `memo`, `useMemo` y `useCallback`, cómo se compara con Svelte, Marko y el React Compiler, y por qué compilar no es un truco de rendimiento sino un cambio de contrato.
El coste de un runtime con VDOM
En React, cuando un estado cambia, el componente se vuelve a ejecutar: reconstruye su árbol de elementos con createElement, lo compara con el árbol anterior para hallar las diferencias, calcula el conjunto mínimo de parches y los aplica al DOM. El virtual DOM es una apuesta razonable —recomponer y comparar en JavaScript suele ser más barato que tocar el DOM a ciegas—, pero su coste crece con el tamaño de la vista: aunque solo cambie un dato, hay que recorrer y recomparar la porción del árbol afectada para averiguar qué mover. Todo el aparato de memo, useMemo, useCallback y las listas de dependencias nace para podar ese trabajo, y su gestión recae en ti.
// React: cada cambio re-ejecuta el componente y reconcilia su arbolfunction Lista({ items }) { const total = useMemo(() => items.reduce(suma, 0), [items]); // poda manual return <p>{total}</p>; // recalculado y rediferenciado en cada render}Ese useMemo es sintomático: es trabajo que existe únicamente para evitar otro trabajo. La lista de dependencias [items] es una promesa que tú firmas y que el runtime no verifica; si te equivocas —olvidas una dependencia o incluyes una de más— el bug es silencioso. El VDOM compra un modelo mental cómodo, la UI como función pura del estado, a cambio de un impuesto de recomputación que tú administras a mano.
Y el impuesto no se paga solo al actualizar. Cada render reconstruye el árbol de elementos del componente aunque el resultado sea idéntico, y ese árbol efímero es basura que el recolector barrerá después. Por eso las apps grandes acaban salpicadas de memo, useMemo y useCallback no por elegancia sino por defensa. La consecuencia cultural es enorme: en React, parte del oficio es aprender a no recomputar; en Solid ese oficio no existe, porque no hay recomputación de la que defenderse.
// React defensivo: cada pieza envuelta para no recomputarconst filas = useMemo(() => items.map(fila), [items]);const onPick = useCallback((id) => elegir(id), []);// ...y un React.memo en cada hijo para cortar la propagacion del renderNada de ese andamiaje tiene equivalente en Solid, y su ausencia no es una carencia: es la señal de que el problema que resuelve no llega a existir.
El coste de Solid: proporcional al cambio
Solid rechaza la premisa. No hay componente que se re-ejecute ni árbol que comparar: cuando un signal muta, despierta solo a sus suscriptores directos —los efectos e insert que lo leyeron— y a nadie más. El coste de una actualización crece con el tamaño del cambio, no con el de la vista. El grafo ya sabe, por construcción en tiempo de compilación, quién depende de qué; no hay nada que descubrir en caliente.
La consecuencia práctica es que una vista puede ser enorme y las actualizaciones seguir siendo diminutas. Da igual que la página tenga diez nodos o diez mil: un signal que muta toca exactamente lo que depende de él. Y lo que en React exigía un useMemo, en Solid es simplemente una función que se rastrea sola.
// Solid: derivacion sin memoria manual; el grafo la rastrea exactofunction Lista(props: { items: () => number[] }) { const total = () => props.items().reduce(suma, 0); // se relee solo si items cambia return <p>{total()}</p>;}No hay lista de dependencias que firmar ni que equivocar: total se suscribe a lo que lee, exactamente y sin tu intervención. No es que Solid compare más rápido; es que no compara.
Ese mismo componente, compilado, no contiene ni rastro de un reconciliador: ni createElement, ni árbol previo que guardar, ni bucle de comparación.
// Solid: el <p> reactivo, sin nada que reconciliarconst _tmpl$ = _$template(`<p>`);const _el$ = _tmpl$();_$insert(_el$, total); // un efecto de render envuelve total y reescribe el textoCuando total cambia, el efecto que lo envuelve reescribe un único nodo de texto. No se vuelve a ejecutar el componente, no se reconstruye el p, no se compara nada con nada. El grafo ya sabía, desde el build, que ese texto —y solo ese texto— dependía de total.
La misma lógica escala a lo grande. En una lista renderizada con For, cambiar un elemento no vuelve a ejecutar la lista ni recorre los demás: el efecto de esa fila —y solo esa fila— reescribe su nodo. Donde un runtime con VDOM recorrería la colección para hallar la fila cambiada, Solid ya tiene, desde que la creó, un puntero directo a ella.
<For each={items()}>{(item) => <Fila dato={item} />}</For>Cada Fila es su propio ámbito reactivo con su propio efecto; el For solo crea, mueve o destruye filas cuando la lista cambia de forma, y no toca en absoluto las que permanecen.
El trabajo se hace en el build
Aquí está la idea que lo unifica todo. Un framework de runtime debe descubrir, en cada actualización, qué cambió y dónde; un compilador lo descubre una vez, en el build, y emite código que ya lleva esa respuesta cableada. Pagas el análisis en tiempo de compilación —una máquina, una vez— y tus usuarios reciben instrucciones especializadas que no tienen que razonar sobre nada. Por eso el runtime de Solid es minúsculo y, a diferencia del coste de reconciliar, no crece con la complejidad de la app: la complejidad se resolvió antes de enviar el código.
Esto se refleja en el bundle. El runtime reactivo de Solid es diminuto y, lo más importante, no crece con el número de componentes: añadir pantallas suma plantillas y efectos, no capas de framework. Un runtime con VDOM, en cambio, carga siempre con su reconciliador como coste fijo, y las herramientas para optimizarlo son código que también hay que descargar y ejecutar. Menos framework en tiempo de ejecución significa menos JavaScript que parsear en el arranque, justo cuando el usuario más lo nota.
Hay un matiz honesto: compilar no es gratis. El coste se desplaza a tu pipeline —el build tarda algo más y el output es específico de cada plantilla, no reutilizable como un runtime genérico—. Pero es un intercambio casi siempre favorable, porque el build lo paga una máquina una vez y el runtime lo pagan todos los usuarios en cada interacción. Mover trabajo del segundo grupo al primero es una de las mejores palancas de rendimiento que existen.
No es el único que compila
Solid no inventó compilar la vista; sí lleva la reactividad de grano fino hasta el final. Conviene situarlo entre sus vecinos para no confundir “compilar” con “ser como Solid”.
- Svelte compila desde siempre, pero durante años su reactividad fue a nivel de componente, con marcado de sucio y actualización de instancia. Svelte 5, con runes, adoptó signals y se acercó al modelo de grano fino de Solid: dos caminos que convergen.
- Marko es un compilador orientado a streaming y resumabilidad, con una partición estático/dinámico aún más agresiva pensada para el servidor.
- El React Compiler —antes React Forget— no elimina el VDOM: inserta automáticamente la memoización que antes escribías a mano, para que dejes de colocar
useMemoyuseCallback. Optimiza el runtime; no lo sustituye. React sigue reconciliando; solo lo hace podando mejor.
La postura de Solid es distinta en el fondo, no en el grado: grano fino desde el primer día, compilador más un runtime reactivo diminuto, y ningún árbol virtual en ninguna parte. Cuando compares frameworks, la pregunta útil no es “compila o no compila”, sino “dónde vive el trabajo de saber qué cambió”: en cada frame del usuario, o una sola vez en tu máquina de build.
Dicho de otro modo, todos estos frameworks reparten el mismo trabajo entre dos momentos —build y runtime— y se distinguen por cuánto empujan hacia el build y cuánto grano de reactividad conservan. Solid empuja casi todo al build y conserva el grano más fino posible; React empuja poco y compensa con un runtime sofisticado; Svelte y Marko se sitúan, cada uno a su manera, del lado del compilador.