
Compartir:
Yonatan ha estado involucrado en algunos proyectos impresionantes en la academia y la industria - desde C / C ++ a través de Matlab a PHP y javascript. Fue director de tecnología en Webiks y arquitecto de software en WalkMe. Actualmente es arquitecto de software en Vonage e instructor de egghead.
Cómo implementar su propio servidor SSR para componentes web
Tiempo de lectura: 11 minutos
Server Side Rendering (SSR) es un tema candente hoy en día. En este artículo, vamos a construir nuestro propio servidor para aprender la mecánica detrás del paradigma SSR y sus posibles extensiones. Comprender cómo funciona el SSR le ayudará a ampliar las soluciones SSR actuales para adaptarlas a sus necesidades. Por ejemplo, puede que necesite componentes web SSR en un servidor Nuxt Nuxt.
En Vonage, tenemos un proyecto público llamado Portal para desarrolladores. Es un sitio web de documentación que no está detrás de una página de inicio de sesión (es decir, público) y que contiene principalmente contenido. También queremos que el contenido esté optimizado para motores de búsqueda (SEO), lo que lo convierte en un buen candidato para SSR.
El portal para desarrolladores está escrito en Vue y se sirve utilizando Nuxt. Nuxt permite SSR a través de su renderizado universal universal. Necesitábamos habilitar Nuxt para que también SSR nuestros componentes web de nuestro sistema de diseño. Así comenzó nuestra andadura en la creación de un mecanismo SSR para componentes web.
¿Qué es el SSR?
En pocas palabras, SSR es el proceso de ejecutar nuestra aplicación en un servidor y devolver HTML plano al cliente.
El código vue se renderiza en nuestro portal en un servidor node.js (Nuxt). La salida de la representación es HTML (con CSS posiblemente en línea). Este HTML (+CSS) se envía al navegador y se muestra allí - sin ningún tipo de JavaScript. Por lo tanto, el usuario puede ver el sitio web realmente rápido.
Además, al mostrar el diseño del sitio web tal y como debe ser con JavaScript se evitan grandes cambios en el diseño como consecuencia de que los componentes adquieran contenido de repente y se expandan una vez que JavaScript entra en funcionamiento.
Tenga en cuenta que los robots (como los rastreadores de los motores de búsqueda) no suelen ver JavaScript, por lo que conseguir este montón de HTML con contenido de inmediato podría hacer maravillas para su posicionamiento en los motores de búsqueda.
Mientras que los formularios, enlaces, vídeos, etc., deberían funcionar en ejemplos no complejos, no tenemos interactividad avanzada sin JavaScript. Por tanto, el usuario ve el sitio web pero no puede interactuar con funciones no nativas.
La documentación es un ejemplo clásico en el que la RSS es realmente necesaria. Muestra sobre todo texto e imágenes al usuario, y la interactividad consiste principalmente en desplazarse para ver más del texto y las imágenes. Esta "fina" capa de texto e imágenes puede denominarse deshidratada de nuestra aplicación.
¿Y si necesitamos interactuar con la página? Necesitaremos hidratar nuestros componentes. Hidratación es un nombre comercial para "cargar nuestro JavaScript". Una vez que el JavaScript se carga, obtenemos nuestra funcionalidad.
En esencia, SSR nos ayuda a cargar nuestro contenido estático más rápido para que los usuarios puedan consumirlo pero no interactuar con él. También contribuye a nuestro posicionamiento SEO.
¿Cómo construir su propio servidor SSR?
Lo primero que recomiendo a la mayoría de la gente es: No construyas tu propio servidor SSR.
Dicho esto, en este artículo, vamos a construir nuestro propio servidor para aprender la mecánica detrás del paradigma SSR y sus posibles extensiones. Comprender cómo funciona SSR le ayudará a ampliar las soluciones SSR actuales para adaptarlas a sus necesidades. Por ejemplo, puede que necesite componentes web SSR en un servidor Nuxt Nuxt.
Ahora que entendemos la utilidad de construir un servidor SSR (o la falta de ella ;) ), vamos a construir uno con fines de aprendizaje.
Un servidor SSR es esencialmente un servidor HTTP que recibe una petición del cliente y, a través de esta petición, analiza una plantilla y devuelve HTML al cliente.
He aquí una ilustración del proceso:
SSR architecture
A partir de aquí, podemos definir los componentes básicos de nuestro servidor:
Un servidor HTTP que gestiona rutas
Una función de representación
Configuración del servidor HTTP
El servidor HTTP es bastante estándar. Sirve archivos estáticos y analiza las rutas que se le envían (como página de inicio):
import http from 'http';
import fs from 'fs';
import path from 'path';
import * as routes from './routes/index.mjs';
const CONTENT_TYPES = {
'.js': 'text/javascript',
'.mjs': 'text/javascript',
'.css': 'text/css',
'.png': 'image/png',
'.jpg': 'image/png',
'.gif': 'image/png',
'.ico': 'image/png',
};
const server = http.createServer(async (req, res) => {
function returnFileContent(filePath, contentType) {
fs.readFile(filePath, (err, content) => {
if (err) {
if (err.code === 'ENOENT') {
res.writeHead(404);
res.end('File not found');
} else {
res.writeHead(500);
res.end(`Server Error: ${err.code}`);
}
} else {
res.writeHead(200, { 'Content-Type': contentType });
res.end(content, 'utf-8');
}
});
}
let filePath = '.' + req.url;
if (filePath === './') {
filePath = 'HomePage';
}
const extname = path.extname(filePath);
let contentType = CONTENT_TYPES[extname] ?? 'text/html';
if (contentType === 'text/html') {
res.writeHead(200, { 'Content-Type': contentType });
res.end(await routes[filePath].template, 'utf-8');
} else {
returnFileContent(filePath, contentType);
}
});
const PORT = 3000;
server.listen(PORT, () => {
console.log(`Server running on http://localhost:${PORT}/`);
}); El sistema de rutas
Nuestro servidor importa un archivo rutas . Este rutas contiene una tabla hash de rutas. Cada ruta tiene una plantilla que devuelve HTML válido. Se usa así:
res.end(await rutas[rutaarchivo].plantilla, 'utf-8');
En nuestro proyecto, tendremos un rutas que contendrá un archivo index.mjs archivo.
Además del archivo índice, crearemos una página de inicio donde residirá la ruta de la página de inicio. Tendrá este aspecto:
Routes folder structurepágina de inicio tendrá su propio index.mjs propio:
Homepage template index fileEste objeto HomePage también se exportará desde el archivo rutas/index.mjs :
export * from './home-page/index.mjs'
Ahora sólo tenemos que implementar getHomePageTemplate en home-page.template.mjs:
export function getHomePageTemplate() {
return `
<div>Hello World</div>
`;
}Por último, necesitamos utilizar la ruta en nuestro servidor, por lo que cambiaremos el archivo principal index.mjs:
import http from 'http';
import fs from 'fs';
import path from 'path';
import * as routes from './routes/index.mjs';
const CONTENT_TYPES = {
'.js': 'text/javascript',
'.css': 'text/css',
'.png': 'image/png',
'.jpg': 'image/png',
'.gif': 'image/png',
};
function returnFileContent(filePath, contentType) {
fs.readFile(filePath, (err, content) => {
if (err) {
if (err.code === 'ENOENT') {
res.writeHead(404);
res.end('File not found');
} else {
res.writeHead(500);
res.end(`Server Error: ${err.code}`);
}
} else {
res.writeHead(200, { 'Content-Type': contentType });
res.end(content, 'utf-8');
}
});
}
const server = http.createServer((req, res) => {
let filePath = '.' + req.url;
if (filePath === './') {
filePath = 'HomePage';
}
const extname = path.extname(filePath);
let contentType = CONTENT_TYPES[extname] ?? 'text/html';
if (contentType === 'text/html') {
res.writeHead(200, { 'Content-Type': contentType });
res.end(routes[filePath].template(), 'utf-8');
} else {
returnFileContent(filePath, contentType);
}
});
const PORT = 3000;
server.listen(PORT, () => {
console.log(`Server running on http://localhost:${PORT}/`);
});Aquí, importamos las rutas (línea 5) y las utilizamos cuando volvemos text/html (línea 41).
Los resultados son asombrosos.
The results of the served content in the browser
Añadamos una plantilla mejor
Esta plantilla es bastante aburrida... volvamos algo picante. Para ello, voy a utilizar el Vivid sistema de diseño. Vivid son componentes web puros. Los usaremos para darle vida a nuestra plantilla y renderizarlos del lado del servidor.
En la página del componente de Vividpodemos tomar el ejemplo de apariencia, que muestra cuatro botones diferentes:
The button code sample from the Vivid documentation
Podemos reemplazar nuestra plantilla en home-page.template.mjscon el código de ejemplo:
export function getHomePageTemplate() {
return `
<vwc-button label="ghost" appearance="ghost"></vwc-button>
<vwc-button label="ghost-light" appearance="ghost-light"></vwc-button>
<vwc-button label="filled" appearance="filled"></vwc-button>
<vwc-button label="outlined" appearance="outlined"></vwc-button>
`;
}Y el resultado aquí es:
The results of the served content in the browser after adding the Vivid buttons to the template
Una página en blanco junto a un cuerpo no tan vacío. ¿Dónde están los componentes del ejemplo de código?
No se cargan porque requieren que carguemos JS y CSS.
¿Cómo cargar CSS y JavaScript?
Suele ser una pregunta trivial, pero ¿cómo se hace en un servidor SSR?
Vamos a optar por la forma más sencilla de hacerlo, que es utilizar una CDN. Puede importar componentes Vivid utilizando esta convención:
https://unpkg.com/@vonage/vivid@latest/{pathToFile}
Con esto, podemos importar nuestro código en la plantilla:
export function getHomePageTemplate() {
return `
<style>
@import "https://unpkg.com/@vonage/vivid@latest/styles/tokens/theme-light.css";
@import "https://unpkg.com/@vonage/vivid@latest/styles/core/all.css";
@import "https://unpkg.com/@vonage/vivid@latest/styles/fonts/spezia-variable.css";
</style>
<vwc-button label="ghost" appearance="ghost"></vwc-button>
<vwc-button label="ghost-light" appearance="ghost-light"></vwc-button>
<vwc-button label="filled" appearance="filled"></vwc-button>
<vwc-button label="outlined" appearance="outlined"></vwc-button>
<script type="module" src="https://unpkg.com/@vonage/vivid@latest/button"></script>
`;
}Si vamos a probar nuestro cliente, veremos nuestros componentes. Bueno... más o menos:
The results of the served content in the browser after importing the Vivid library client-side
Una cosa que tenemos que hacer para que Vivid funcionen es añadir el componente vvd-root al elemento que los envuelve (normalmente el cuerpo...).
Definamos una envoltura para nuestra plantilla:
export function getHomePageTemplate() {
return `
<style>
@import "https://unpkg.com/@vonage/vivid@latest/styles/tokens/theme-light.css";
@import "https://unpkg.com/@vonage/vivid@latest/styles/core/all.css";
@import "https://unpkg.com/@vonage/vivid@latest/styles/fonts/spezia-variable.css";
#buttons-wrapper {
min-width: 50px;
min-height: 50px;
background-color: crimson;
}
</style>
<div id="buttons-wrapper" class="vvd-root">
<vwc-button label="ghost" appearance="ghost"></vwc-button>
<vwc-button label="ghost-light" appearance="ghost-light"></vwc-button>
<vwc-button label="filled" appearance="filled"></vwc-button>
<vwc-button label="outlined" appearance="outlined"></vwc-button>
</div>
<script type="module" src="https://unpkg.com/@vonage/vivid@latest/button"></script>
`;
}Este es el resultado:
An animated Gif showing the page load of the code above
Así que los botones funcionan, pero... ¿Puedes ver el problema?
El HTML se carga - como podemos ver en el div envolvente - y luego los botones se renderizan una vez que el JS entra en acción, creando un cambio importante en el diseño. Imagina que esto ocurre en una aplicación más grande con muchos más componentes.
Eso no es bueno...
¿Cómo podemos evitar este flash? ¡Rendericemos los componentes en el servidor!
Creación de la función de renderizado
En lugar de cargar el JS en el lado del cliente, podemos renderizar los componentes en el servidor y enviar un HTML completo. Por lo tanto, tenemos que encontrar una manera de renderizar nuestros componentes en el servidor como si estuvieran en un navegador.
Cada framework tiene un método de renderizado diferente.
Los componentes web son renderizados de forma nativa por el navegador. Los componentes web también aportan la idea de shadow DOM. En esencia, el shadow DOM es un fragmento de documento en el que se puede añadir HTML y CSS. Para ello, el navegador crea un shadow root dentro de nuestro componente:
Shadow Root under the button
Todo lo que queda fuera de la raíz de sombra está "en la luz", mientras que el resto está en la sombra. La ventaja de un shadowDOM es que encapsula los estilos. Los estilos dentro no afectan a nada fuera y (casi completamente) viceversa.
Eso significa que si tomamos nuestra plantilla y la establecemos como el innerHTML de un div, deberíamos obtener componentes renderizados. Intentémoslo en el navegador:
const div = document.createElement('div');
div.innerHTML = `
<style>
@import "https://unpkg.com/@vonage/vivid@latest/styles/tokens/theme-light.css";
@import "https://unpkg.com/@vonage/vivid@latest/styles/core/all.css";
@import "https://unpkg.com/@vonage/vivid@latest/styles/fonts/spezia-variable.css";
#buttons-wrapper {
min-width: 50px;
min-height: 50px;
background-color: crimson;
}
</style>
<div id="buttons-wrapper" class="vvd-root">
<vwc-button label="ghost" appearance="ghost"></vwc-button>
<vwc-button label="ghost-light" appearance="ghost-light"></vwc-button>
<vwc-button label="filled" appearance="filled"></vwc-button>
<vwc-button label="outlined" appearance="outlined"></vwc-button>
</div>
<script type="module" src="https://unpkg.com/@vonage/vivid@latest/button"></script>
`;
document.body.appendChild(div);Si pegas este código en tu navegador, deberías ver el div carmesí sin el botón porque el JS no se importaría.
No obstante, si hubieras importado el JS antes, habría funcionado:
const script = document.createElement('script');
script.type = 'module';
script.src = 'https://unpkg.com/@vonage/vivid@latest/button';
const div = document.createElement('div');
div.innerHTML = `
<style>
@import "https://unpkg.com/@vonage/vivid@latest/styles/tokens/theme-light.css";
@import "https://unpkg.com/@vonage/vivid@latest/styles/core/all.css";
@import "https://unpkg.com/@vonage/vivid@latest/styles/fonts/spezia-variable.css";
#buttons-wrapper {
min-width: 50px;
min-height: 50px;
background-color: crimson;
}
</style>
<div id="buttons-wrapper" class="vvd-root">
<vwc-button label="ghost" appearance="ghost"></vwc-button>
<vwc-button label="ghost-light" appearance="ghost-light"></vwc-button>
<vwc-button label="filled" appearance="filled"></vwc-button>
<vwc-button label="outlined" appearance="outlined"></vwc-button>
</div>
`;
document.body.appendChild(div);
document.body.appendChild(script);Probado en Google.com:
The google home page with the button HTML snippet added to it
La cuestión es que los elementos document, body y HTML no existen de forma nativa en el lado del servidor. Así que...
¿Cómo se puede renderizar HTML en un servidor?
Buena pregunta. Me alegro de que lo preguntes.
Hay varias formas de renderizar HTML en el lado del servidor.
Dado que Vivid prueba sus componentes utilizando jsdom, sabemos que puede renderizar nuestros componentes sin un navegador.
Por lo tanto, si creamos un entorno JSDOM en nuestro servidor, podremos utilizar nuestro código para renderizar nuestros componentes.
Eso es muy fácil gracias al todopoderoso MNP.
npm i global-jsdom/register jsdom añadirá jsdom - una biblioteca que imita la API DOM del navegador en el tiempo de ejecución del servidor, lo que le permite crear marcado como si estuviera en el navegador. global-jsdom/register expone la API del navegador de forma global para que podamos utilizarla en nuestro código. De este modo, podemos renderizar nuestros componentes en el servidor.
Cambiemos un poco el código de nuestra plantilla para utilizarlo:
import 'global-jsdom/register';
import '@vonage/vivid/button';
export function getHomePageTemplate() {
const template = `
<style>
@import "https://unpkg.com/@vonage/vivid@latest/styles/tokens/theme-light.css";
@import "https://unpkg.com/@vonage/vivid@latest/styles/core/all.css";
@import "https://unpkg.com/@vonage/vivid@latest/styles/fonts/spezia-variable.css";
#buttons-wrapper {
min-width: 50px;
min-height: 50px;
background-color: crimson;
}
</style>
<div id="buttons-wrapper" class="vvd-root">
<vwc-button label="ghost" appearance="ghost"></vwc-button>
<vwc-button label="ghost-light" appearance="ghost-light"></vwc-button>
<vwc-button label="filled" appearance="filled"></vwc-button>
<vwc-button label="outlined" appearance="outlined"></vwc-button>
</div>
`;
const div = document.createElement('div');
div.innerHTML = template;
document.body.appendChild(div);
return div.innerHTML;
}Importamos global-jsdom/register. Nótese que importamos el archivo @vonage/vivid/button para que el componente web se muestre como uno solo.
Dejamos que jsdom renderice nuestra plantilla simplemente añadiéndola al DOM y devolviendo su innerHTML. Se ve así:
The results of the served content in the browser after serving the HTML from the server.
¡NO! ¡No hay botones en la vista! Sí que están en el DOM. También podemos ver la entrada en la luz DOM dentro de cada botón (que está ahí para resolver la asociación de formulario).
La razón por la que no vemos nada es que innerHTML no nos da el contenido del shadowDOM.
Así que lo que podríamos intentar hacer es obtener el shadowDOM de cada componente así:
function appendOwnShadow(element) {
const shadowTemplate = ${element.shadowRoot.innerHTML};
const tmpElement = document.createElement('div');
tmpElement.innerHTML = shadowTemplate;
element.appendChild(tmpElement.children[0]);
}
Array.from(div.querySelectorAll(‘vwc-button’))
.forEach(button => button.appendChild(appendOwnShadow(button)));Lo que nos da esta IU:
The four buttons deformed¡Sí! Podemos ver algo, pero... no es exactamente lo mismo, ¿verdad?
Mirando el HTML, podemos ver que shadowroot falta en este enlace gist.
Esto definitivamente podría afectar el estilo del componente ya que estamos perdiendo la encapsulación.
Cómo renderizar explícitamente Shadow DOM sin JavaScript
Para ello, la especificación HTML define ahora un shadowrootmode para la etiqueta de plantilla. Cuando el navegador encuentra <template shadowrootmode=”open”>, sabe que debe tomar todo lo que hay dentro de esa plantilla y renderizarlo dentro de un shadow DOM.
Utilizando este conocimiento, podemos cambiar nuestro código de la siguiente manera:
function appendOwnShadow(element) {
const shadowTemplate = <template shadowrootmode="open"> ${element.shadowRoot.innerHTML}</template>;
const tmpElement = document.createElement('div');
tmpElement.innerHTML = shadowTemplate;
element.appendChild(tmpElement.children[0]);
}
Array.from(div.querySelectorAll(‘vwc-button’))
.forEach(button => button.appendChild(appendOwnShadow(button)));Ahora aparece así:
The four buttons appear correctly
Que es lo que esperábamos. ¡Hurra!
Si miras el DOM ahora, se ve así:
The buttons' HTML snippet from the Elements Panel¿No es genial? Hemos renderizado nuestros componentes web en el servidor y hemos evitado el cambio de diseño en nuestra aplicación.
Intentemos animar nuestra aplicación.
Manejo de componentes complejos
El botón que utilizamos era bastante básico. Intentemos utilizar un botón con un icono en su interior:
<vwc-button icon="facebook-color" label="ghost" appearance="ghost"></vwc-button>
<vwc-button icon="linkedin-color" label="ghost-light" appearance="ghost-light"></vwc-button>
<vwc-button icon="twitter-color" label="filled" appearance="filled"></vwc-button>
<vwc-button icon="instagram-color" label="outlined" appearance="outlined"></vwc-button>Y se ve así en el navegador:
The buttons appear, but internal icon elements are not renderedAlgo ha cambiado, pero no podemos ver ningún icono...
El HTML dentro del botón tiene este aspecto:
A button's shadow-root innerHTMLPodemos ver vwc-icon justo ahí en medio. Podemos ver dos problemas aquí:
El icono no tiene atributos, por lo que no sabe cómo representarse.
El icono no tiene contenido - principalmente, no hay shadowroot.
Resolver el problema del icono que no recibe atributos
Resolvamos la cuestión más sencilla. El icono obtiene sus atributos del componente botón. La plantilla se renderiza de forma asíncrona. Esto significa que después de añadir el div al DOM, la actualización real se produce después de otra iteración del bucle de eventos el bucle de eventos. Por lo tanto, tenemos que esperar a la finalización del proceso de renderizado.
Para ello, podemos configurar la función de plantilla para que sea asíncrona y espere un ciclo de bucle de eventos:
import 'global-jsdom/register';
import '@vonage/vivid/button';
function appendOwnShadow(element) {
const shadowTemplate = `<template shadowrootmode="open">${element.shadowRoot.innerHTML}</template>`;
const tmpElement = document.createElement('div');
tmpElement.innerHTML = shadowTemplate;
element.appendChild(tmpElement.children[0]);
}
export async function getHomePageTemplate() {
const template = `
<style>
@import "https://unpkg.com/@vonage/vivid@latest/styles/tokens/theme-light.css";
@import "https://unpkg.com/@vonage/vivid@latest/styles/core/all.css";
@import "https://unpkg.com/@vonage/vivid@latest/styles/fonts/spezia-variable.css";
#buttons-wrapper {
min-width: 50px;
min-height: 50px;
background-color: crimson;
}
</style>
<div id="buttons-wrapper" class="vvd-root">
<vwc-button icon="facebook-color" label="ghost" appearance="ghost"></vwc-button>
<vwc-button icon="linkedin-color" label="ghost-light" appearance="ghost-light"></vwc-button>
<vwc-button icon="twitter-color" label="filled" appearance="filled"></vwc-button>
<vwc-button icon="instagram-color" label="outlined" appearance="outlined"></vwc-button>
</div>
`;
const div = document.createElement('div');
div.innerHTML = template;
document.body.appendChild(div);
await new Promise(res => setTimeout(res));
Array.from(div.querySelectorAll('vwc-button')).forEach(appendOwnShadow);
return div.innerHTML;
}Fíjate en que hemos añadido la magia await new Promise(res => setTimeout(res)); en la línea 28.
Ahora, cuando echamos un vistazo a nuestro HTML, vemos que el icono recibe los atributos:
A snippet showing the vwc-icon inside the button after the change
Carga de componentes internos
El segundo problema -por el que no vemos los iconos- surge del hecho de que no obtenemos el HTML de shadowroot de los componentes internos.
Una forma de solucionar esto sería encontrar todos los componentes web recursivamente y renderizarlos.
Para encontrar los componentes, podemos recorrer el árbol DOM de la siguiente manera:
function getAllNestedShadowRootsParents(element) {
const nestedShadowRoots = [];
function traverseShadowRoot(node) {
if (node.shadowRoot) {
nestedShadowRoots.push(node);
node.shadowRoot.querySelectorAll('*').forEach(child => {
traverseShadowRoot(child);
});
} else {
Array.from(node.querySelectorAll('*')).forEach(child => traverseShadowRoot(child));
}
}
traverseShadowRoot(element);
return Array.from(new Set(nestedShadowRoots));
}Esta función obtiene un elemento (supuestamente nuestro div envolvente) y encuentra todos los componentes web con shadowDOM.
Ahora, todo lo que queda por hacer es analizar cada uno de ellos en nuestro archivo de plantilla:
Hagámoslo:
import 'global-jsdom/register';
import '@vonage/vivid/button';
function getAllNestedShadowRootsParents(element) {
const nestedShadowRoots = [];
function traverseShadowRoot(node) {
if (node.shadowRoot) {
nestedShadowRoots.push(node);
node.shadowRoot.querySelectorAll('*').forEach(child => {
traverseShadowRoot(child);
});
} else {
Array.from(node.querySelectorAll('*')).forEach(child => traverseShadowRoot(child));
}
}
traverseShadowRoot(element);
return Array.from(new Set(nestedShadowRoots));
}
function appendOwnShadow(element) {
const shadowTemplate = `<template shadowrootmode="open">${element.shadowRoot.innerHTML}</template>`;
const tmpElement = document.createElement('div');
tmpElement.innerHTML = shadowTemplate;
element.appendChild(tmpElement.children[0]);
}
export async function getHomePageTemplate() {
const template = `
<style>
@import "https://unpkg.com/@vonage/vivid@latest/styles/tokens/theme-light.css";
@import "https://unpkg.com/@vonage/vivid@latest/styles/core/all.css";
@import "https://unpkg.com/@vonage/vivid@latest/styles/fonts/spezia-variable.css";
#buttons-wrapper {
min-width: 50px;
min-height: 50px;
background-color: crimson;
}
</style>
<div id="buttons-wrapper" class="vvd-root">
<vwc-button icon="facebook-color" label="ghost" appearance="ghost"></vwc-button>
<vwc-button icon="linkedin-color" label="ghost-light" appearance="ghost-light"></vwc-button>
<vwc-button icon="twitter-color" label="filled" appearance="filled"></vwc-button>
<vwc-button icon="instagram-color" label="outlined" appearance="outlined"></vwc-button>
</div>
`;
const div = document.createElement('div');
div.innerHTML = template;
document.body.appendChild(div);
await new Promise(res => setTimeout(res));
getAllNestedShadowRootsParents(div).reverse().forEach(appendOwnShadow);
return div.innerHTML;
}Fíjate en el cambio de la línea 54: estamos repasando todos los elementos con shadow DOM en orden inverso y añadiendo un elemento shadowroot con su innerHTML.
El resultado es asombroso:
The four buttons with their icons rendered correctlySi has seguido hasta aquí, ¡buen trabajo! Ya conoces los fundamentos de la RSS.
¿Podemos servir más?
Nuestro sencillo servidor SSR puede optimizarse aún más. Por ejemplo, algunas cosas, como el CSS y los SVG de los iconos, siguen dependiendo de servidores que están lejos. Podemos añadir más lógica a nuestro servidor SSR para obtenerlos y alinearlos en el HTML devuelto.
Se pueden tomar más ideas de otros sistemas SSR. Por ejemplo, los componentes del servidor de react tienen una API dedicada para obtener y enviar peticiones al servidor, que a su vez solicita los datos y renderiza la vista necesaria.
Qwik configura trabajadores de servicio para obtener el JS en segundo plano.
Todos los frameworks SSR tienen muchas optimizaciones hechas para ti, pero no siempre se ajustan a tus necesidades, por lo que saber cómo funcionan es un buen comienzo para ampliarlos.
Resumen
Fue todo un paseo, ¿no?
Construir un mecanismo SSR es bastante sencillo, en esencia, pero siempre se puede mejorar, ajustar y optimizar. Es posible que te encuentres manteniendo una gran base de código sólo para manejar SSR.
Puedes elegir usar nextjs (react), Nuxtjs (vue), o alguna otra librería SSR. Si está utilizando componentes web, las bibliotecas SSR como litssr o fastssr pueden tomar el trabajo pesado de usted.
Una gran advertencia con estos frameworks o librerías SSR es que sólo funcionan para el framework o librería con el que se supone que deben trabajar.
Nuestro caso de uso era construir un mecanismo SSR para trabajar junto con Nuxt. Así que puedes llamar a mi código un plugin SSR. Espero que este artículo te haya dado una pista sobre cómo empezar a construir un plugin de este tipo si alguna vez surge la necesidad.
Lo común a todos los SSRs es que hay alguna función de renderizado. Esta función se utiliza en su plantilla y devuelve una cadena HTML que se envía al cliente (bueno, excepto React Server Components que en realidad envían un JSON - pero eso está más allá del alcance de este artículo).
Parte del HTML se hidrata posteriormente después de que el JavaScript se cargue de forma asíncrona sin bloquear la página. En este artículo, aprendimos cómo hacerlo con componentes web y shadow DOM.
No bloqueamos la página con la carga de JS, lo que nos ayuda a servir el contenido más rápido, evitar cambios pesados en el diseño y, posiblemente, mejorar nuestro posicionamiento SEO.
Únase a nosotros en nuestro Slack de la comunidad de Vonage o envíanos un mensaje en X, antes conocido como Twittery dinos cómo podemos ayudarte.
Compartir:
Yonatan ha estado involucrado en algunos proyectos impresionantes en la academia y la industria - desde C / C ++ a través de Matlab a PHP y javascript. Fue director de tecnología en Webiks y arquitecto de software en WalkMe. Actualmente es arquitecto de software en Vonage e instructor de egghead.