Angelitedocs ESEN

Inicio › Comparar

Análisis frente a los demás

Qué tienen ellos que nosotros no, qué tenemos nosotros que ellos no, y en qué orden conviene cerrar la brecha. La tabla completa está en comparativa (37 filas, con la fuente de cada dato).

Qué tienen ellos que nosotros no, qué tenemos nosotros que ellos no, y en qué orden conviene cerrar la brecha. La tabla completa está en comparativa (37 filas, con la fuente de cada dato).

Cómo se comprobó#

Cada afirmación lleva una fuente:

  • V — comprobado en esta sesión: ejecutando el código o leyendo el paquete instalado (React 19.3.1, Preact 10.29.8, Vue 3.5.44, Angular 20.3). Las pruebas de seguridad se pueden repetir con cd benchmarks && npm install && npm run probe:security.
  • D — documentación oficial.
  • G — conocimiento general que no se comprobó aquí. Revísalo antes de citarlo.

De las 37 filas de la tabla: 16 son V, 2 son D y 19 son G. Las filas de seguridad son casi todas V.

Resumen#

  1. Estado: los seis huecos de seguridad que encontramos están cerrados (ver la sección 1), con un test real para cada uno y para su variante precompilada. AngeLite pasa de peor a mejor del grupo en las pruebas de ataque que ejecutamos.
  2. Donde ganamos es tamaño, memoria y velocidad de listas (ver el benchmark). Somos además el único con un modo «sin build» pequeño: ~10 KB gzip frente a 63,7 KB de Vue con su compilador, y sin eval.
  3. Donde perdemos es todo lo que rodea al núcleo: tipos, SSR, router, formularios, herramientas, comunidad. La tabla nos da 19,5 de 38 frente a 24,5 (Preact), 27 (Vue), 28,5 (React) y 33 (Angular). No es un ranking, pero la brecha es real.

1. Seguridad#

Lo que comprobamos que hace cada uno#

Se repite con cd benchmarks && npm install && npm run probe:security.

PruebaAngeLite (antes)AngeLite (ahora)PreactVueReactAngular
Texto escapado por defecto✓✓✓✓✓✓
href con javascript:pasabaunsafe: + avisopasapasalo neutralizalo sanea
onclick como textocreaba el atributoerror de compilaciónno lo creacrea el atributono lo crealo prohíbe
HTML en innerHTMLsin sanearsolo con trustHtml(); si no, textosin sanearsin sanearsin sanearsanea
Expresiones llegan a windowsí, vía $elno, por ningún caminon/a (JSX)non/a (JSX)no
Funciona sin evalsísísísolo la build runtime-onlysísolo AOT; el JIT usa new Function
Trusted Typesnosí (política angelite)nouna referencianointegrado
Estilos bajo CSP estricta<style> sin noncehojas construibles; fallback con noncen/an/an/anonce

Vue y Preact siguen sin cerrar las URL; solo React y Angular lo hacen. Con estos cambios AngeLite cierra todo lo que cierran ellos, más lo que nadie más hace (compilación que rechaza bindings peligrosos, valor-guardia contra window).

Cómo se cerró cada hueco#

#HuecoCierreTest
1[href] / href="{{ }}" aceptaban javascript:Lista de esquemas (http, https, mailto, tel, sms, ftp, relativas) en href, src, action, formaction, data y xlink:href; ignora los caracteres que los navegadores descartan dentro del esquema (java\tscript:); el resto pasa a unsafe:<url> con aviso. trustUrl() como salida explícita.matriz de 12 URLs peligrosas y 13 seguras en 7 contextos
2[attr.onclick] creaba un manejadorError de compilación para [on*], [attr.on*] y on*="{{ }}", en el compilador del navegador y en el AOT5 variantes, también con mayúsculas
3[innerHTML] sin sanearSolo se renderiza si está envuelto en trustHtml(); cualquier otro valor se inserta como texto y avisa3 tests
4[srcdoc] aceptadoError de compilación (y [outerHTML])3 tests
5Una plantilla llegaba a window con $el.ownerDocument.defaultView.fn()Filtro por valor: el objeto global y los documentos nunca se devuelven a una plantilla, vengan de donde vengan ($el.ownerDocument, $event.view, getRootNode(), composedPath()[n], un campo del componente). Además, métodos del DOM que convierten texto en marcado o atributos (insertAdjacentHTML, setAttribute, write…) quedan bloqueados por nombre.8 caminos de ataque + eventos
6El paquete UI inyectaba <style> sin nonceadoptedStyleSheets (no necesita nonce); si no existen, <style> con el nonce de <meta name="csp-nonce">3 tests

Además: bindings e interpolación en <script>, <style>, <base>, <link> y <meta> son error de compilación; Trusted Types con una única política angelite para las plantillas y trustHtml(); SECURITY.md con el modelo de confianza y cómo reportar; CodeQL con consultas ampliadas, Dependabot y npm audit en el CI.

Lo que queda (y es honesto)#

  • El sandbox de expresiones no es un límite de seguridad. Una plantilla sigue pudiendo llamar métodos de su componente y de los elementos que alcance. Es defensa en profundidad: nunca se compilan plantillas con datos de usuario (Vue dice lo mismo). En producción, precompila con tools/aot.mjs: no se compila nada en el navegador.
  • No incluimos un sanitizador de HTML. Angular trae uno; nosotros exigimos trustHtml() y recomendamos DOMPurify antes de envolver. Incluir un sanitizador propio costaría varios KB y es fácil equivocarse.
  • Sin revisión externa ni historial. Los demás llevan años de auditorías; nosotros, no.
  • El framework antiguo (src/angelite.js y sus carpetas) no está endurecido y está deprecado. Usa lite.

Qué tienen ellos que nosotros no (seguridad)#

  • Angular: un sanitizador de HTML y URL por contexto integrado, años de revisión de seguridad.
  • Todos: procesos de divulgación consolidados y una comunidad que encuentra y corrige fallos.

2. Velocidad y rendimiento#

Medido tras la Ola 1 y la Ola 2 (5 rondas × 15 iteraciones por operación, arranque con 41 cargas intercaladas; contenedor compartido, sin pintado; esta máquina fue ~35 % más lenta que la de la medición anterior, así que los milisegundos absolutos no son comparables, sí las posiciones). Con la regla «a menos del 10 % es un empate»:

AngeLite precompiladoMejor de los demás
Tamaño de la app (gzip)6,1 KB (el más pequeño; las funciones nuevas costaron 0,2 KB)Preact 6,2 KB
Arranque23,1 ms (rangos intercuartílicos solapados: empate técnico)Preact 21,3 ms
Memoria con 10.000 filas6,6 MBAngular 11,8 MB
12 mediciones en totalmediana más baja en 7; empate dentro del 10 % en 4 (arranque, replace1k, swap, create10k); por detrás en remove (Preact, +16 % en esta corrida)—

swap y remove varían de una corrida a otra: dos repeticiones dedicadas (5 × 15) dieron 4,70 ms frente a 4,90 de Preact en ambas, y otra sin el gancho de animaciones 4,75 frente a 4,70. Lo honesto es leerlas como empate con Preact y Angular. La puntuación global (media geométrica del tiempo relativo al mejor) es 1,03× para AngeLite precompilado; React queda en 1,55×. Mejoras de esta ronda que se midieron: el anfitrión raíz con display: contents (−1,5 ms tras el montaje) y una vía rápida en @for cuando la lista mantiene las mismas claves en el mismo orden (update10th pasó de ~9,8 a ~7,9 ms).

Lo que no tenemos:

FaltaQuién lo tieneQué aportaría
Optimizaciones de compilación (subárboles estáticos elevados, constantes plegadas, banderas por nodo)Vue (patch flags + hoisting), React Compiler, Angular AOTMenos trabajo por actualización; hoy el AOT precompila pero no optimiza
Renderizado concurrente y transicionesReactInterfaz fluida con actualizaciones pesadas. No lo recomendamos copiar: la complejidad es enorme para un framework ligero
SSR e hidrataciónReact, Vue, Angular (incluida la incremental), Preact (paquete aparte)Primer pintado y SEO. Ver la ventaja arquitectónica abajo
Carga diferida declarativa (@defer, lazy)TodosDividir el código
Detección de regresiones de rendimiento en CI—Hoy el benchmark es manual: una PR podría empeorar la velocidad sin que nadie lo vea

Límites propios que conviene conocer (verificados en el código):

  • Un efecto por vista, no por binding. Una señal que cambia re-evalúa todos los bindings de esa vista (solo escribe en el DOM si el valor cambió). En filas con pocos bindings es lo más barato; en un componente con cientos de bindings y una señal que cambia muchas veces por segundo puede costar. Vue y Solid son más finos.
  • Listas inmutables. Se compara identidad: mutar un array en el sitio y volver a asignar el mismo no actualiza.
  • Ruido de la medición. Los números salen de un contenedor compartido y sin pintado; las diferencias menores del 10 % no son concluyentes.

Ideas para seguir mejorando (en este orden): elevar subárboles estáticos en el AOT, scope plano en las filas (menos objetos por fila), propiedades internas con nombre reducido (~0,5 KB), y arranque más rápido (hoy empatamos con Preact con 28 ms).

3. Arquitectura y estructura (mirándolo como arquitecto)#

Puntos débiles reales del diseño actual:

  1. Estado global de módulo. El registro de pipes, el mapa de servicios de inject(), el router, el conjunto de eventos delegados y el enganche del router son singletons. No se puede tener dos aplicaciones aisladas en la misma página, ni renderizar en servidor sin mezclar peticiones, ni ejecutar tests en paralelo con garantías. Dirección: un objeto app (createApp()) que contenga registros, inyector y router.
  2. Acoplamiento al DOM global. El runtime llama a document directamente. Eso impide SSR y otros renderizadores. Dirección: abstraer las pocas operaciones DOM (crear plantilla, insertar, mover, quitar).
  3. Dos bases de código. El framework antiguo (~7.000 líneas, src/angelite.js) convive con AngeLite. Duplica conceptos y mantenimiento, y los tests del antiguo son humo y lógica de formularios. Dirección: congelarlo, marcarlo como deprecado y portar a AngeLite lo que valga (formularios reactivos, guards y resolvers del router).
  4. Ámbito por cadena de prototipos y claves de texto. Cada fila crea un objeto con prototipo del padre y las expresiones buscan por nombre. Es rápido y compacto, pero no se puede analizar estáticamente (sin tipos, sin comprobación de plantillas) y $event / $index pueden colisionar con campos del usuario.
  5. Eventos delegados en document. Es lo que da la memoria baja, pero no funciona dentro de Shadow DOM ni iframes y event.currentTarget no es el elemento. Dirección: delegar en la raíz de cada app (no en document).
  6. Propiedades añadidas a los elementos ($s, $click…) pueden colisionar con otras librerías. Dirección: un WeakMap o prefijo con símbolo, si la medición lo permite.
  7. Sin contrato de versionado ni de API estable, sin tipos y sin modo desarrollo con avisos. Es lo que más frena su adopción, más que cualquier medida de rendimiento.

Qué tienen ellos aquí que nosotros no (la Ola 1 cerró lo marcado con ✓; ver CHANGELOG.md):

  • ✓ inyección de dependencias con ámbitos (providers), ✓ router con rutas anidadas, guards y carga diferida, ✓ TypeScript (.d.ts comprobados con tsc), ✓ límites de error (ngOnError; no capturan constructor ni ngOnInit), ✓ portales, ✓ slots con nombre, ✓ referencias a hijos (viewChild), ✓ utilidades de testing, ✓ avisos de desarrollo, ◐ comprobación de plantillas (elementos, pipes y nombres; no tipos).
  • La Ola 2 añadió, como módulos opcionales: formularios reactivos, cliente HTTP con interceptores y resource(), i18n, animaciones de entrada y salida, SSR con hydrate(), DevTools básicas, modo history con query y resolvers, plugin de Vite y angelite-check. Cada uno es más pequeño y con menos funciones que el de Angular (la tabla lo dice).
  • Sigue sin existir: hidratación que adopte los nodos del servidor, servicio de lenguaje para el editor (autocompletado), extensión de DevTools para el navegador, validadores asíncronos, createApp(), renderizado concurrente (decisión: no), y madurez / comunidad (solo se arreglan con tiempo y usuarios).

Dónde lo tienen los demás importa: casi nada de esto viene en el núcleo de React, Preact o Vue (router, formularios, HTTP, i18n, animaciones y testing son paquetes aparte, oficiales o de terceros). Lo que sí traen dentro Vue, React y Angular es lo estructural (portales, límites de error, carga diferida, slots, DI), que es justo lo que cerramos en el propio paquete. La tabla de la comparativa marca para cada fila si es integrado, módulo opcional, paquete oficial o de terceros.

Una ventaja arquitectónica que sí tenemos#

El blueprint es datos: el AOT ya serializa la lista de instrucciones por posición de nodo. Eso hace que SSR e hidratación sean un paso natural: renderizar a texto evaluando las instrucciones sin DOM, y en el cliente recorrer el DOM existente con las mismas instrucciones (la numeración por posición ya existe). Pocos frameworks tienen esa base; hay que aprovecharla antes de añadir más funciones.

4. Lo que tenemos y ellos no (o no tanto)#

Todo verificado:

  • Modo «sin build» pequeño: 10,1 KB gzip con compilador incluido, frente a 63,7 KB de la build con compilador de Vue. Angular no tiene equivalente utilizable (su compilador JIT es grande y usa new Function).
  • Sin eval ni new Function en ningún bundle, también en el modo sin build. La build con compilador de Vue y el JIT de Angular sí los usan, y requieren unsafe-eval en CSP.
  • Cero dependencias de runtime (Vue: 5, Angular: tslib más peers, React: scheduler).
  • Memoria: 6,4 MB con 10.000 filas frente a 11,8 – 17,8 MB.
  • Un mismo código en dos modos (navegador y precompilado) con un compilador de plantillas con backend intercambiable.
  • Sintaxis de Angular (@if, @for, señales) sin Zone.js ni CLI.
  • Bloqueo de constructor, __proto__ y prototype en las expresiones.

Hay que ser honestos con el peso real de esto: casi todo son ventajas de tamaño, memoria y simplicidad. En funciones, ecosistema y madurez estamos muy por detrás.

5. Hoja de ruta propuesta#

Esfuerzo: S = horas, M = días, L = semanas.

P0 — seguridad y confianza (S–M):

  1. Sanear URL en bindings y atributos interpolados (hueco 1). S
  2. Rechazar on* y srcdoc (huecos 2 y 4). S
  3. [innerHTML] solo con trustHtml() (hueco 3). S
  4. Denylist de globales en el evaluador y documentación sobre plantillas no confiables (hueco 5). S
  5. SECURITY.md. S
  6. adoptedStyleSheets en el paquete UI (hueco 6). S
  7. Política opcional de Trusted Types para el compilador del navegador. M

Al arreglar cada uno, quitar el todo de su test en tests/security.test.mjs.

P1 — que sea adoptable (M): (✓ = hecho en la Ola 1)

  1. ✓ Tipos .d.ts, escritos a mano y comprobados con tsc en el CI.
  2. ✓ Modo desarrollo con avisos (enableDevWarnings(): input inexistente, claves duplicadas, elemento desconocido, módulo sin importar) y ✓ gancho de errores por componente (ngOnError). Falta un gancho global.
  3. Router: ✓ rutas anidadas, ✓ guards, ✓ carga diferida y ✓ redirectTo. Faltan resolvers, parámetros de consulta y modo history.
  4. ✓ Formularios reactivos sobre señales (Ola 2).
  5. ✓ Utilidades de testing (render, click, type, flush, cleanup).
  6. ✓ Slots con nombre, ✓ referencias (viewChild) y ✓ portales.
  7. createApp() con registros e inyector por aplicación (arregla el estado global). Ola 2

P2 — plataforma y rendimiento (L):

  1. ◐ SSR con jsdom y hydrate() que sustituye el HTML (Ola 2). Pendiente: renderizar los blueprints sin DOM y adoptar los nodos.
  2. ✓ @defer (disparadores idle, viewport, interaction, hover, timer, immediate, when) y rutas perezosas. Falta la división automática del código de un componente suelto.
  3. Optimizaciones del AOT: subárboles estáticos elevados, scope plano, constantes plegadas.
  4. ✓ Gancho para DevTools y un panel mínimo (enableDevtools).
  5. Presupuesto de rendimiento en CI con métricas deterministas (número de operaciones DOM por escenario), no tiempos.
  6. Retirar el código antiguo.

P3 — según demanda: ✓ transiciones y animaciones (básicas), ✓ i18n, virtual scroll, ✓ plugin de Vite, servicio de lenguaje para el editor.

Qué no copiar: Zone.js; reactividad profunda por proxies (más lenta y más memoria); renderizado concurrente completo. (El cliente HTTP se hizo finalmente, pero como un módulo de ~1,3 KB sobre fetch, no como el de Angular.)