Actualmente existen dos formas de intercambiar información entre cliente y servidor. Una basada en JSON y otra en HTML. Generalmente las SPAs usan la primera. React, Angular y Vue como las más conocidas, pero también versiones pequeñas como Petite-vue, Alpine, o Helium entre otras muchas. En el caso de HTML, lo usan HTMX o Fixi.js, o incluso la minúscula HTMz.
Generalmente la industria ha seguido el camino marcado por React con lo que ello supone: fase de compilación, carga lentísima inicial, decenas de dependencias incluso para proyectos minúsculos, vulnerabilidades, etc. Sin embargo, cuando se propone el uso de HTMX generalmente es descartado porque no está probado suficientemente en proyectos grandes y muchos desarrolladores afirman que no es escalable fácilmente para proyectos complejos.
He estado pensando en otras opciones, y fruto de ello y del concepto de Action web components, se me ocurrido lo siguiente. La petición de información al servidor devuelve HTML pero usando un web component que encapsule la respuesta generada por el servidor (data.html) de esta forma:
<data-hydrate template="tpl-item">[
{"title": "Primer item", "desc": "Descripción del primero"},
{"title": "Segundo item", "desc": "Descripción del segundo"}
]</data-hydrate>
La idea es la siguiente. En la página principal se definen un web component que busque el template referenciado en su atribute "template" y lo use para hidratarlo alimentando su propio contenido. Algo así como
<h1>Demo data-hydrate</h1>
<button onclick="fetch('data.html').then(r=>r.text()).then(html=>document.querySelector('#container').innerHTML=html)">Cargar datos</button>
<div class="table" id="container"></div>
<template id="tpl-item">
<h3><slot name="title"></slot></h3>
<span><slot name="desc"></slot></span>
</template>
<script>
class DataHydrate extends HTMLElement {
connectedCallback() {
const templateId = this.getAttribute('template');
const tpl = document.getElementById(templateId);
if (!tpl) return;
const shadow = this.attachShadow({mode:'open'});
let content = this.textContent.trim();
this.textContent = '';
let data;
try {
data = JSON.parse(content);
} catch(e) {
data = content;
}
const hydrateTemplate = (obj) => {
const clone = tpl.content.cloneNode(true);
Object.keys(obj).forEach(key => {
const slot = clone.querySelector(`slot[name="${key}"]`);
if(slot) slot.textContent = obj[key];
});
shadow.appendChild(clone);
};
if(Array.isArray(data)) {
data.forEach(item => hydrateTemplate(item));
} else if (typeof data === 'object') {
hydrateTemplate(data);
} else {
// Si és string simple i el template té un slot sense nom, el posarem allà
const clone = tpl.content.cloneNode(true);
const slot = clone.querySelector('slot:not([name])');
if(slot) slot.textContent = data;
shadow.appendChild(clone);
}
}
}
customElements.define('data-hydrate', DataHydrate);
</script>
Por tanto, en cuanto aparece el tag <data-hydrate> en la página, automáticamente se usa su contenido (enviado desde el servidor) para alimentar un template en Shadow dom declarativa, de forma que el propio HTML se encargue de usar los slots automáticamente.
Esta técnica puede resultar inspiradora aunque también arrastra sus defectos. Por un lado, la sintaxis de los templates de html no es la ideal precisamente para especificar el uso de un elemento (siempre será mucho más cómodo poner {{data}} que <slot name="data">). Y por otro, usar web components declarativos tiene también un coste en la estructura del html generado introduciendo más tags de los necesarios. Posiblemente en el futuro explore una variación de esta técnica usando otra sintaxis, como la string literals: ${data} o similar, y haciendo la sustitución de los templates mediante código js.
0 comentarios:
Publicar un comentario