Webové komponenty vykreslování na straně serveru

Je běžným mýtem, že webové komponenty nelze vykreslovat na straně serveru. Ukázalo se, že můžete, pokud se podíváte na správné místo. Řekli byste si, že vykreslení vlastního prvku na serveru zvládne nespočet nástrojů, ale není tomu tak. Zní to zvláštně, protože vlastní prvky jsou specifikace, zatímco frameworky JavaScript nikoli. Někteří inženýři uvedli, že tento úkol je nemožný, a uvedli nedostatek SSR jako důvod, proč se Web Components úplně vyhnout.

Může se zdát jako zbytečné, že balíčky SSR budou podporovat webové komponenty, protože vlastní prvky jsou spec. Zatímco některé vykreslovací nástroje na straně serveru mohou nabízet podporu pro vlastní prvky, obsah ShadowDOM často chybí, když je pohled doručen klientovi. To může vést k určitému zmatku. Je toho hodně na co se ptát, protože to znamená, že ShadowDOM dostává zvláštní zacházení pro účely vykreslování na straně serveru. JSDOM minulý týden přidal podporu pro vlastní prvky a uzavřel tiket, který byl na Githubu otevřený dlouhých pět let. Bohužel jsem nemohl přijít na to, jak vystavit Shadow DOM pomocí nejnovější verze JSDOM.

@skatejs je sada nástrojů pro vývoj webových komponent, která existuje již několik let. @skatejs/ssr balíček může vykreslit ShadowDOM na serveru. Dosahují tohoto výkonu tím, že se prodlužují. Úžasná část na @skatejs/ssr je, že nemusíte kódovat vlastní prvky pomocí @skatejs, abyste mohli využít vykreslování na straně serveru. Můžete použít, co chcete. Vlastní prvky kóduji pomocí knihovny s názvem Readymade.

S pouhými několika řádky kódu v node.js jsem byl schopen vykreslit vlastní prvky pomocí ShadowDOM. V tomto příspěvku jsem nastínil svůj postup, aby ostatní mohli využít webové komponenty pro vykreslování na straně serveru.

Vše začíná routerem na straně klienta. Potřeboval jsem router, abych mohl namapovat vlastní prvek a jeho šablonu na stránku. To by mi umožnilo dělat to samé na serveru. Nejprve jsem si vybral @vaadin/router, protože je kompatibilní s Web Components a líbilo se mi API. Rychle jsem zjistil, že tento balíček není kompatibilní s vykreslováním na straně serveru hned po vybalení. Dojde k podivnému problému, který způsobí, že se stejný prvek zobrazí na stránce dvakrát, pravděpodobně způsobený tím, že směrovač připojí DOM k prvku kontejneru, místo aby jej přepsal. Sotva jsem čekala hydrataci, ale myslela jsem si, že by to mohlo fungovat.

Místo toho jsem skončil kódováním jednoduchého klientského routeru, který používá historii a umístění k zobrazení vlastního prvku na trase. Je to velmi holé kosti, ale zatím to dělá. Podívejte se na kód zde. Implementace vlastního routeru uvnitř aplikace, která používá vlastní prvky, vypadá takto:


import { RdRouter } from './router/index';

const routing = [
    { path: '/', component: 'app-home' },
    { path: '/about', component: 'app-about' }
];

const rdrouter = new RdRouter('#root', routing);

Ve výše uvedeném příkladu jsou dvě trasy mapovány na názvy značek dvou vlastních prvků:app-home a app-about . Oba vlastní prvky budou vykresleny v prvku div s ID root .

resolve(route: RdRoute) {
    const component = document.createElement(route.component);
    this.rootElement.innerHTML = '';
    this.rootElement.appendChild(component);
}

Jakmile bylo směrování na místě, musel jsem zjistit, co je @skatejs/ssr očekávané vykreslení balíčku. Všechny příklady, které jsem našel, ukázaly, že třída ES2015 vlastního prvku je předávána do metody vykreslování.

Svou aplikaci jsem již spojoval se službou Parcel. Potřeboval jsem způsob, jak spojit pouze komponenty zobrazení vázané na každou trasu, abych je mohl předat metodě renderování @skatejs/ssr v node.js. Každý „view“ obsahuje šablonu zapouzdřenou ShadowDOM. Tato šablona obsahuje všechny prvky na stránce. Rozhodl jsem se před produkčním sestavením spojit vlastní prvky s Rollup a poté importovat zdrojový kód každého do souboru, který obsahuje middleware.

Chtěl jsem dynamicky vykreslit každý pohled. V novém balíčku jsem exportoval jednoduchou konfiguraci pro interpretaci middlewaru node.js.


const routes = [
    { path: '/', component: HomeComponent },
    { path: '/about', component: AboutComponent }
];

export { routes };

U jednostránkové aplikace byste obvykle poskytli index.html na každý požadavek, ale protože nyní vykreslujeme na straně serveru, musíme vytvořit nějaký middleware pro zpracování stejných požadavků. Místo statického html odpoví server webovými komponentami generovanými na straně serveru.

import ssr from "./middleware/ssr";

// app.get("/*", (req, res) => {
//   res.sendFile(path.resolve(process.cwd(), "dist", "client", "index.html"));
// });

app.get("/*", ssr);

Middleware je ve srovnání s frameworky JS vlastně docela jednoduchý. Parcel se v mém projektu zabývá sdružováním a optimalizací, takže v tomto middlewaru jsem četl index.html Parcel zkompilovaný. Kód serveru je umístěn v adresáři sourozence klienta. Po importu JavaScriptu, který tvoří pohled, zavolám render, předám výslednou šablonu do HTML šablony indexu a odešlem odpověď klientovi s vykreslenými vlastními prvky na straně serveru.


require('@skatejs/ssr/register');
const render = require('@skatejs/ssr');

const url = require("url");
const path = require("path");
const fs = require("fs");

const { routes } = require('./../view/index.js');

const indexPath = path.resolve(process.cwd(), "dist", "client", "index.html");
const dom = fs.readFileSync(indexPath).toString();

export default async (req, res) => {
    let template = class {};
    template = routes.find(route => route.path === url.parse(req.url).pathname).component;
    if (template) {
        render(new template()).then((tmpl) => {
            const index = dom.replace(`<div id="root"></div>`, `<div id="root">${tmpl}</div>`)
                              .replace(/__ssr\(\)/g, '');
            res.send(index);
        })
    } else {
        res.send(dom);
    }

}

V příkladu chybí určitá logika, jako je přesměrování, když trasa neexistuje. Toto je jednoduchý důkaz konceptu. Z nějakého důvodu @skatejs/ssr balíček stále vkládal volání na __ssr funkce, která na klientovi neexistuje, takže jsem ji musel před odesláním šablony klientovi vymazat, jinak prohlížeč hlásí chybu.

Vykreslená webová komponenta je vložena do stejného uzlu DOM, do kterého směrovač na straně klienta vkládá vlastní prvek.

@skatejs/ssr udělá něco trochu zvláštního a zabalí obsah Shadow DOM do shadowroot tag.

To je v pořádku, protože směrovač na straně klienta se okamžitě spustí, nahradí prvek ve stejném kontejneru a vykreslí příslušný shadow-root v DOM.

V poslední době jsem vyvíjel nějaký startovací kód pro vytváření aplikací pomocí Readymade, mikro knihovny pro webové komponenty. To mě přimělo zjistit, jak implementovat směrování a vykreslování na straně serveru pomocí webových komponent. Píše se rok 2020 a doufal jsem, že vytáhnu z regálu nějaké balíčky, abych to dokončil, ale musel jsem implementovat router na straně klienta, aby to fungovalo hladce. Možná jsem mohl použít balíček sk-router, ale při první kontrole jsem nebyl ohromen jeho zdáním reagovat-router. To je v pořádku. Chvíli jsem chtěl přijít na to, jak implementovat router s vanilla JS. Existují také určité zvláštnosti při vykreslování vlastních prvků pomocí ShadowDOM, ale je to možné, na rozdíl od všeobecného mínění.

Miluji, když se mi říká, že něco nejde. 😎

Zdrojový kód je zde.

Pokud jste našli jiný způsob, jak vykreslit server ShadowDOM nebo máte nějaké postřehy nebo dotazy ohledně vykreslování webových komponent na straně serveru, podělte se prosím v komentářích níže.