Rendimiento de componentes web y desafíos con la API
Crear un componente web con solo una propiedad requiere una cantidad significativa de código usando la API de Elementos Personalizados. La implementación base solo permite atributos de tipo cadena, lo que obliga a serializar otros tipos de datos.
Ejemplo de un componente mínimo:
class MyFoo extends HTMLElement {
_bar = 0
get bar() {
return this._bar
}
set bar( next ) {
this.setAttribute( 'bar', next )
}
static observedAttributes = [ 'bar' ]
attributeChangedCallback( name, prev, next ) {
this[ '_' + name ] = next
}
}
globalThis.customElements.define( 'my-foo', MyFoo )
Para soportar números, se añade serialización JSON con una bandera _muted_ para evitar recursión:
class MyFoo extends HTMLElement {
_bar = 0
get bar() {
return this._bar
}
set bar( next ) {
this._bar = next
this._muted_ = true; try {
this.setAttribute( 'bar', JSON.stringify( next ) )
} finally { this._muted_ = false }
}
_muted_ = false
static observedAttributes = [ 'bar' ]
attributeChangedCallback( name, prev, next ) {
if( this._muted_ ) return
this[ '_' + name ] = JSON.parse( next )
}
}
Esto introduce problemas con la escapación de cadenas de atributos y exige capas adicionales de abstracción para fechas, objetos y otros tipos.
Rendimiento: Microsegundos que arruinan los fotogramas
Los componentes web son órdenes de magnitud más lentos que los objetos JS normales debido a la proxyación en tiempo de ejecución. Comparación del tiempo de creación:
- Objeto JS normal: ~0,01 μs
- Componente web: ~1–3 μs
Mil componentes tardan entre 1 y 3 ms en crearse—una porción sustancial del presupuesto de 16 ms por fotograma. Los cambios de propiedades también consumen microsegundos:
- Asignación simple en JS: nanosegundos
- Setter en componente web: microsegundos
La causa raíz es el intercambio constante de datos entre JS y el runtime en C++ del navegador, bloqueando la optimización JIT. Uso de memoria: 124 bytes por componente web trivial frente a 16 bytes para un objeto JS.
Registro global y conflictos de nombres
El registro de componentes es global e irreversible hasta que se cierre la pestaña:
customElements.define( 'ya-button', YandexButton )
// ...
customElements.define( 'ya-button', YahooButton ) // 💥 Error: nombre ya usado
Los conflictos de nombres rompen la modularidad. Aunque el CustomElementRegistry en Shadow DOM ayuda parcialmente, no hay polyfills disponibles, y fusionar registros requiere gestión de espacios de nombres.
Alternativas:
- Un registro fractal global como DNS
- VerLess — actualizaciones sin romper compatibilidad
Ganchos de ciclo de vida y Shadow DOM
connectedCallback/disconnectedCallback se disparan ante cualquier movimiento del componente—incluso cuando se mueve al mismo lugar—activando cascadas en subárboles. Esto genera tareas asíncronas innecesarias.
Shadow DOM ofrece aislamiento pero complica el estilo: adoptedStyleSheets requieren importaciones dinámicas de estilos desde fuera. Los nombres de elementos deben ser estrictamente kebab-case, chocando con elementos estándar del DOM.
Lo que realmente importa
- Rendimiento: Los componentes web son 100–1000x más lentos que los objetos JS por la proxyación del host
- Código repetitivo: Serialización/deserialización es obligatoria para propiedades no de tipo cadena
- Registro: Global, irreversible, propenso a colisiones de nombres
- Ciclo de vida: Ganchos se activan en movimientos, causando cascadas
- Shadow DOM: Dificulta la personalización de estilos y el uso de polyfills
Los componentes web no admiten tipado estático de TypeScript ni reactividad integrada, requiriendo frameworks para aplicaciones reales.
— Editorial Team
Aún no hay comentarios.