
Partager:
Yonatan a participé à des projets impressionnants à l'université et dans l'industrie - de C/C++ à PHP et javascript en passant par Matlab. Il a été directeur technique chez Webiks et architecte logiciel chez WalkMe. Il est actuellement architecte logiciel chez Vonage et instructeur.
Comment mettre en œuvre votre propre serveur SSR pour les composants Web
Temps de lecture : 12 minutes
Le Server Side Rendering (SSR) est un sujet brûlant aujourd'hui. Dans cet article, nous allons construire notre propre serveur pour apprendre les mécanismes derrière le paradigme SSR et ses extensions possibles. Comprendre le fonctionnement du SSR vous aidera à étendre les solutions SSR actuelles pour répondre à vos besoins. Par exemple, vous pourriez avoir besoin de composants web SSR dans une application Nuxt Nuxt.
Chez Vonage, nous avons un projet public appelé Developer Portal. Il s'agit d'un site web de documentation qui ne se trouve pas derrière une page de connexion (c'est-à-dire public) et qui contient essentiellement du contenu. Nous voulons également que le contenu soit optimisé pour les moteurs de recherche (SEO), ce qui en fait un bon candidat pour la RSS.
Le portail du développeur est écrit en Vue et est servi à l'aide de Nuxt. Nuxt permet le SSR grâce à son mécanisme de rendu universel Universal Rendering. Nous devions permettre à Nuxt de rendre également SSR les composants web de notre système de conception. composants web de notre système de conception. C'est ainsi que nous avons commencé à construire un mécanisme SSR pour les composants Web.
Qu'est-ce que la RSS ?
En résumé, la RSS consiste à exécuter notre application sur un serveur et à renvoyer du HTML simple au client.
Le code vue est rendu dans notre portail sur un serveur node.js (Nuxt). Le résultat du rendu est du HTML (avec éventuellement du CSS intégré). Ce HTML (+CSS) est envoyé au navigateur et y est affiché - sans aucun JavaScript. Ainsi, l'utilisateur voit le site web très rapidement.
En outre, l'affichage de la mise en page du site web telle qu'elle devrait être avec JavaScript permet d'éviter d'importants changements de mise en page résultant de composants qui obtiennent soudainement du contenu et qui s'étendent lorsque JavaScript est activé.
Notez que les robots (tels que les robots d'indexation des moteurs de recherche) ne voient généralement pas le JavaScript, de sorte que la mise en place immédiate de ce contenu HTML pourrait faire des merveilles pour le classement de votre site dans les moteurs de recherche.
Alors que les formulaires, les liens, les vidéos, etc., devraient fonctionner dans des exemples non complexes, nous n'avons pas d'interactivité avancée sans JavaScript. L'utilisateur voit donc le site web, mais ne peut pas interagir avec les fonctionnalités non natives.
La documentation est un exemple classique de cas où la RSS est vraiment nécessaire. Elle présente essentiellement du texte et des images à l'utilisateur, et l'interactivité consiste principalement à faire défiler le texte et les images pour en voir davantage. Cette "fine" couche de texte et d'images peut être qualifiée de "déshydratée". déshydratée de notre application.
Et si nous devons interagir avec la page ? Nous devrons hydrater nos composants. L'hydratation est un nom commercial pour "charger notre JavaScript". Une fois le JavaScript chargé, nous obtenons notre fonctionnalité.
Essentiellement, la RSS nous aide à charger notre contenu statique plus rapidement afin que les utilisateurs puissent le consommer sans interagir avec lui. Il contribue également à notre classement en matière de référencement.
Comment construire son propre serveur SSR ?
La première chose que je recommande à la plupart des gens est de ne pas construire leur propre serveur SSR : Ne construisez pas votre propre serveur SSR.
Cela dit, dans cet article, nous allons construire notre propre serveur afin d'apprendre les mécanismes qui sous-tendent le paradigme de la RSS et ses extensions possibles. Comprendre le fonctionnement de la RSS vous aidera à étendre les solutions RSS actuelles pour les adapter à vos besoins. Par exemple, vous pourriez avoir besoin de composants web SSR dans un serveur Nuxt Nuxt.
Maintenant que nous comprenons l'utilité de construire un serveur SSR (ou son absence ;) ), construisons-en un à des fins d'apprentissage.
Un serveur SSR est essentiellement un serveur HTTP qui reçoit une demande du client et qui, par le biais de cette demande, analyse un modèle et renvoie le code HTML au client.
Voici une illustration du processus :
SSR architecture
À partir de là, nous pouvons définir les éléments constitutifs de notre serveur :
Un serveur HTTP qui gère les itinéraires
Une fonction de rendu
Configuration du serveur HTTP
Le serveur HTTP est assez standard. Il sert des fichiers statiques et analyse les routes qui lui sont envoyées (comme page d'accueil) :
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}/`);
}); Le système de routage
Notre serveur importe un fichier routes un objet de type routes. Cet objet routes contient une table de hachage des itinéraires. Chaque itinéraire possède un modèle qui renvoie du HTML valide. Elle s'utilise comme suit :
res.end(await routes[filePath].template, 'utf-8') ;
Dans notre projet, nous aurons un fichier routes qui contiendra un fichier index.mjs .
Outre le fichier d'index, nous allons créer un fichier page d'accueil dans lequel résidera la route de la page d'accueil. Il ressemblera à ceci :
Routes folder structurepage d'accueil contiendra son propre index.mjs propre :
Homepage template index fileCet objet HomePage sera également exporté depuis le fichier routes/index.mjs :
export * from './home-page/index.mjs'
Il ne nous reste plus qu'à implémenter getHomePageTemplate dans home-page.template.mjs:
export function getHomePageTemplate() {
return `
<div>Hello World</div>
`;
}Enfin, nous devons utiliser la route dans notre serveur, nous allons donc modifier le fichier 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}/`);
});Ici, nous importons les itinéraires (ligne 5) et les utilisons lorsque nous retournons text/html (ligne 41).
Les résultats sont stupéfiants !
The results of the served content in the browser
Ajoutons un meilleur modèle
Ce modèle est plutôt ennuyeux... retournons quelque chose de plus épicé. Pour cela, j'utiliserai le modèle Vivid le système de conception. Les composants Vivid sont des composants purement web. Nous allons les utiliser pour pimenter notre modèle et les rendre côté serveur.
Dans la page de Vivid, nous pouvons prendre l'exemple de l'apparence d'un bouton.nous pouvons prendre l'exemple de l'apparence, qui présente quatre boutons différents :
The button code sample from the Vivid documentation
Nous pouvons remplacer notre modèle dans home-page.template.mjspar le code de l'exemple :
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>
`;
}Et le résultat est là :
The results of the served content in the browser after adding the Vivid buttons to the template
Une page blanche à côté d'un corps pas si vide que cela. Où sont les composants de l'exemple de code ?
Ils ne se chargent pas parce qu'ils nous obligent à charger JS et CSS.
Comment charger les feuilles de style CSS et JavaScript ?
Il s'agit généralement d'une question triviale, mais comment cela se passe-t-il dans un serveur SSR ?
La solution la plus simple est d'utiliser un CDN. Vous pouvez importer des composants Vivid en utilisant cette convention :
https://unpkg.com/@vonage/vivid@latest/{pathToFile}
Nous pouvons ainsi importer notre code dans le modèle :
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 nous testons notre client, nous verrons nos composants. Enfin... en quelque sorte :
The results of the served content in the browser after importing the Vivid library client-side
Une chose que nous devons faire pour rendre Vivid fonctionne, c'est d'ajouter l'option vvd-root à l'élément qui les englobe (généralement le corps...).
Définissons une enveloppe pour notre modèle :
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>
`;
}Voici le résultat :
An animated Gif showing the page load of the code above
Les boutons fonctionnent, mais... Voyez-vous le problème ?
Le code HTML se charge - comme le montre la div qui l'enveloppe - puis les boutons s'affichent une fois que le code JS est activé, ce qui crée un changement majeur dans la mise en page. Imaginez que cela se produise dans une application plus grande, avec beaucoup plus de composants.
Ce n'est pas bon...
Comment éviter ce flash ? Rendons les composants sur le serveur !
Création de la fonction de rendu
Au lieu de charger le JS du côté client, nous pouvons rendre les composants sur le serveur et envoyer un HTML complet. Nous devons donc trouver un moyen de rendre nos composants sur le serveur comme s'ils étaient dans un navigateur.
Chaque cadre a une méthode de rendu différente.
Les composants Web sont rendus nativement par le navigateur. Les composants Web apportent également l'idée du shadow DOM. En substance, le shadow DOM est un fragment de document dans lequel vous pouvez ajouter du HTML et du CSS. Pour ce faire, le navigateur crée une racine d'ombre à l'intérieur de notre composant :
Shadow Root under the button
Tout ce qui se trouve en dehors de la racine de l'ombre est "dans la lumière", tandis que le reste est dans l'ombre. L'avantage d'un shadowDOM est qu'il encapsule les styles. Les styles à l'intérieur n'affectent rien à l'extérieur et (presque complètement) vice-versa.
Cela signifie que si nous prenons notre modèle et que nous le définissons comme le innerHTML d'une div, nous devrions obtenir des composants rendus. Essayons cela dans le navigateur :
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 vous collez ce code dans votre navigateur, vous devriez voir la div cramoisie sans le bouton car le JS n'a pas été importé.
Néanmoins, si vous aviez importé le JS au préalable, cela aurait fonctionné :
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);Testé sur Google.com :
The google home page with the button HTML snippet added to it
Le problème est que les éléments document, body et HTML n'existent pas nativement du côté du serveur. C'est pourquoi...
Comment rendre le code HTML sur un serveur ?
Excellente question ! Je suis heureux que vous l'ayez posée.
Il existe plusieurs façons de rendre le HTML du côté du serveur.
Parce que Vivid teste ses composants en utilisant jsdom, nous savons qu'il peut rendre nos composants sans navigateur.
Par conséquent, si nous créons un environnement JSDOM dans notre serveur, nous pouvons utiliser notre code pour rendre nos composants.
C'est assez facile grâce au tout-puissant NPM !
npm i global-jsdom/register jsdom ajoutera jsdom - une bibliothèque qui imite l'API DOM du navigateur dans le runtime du serveur, lui permettant de créer des balises comme s'il était dans le navigateur. global-jsdom/register expose l'API du navigateur de manière globale afin que nous puissions l'utiliser dans notre code. Ainsi, nous pouvons effectuer le rendu de nos composants côté serveur.
Modifions un peu le code de notre modèle pour l'utiliser :
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;
}Nous importons global-jsdom/register. Notez que nous importons le fichier @vonage/vivid/button côté serveur, afin que le composant web soit rendu comme tel.
Nous laissons jsdom de rendre notre modèle en l'ajoutant au DOM et en renvoyant son innerHTML. Cela ressemble à ceci :
The results of the served content in the browser after serving the HTML from the server.
OH NON ! Pas de boutons dans la vue ! Ils sont bien dans le DOM. Nous pouvons également voir l'entrée dans le DOM léger à l'intérieur de chaque bouton (c'est là pour résoudre l'association de formulaire).
La raison pour laquelle nous ne voyons rien est que innerHTML ne nous donne pas le contenu du shadowDOM.
Nous pourrions donc essayer de récupérer le shadowDOM de chaque composant comme ceci :
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)));Ce qui nous donne cette interface utilisateur :
The four buttons deformedC'est bien ! Nous pouvons voir quelque chose, mais... ce n'est pas exactement la même chose, n'est-ce pas ?
En regardant le HTML, nous pouvons voir que le shadowroot est manquant sur ce lien gist.
Cela pourrait certainement affecter le style du composant puisque nous perdons l'encapsulation.
Comment rendre explicitement Shadow DOM sans JavaScript
À cette fin, la spécification HTML définit désormais un mode shadowrootmode pour la balise template. Lorsque le navigateur rencontre <template shadowrootmode=”open”>, il sait qu'il doit prendre tout ce qui se trouve à l'intérieur de ce modèle et le rendre à l'intérieur d'un DOM d'ombre.
En utilisant ces connaissances, nous pouvons modifier notre code comme suit :
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)));Le rendu est maintenant le suivant :
The four buttons appear correctly
C'est ce que nous avions prévu de faire ! Hourra !
Si vous regardez le DOM maintenant, il ressemble à ceci :
The buttons' HTML snippet from the Elements PanelQu'est-ce que c'est chouette ! Nous avons rendu nos composants web côté serveur et empêché le changement de disposition dans notre application !
Essayons de pimenter notre application.
Manipulation de composants complexes
Le bouton que nous avons utilisé était assez basique. Essayons d'utiliser un bouton avec une icône à l'intérieur :
<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>Et cela ressemble à ceci dans le navigateur :
The buttons appear, but internal icon elements are not renderedQuelque chose a changé, mais nous ne voyons pas d'icônes...
Le code HTML contenu dans le bouton ressemble à ceci :
A button's shadow-root innerHTMLNous pouvons voir vwc-icon en plein milieu. Deux problèmes se posent ici :
L'icône n'a pas d'attributs - elle ne sait donc pas vraiment comment se rendre.
L'icône n'a pas de contenu - principalement, pas de racine de l'ombre.
Résolution du problème de l'icône n'obtenant pas d'attributs
Résolvons le problème le plus simple. L'icône obtient ses attributs du composant bouton. Le modèle est rendu de manière asynchrone. Cela signifie qu'après avoir ajouté la balise div au DOM, la mise à jour réelle se produit après une autre itération de de la boucle d'événements. Nous devons donc attendre la fin du processus de rendu.
Pour ce faire, nous pouvons définir la fonction modèle comme étant asynchrone et attendre un cycle de boucle d'événements :
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;
}Remarquez que nous avons ajouté la magie await new Promise(res => setTimeout(res)); à la ligne 28.
Maintenant, lorsque nous jetons un coup d'œil à notre HTML, nous voyons que l'icône reçoit les attributs :
A snippet showing the vwc-icon inside the button after the change
Chargement des composants internes
Le deuxième problème - qui fait que nous ne voyons pas les icônes - vient du fait que nous n'obtenons pas le HTML des composants internes de la racine de l'ombre.
Une solution consisterait à rechercher tous les composants web de manière récursive et à les rendre.
Pour trouver les composants, nous pouvons parcourir l'arbre DOM comme suit :
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));
}Cette fonction récupère un élément (supposé être notre div enveloppant) et trouve tous les composants web avec shadowDOM.
Il ne reste plus qu'à analyser chacun d'entre eux dans notre fichier modèle :
C'est ce que nous allons faire :
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;
}Remarquez le changement à la ligne 54 - nous passons en revue tous les éléments avec shadow DOM dans l'ordre inverse et nous ajoutons un élément shadowroot à leur innerHTML.
Le résultat est stupéfiant :
The four buttons with their icons rendered correctlySi vous avez suivi jusqu'ici, bravo ! Vous avez acquis les bases de la RSS.
Pouvons-nous servir davantage ?
Notre simple serveur SSR peut encore être optimisé. Par exemple, certains éléments, tels que le CSS et les SVG des icônes, dépendent encore de serveurs éloignés. Nous pouvons ajouter plus de logique à notre serveur SSR pour les récupérer et les intégrer dans le code HTML renvoyé.
D'autres idées peuvent être tirées d'autres systèmes SSR. Par exemple, les composants du serveur react ont une API dédiée pour récupérer et envoyer des requêtes au serveur, qui à son tour demande les données et rend la vue nécessaire.
Qwik met en place des travailleurs de service pour récupérer le JS en arrière-plan.
Tous les frameworks SSR comportent de nombreuses optimisations, mais ils ne répondent pas toujours à vos besoins ; savoir comment ils fonctionnent est donc un bon point de départ pour les étendre.
Résumé
C'était une sacrée balade, n'est-ce pas ?
La construction d'un mécanisme de RSS est assez simple, par essence, mais il est toujours possible de l'améliorer, de le peaufiner et de l'optimiser. Il se peut que vous vous retrouviez à maintenir une base de code importante juste pour gérer le SSR.
Vous pouvez choisir d'utiliser nextjs (react), Nuxtjs (vue), ou une autre bibliothèque SSR. Si vous utilisez des composants web, des bibliothèques SSR telles que litssr ou fastssr peuvent se charger du gros du travail.
L'une des principales mises en garde concernant ces cadres ou bibliothèques SSR est qu'ils ne fonctionnent que pour le cadre ou la bibliothèque avec lesquels ils sont censés fonctionner.
Notre objectif était de mettre en place un mécanisme SSR qui fonctionnerait en parallèle avec Nuxt. Vous pouvez donc appeler mon code un plugin SSR. J'espère que cet article vous a donné un indice sur la façon de commencer à construire un tel plugin si le besoin s'en fait sentir.
Le point commun à tous les SSR est qu'il existe une fonction de rendu. Cette fonction est utilisée sur votre modèle et renvoie une chaîne HTML qui est envoyée au client (enfin, à l'exception des composants React Server qui envoient en fait un JSON - mais cela dépasse le cadre de cet article).
Certains éléments HTML sont hydratés plus tard, après le chargement asynchrone du JavaScript, sans bloquer la page. Dans cet article, nous avons appris à le faire avec les composants web et le shadow DOM.
Nous ne bloquons pas la page avec le chargement JS, ce qui nous permet de servir le contenu plus rapidement, d'éviter de lourdes modifications de la mise en page et, éventuellement, d'améliorer notre classement en matière de référencement.
Rejoignez-nous sur notre Communauté Vonage Slack ou envoyez-nous un message sur X, anciennement connu sous le nom de Twitteret faites-nous savoir comment nous pouvons vous aider !
Partager:
Yonatan a participé à des projets impressionnants à l'université et dans l'industrie - de C/C++ à PHP et javascript en passant par Matlab. Il a été directeur technique chez Webiks et architecte logiciel chez WalkMe. Il est actuellement architecte logiciel chez Vonage et instructeur.