Webösszetevők használata a Next-el (vagy bármely SSR-keretrendszerrel)

Az én előző poszt megnéztük a Shoelace-t, amely egy olyan komponenskönyvtár, amely UX komponensek teljes csomagját tartalmazza, amelyek gyönyörűek, hozzáférhetőek és – talán váratlanul – beépítettek Web összetevők. Ez azt jelenti, hogy bármilyen JavaScript keretrendszerrel használhatók. Míg a React webkomponensének interoperabilitása jelenleg kevésbé ideális, vannak megoldások.

A Web Components egyik komoly hiányossága azonban az, hogy jelenleg nem támogatják a szerveroldali renderinget (SSR). Van valami, amit Declarative Shadow DOM-nak (DSD) hívnak, de a jelenlegi támogatás meglehetősen minimális, és tulajdonképpen a webszerver vásárlására van szükség ahhoz, hogy speciális jelöléseket adjon ki a DSD-hez. Jelenleg folyik a munka Next.js hogy alig várom. Ebben a bejegyzésben azonban megvizsgáljuk, hogyan kezelhetjük a webes összetevőket bármely SSR-keretrendszerből, például a Next.js-ből, Ma.

Nem triviális mennyiségű kézi munkát végzünk majd, és némileg rontja oldalunk indulási teljesítményét a folyamat során. Ezután megvizsgáljuk, hogyan lehet minimalizálni ezeket a teljesítményköltségeket. De ne tévedjen: ez a megoldás nem kompromisszumok nélküli, ezért ne számítson másra. Mindig mérje meg és profilozza meg.

A probléma

Mielőtt belemerülnénk, szánjunk egy pillanatot, és magyarázzuk el a problémát. Miért nem működnek jól a webkomponensek a szerveroldali rendereléssel?

Az olyan alkalmazás-keretrendszerek, mint a Next.js, a React kódot veszik, és egy API-n keresztül futtatják, hogy lényegében „karakterizálják”, vagyis az összetevőket egyszerű HTML-vé alakítja. Így a React komponens fa a webalkalmazást tároló szerveren jelenik meg, és ez a HTML a webalkalmazás HTML-dokumentumának többi részével együtt elküldésre kerül a felhasználó böngészőjének. A HTML mellett néhány címkéket, amelyek betöltik a Reactot, valamint az összes React-összetevő kódját. Amikor egy böngésző ezeket feldolgozza címkéket, a React újra előállítja az összetevőfát, és összeegyezteti a dolgokat az elküldött SSR-HTML-kóddal. Ezen a ponton az összes effekt futni kezd, az eseménykezelők összekapcsolódnak, és az állapot valójában… állapotot tartalmaz. Ezen a ponton válik a webalkalmazás interaktív. Meghívásra kerül az a folyamat, amelynek során újra feldolgozzák a komponensfát az ügyfélen, és bekötnek mindent hidratáció.

Tehát mi köze ennek a webkomponensekhez? Nos, amikor renderelsz valamit, mondd ugyanazt a Cipőfűzőt komponens, amelyet meglátogattunk utoljára:


   General 
   Custom 
   Advanced 
   Disabled 

  This is the general tab panel.
  This is the custom tab panel.
  This is the advanced tab panel.
  This is a disabled tab panel.

…Reagálj (vagy őszintén bármilyen JavaScript keretrendszer) látni fogja ezeket a címkéket, és egyszerűen továbbadja őket. A React (vagy Svelte vagy Solid) nem felelős azért, hogy ezeket a címkéket szépen formázott lapokká alakítsák. Ennek kódja el van rejtve bármilyen kódban, amely meghatározza ezeket a webes összetevőket. Esetünkben ez a kód a Cipőfűző könyvtárban található, de a kód bárhol lehet. Ami fontos az amikor a kód fut.

Általában az ezeket a webes összetevőket regisztráló kódot a program behúzza az alkalmazás normál kódjába JavaScripten keresztül import. Ez azt jelenti, hogy ez a kód bekerül a JavaScript-kötegbe, és lefut a hidratálás során, ami azt jelenti, hogy aközött, hogy a felhasználó először látja az SSR'd HTML-kódot, és a hidratálás megtörténik, ezek a lapok (vagy bármely webkomponens) nem a megfelelő tartalmat jelenítik meg. . Ezután, amikor a hidratálás megtörténik, a megfelelő tartalom jelenik meg, ami valószínűleg azt eredményezi, hogy a webkomponensek körüli tartalom elmozdul, és illeszkedik a megfelelően formázott tartalomhoz. Ezt a stílustalan tartalom villanása, vagy FOUC. Elméletileg ezek közé jelölést is beilleszthet címkéket, hogy illeszkedjenek a kész kimenethez, de ez a gyakorlatban lehetetlen, különösen egy külső féltől származó komponenskönyvtárak esetében, mint a Shoelace.

Webkomponens regisztrációs kódunk áthelyezése

Tehát a probléma az, hogy a kód, amely arra készteti a webes összetevőket, hogy megtegye, amit kell, valójában nem fut le, amíg a hidratáció meg nem történik. Ebben a bejegyzésben megvizsgáljuk, hogy a kódot mihamarabb lefutjuk; azonnal, sőt. Megvizsgáljuk a webkomponens kódunk egyéni kötegelését, és egy szkript manuális hozzáadását közvetlenül a dokumentumunkhoz. így azonnal lefut, és addig blokkolja a dokumentum többi részét. Ez általában szörnyű dolog. A szerveroldali renderelés lényege az nem letiltja oldalunk feldolgozását, amíg a JavaScript feldolgozása meg nem történik. De ha ez megtörtént, ez azt jelenti, hogy mivel a dokumentum kezdetben a HTML-kódunkat jeleníti meg a szerverről, a webösszetevők regisztrálásra kerülnek, és azonnal és szinkronban a megfelelő tartalmat bocsátják ki.

A mi esetünkben mi éppen Webkomponens regisztrációs kódunkat blokkoló szkriptben szeretné futtatni. Ez a kód nem hatalmas, és igyekszünk jelentősen csökkenteni a teljesítményt azáltal, hogy hozzáadunk néhány gyorsítótár-fejlécet a későbbi látogatások megkönnyítésére. Ez nem tökéletes megoldás. Amikor egy felhasználó először böngészi az oldalt, a program mindig letiltja a szkriptfájl betöltését. A későbbi látogatások szépen gyorsítótáraznak, de ez a kompromisszum Lehet, hogy nem megvalósítható az Ön számára – e-kereskedelem, bárki? Mindenesetre profilozzon, mérjen, és hozza meg a megfelelő döntést az alkalmazás számára. Emellett a jövőben teljesen lehetséges, hogy a Next.js teljes mértékben támogatja a DSD-t és a webes összetevőket.

Az első lépések

Az összes kód, amit nézni fogunk, benne van ezt a GitHub repót és a itt telepítve a Vercellel. A webalkalmazás megjelenít néhány cipőfűző-összetevőt olyan szöveggel együtt, amely hidratáláskor megváltoztatja a színét és a tartalmát. Látnia kell, hogy a szöveg „Hydrated”-re változik, és a cipőfűző összetevői már megfelelően jelennek meg.

Egyéni kötegelt webkomponens kód

Első lépésünk egyetlen JavaScript-modul létrehozása, amely importálja az összes webösszetevő-definíciónkat. Az általam használt cipőfűző-összetevők esetében a kódom így néz ki:

import { setDefaultAnimation } from "@shoelace-style/shoelace/dist/utilities/animation-registry";

import "@shoelace-style/shoelace/dist/components/tab/tab.js";
import "@shoelace-style/shoelace/dist/components/tab-panel/tab-panel.js";
import "@shoelace-style/shoelace/dist/components/tab-group/tab-group.js";

import "@shoelace-style/shoelace/dist/components/dialog/dialog.js";

setDefaultAnimation("dialog.show", {
  keyframes: [
    { opacity: 0, transform: "translate3d(0px, -20px, 0px)" },
    { opacity: 1, transform: "translate3d(0px, 0px, 0px)" },
  ],
  options: { duration: 250, easing: "cubic-bezier(0.785, 0.135, 0.150, 0.860)" },
});
setDefaultAnimation("dialog.hide", {
  keyframes: [
    { opacity: 1, transform: "translate3d(0px, 0px, 0px)" },
    { opacity: 0, transform: "translate3d(0px, 20px, 0px)" },
  ],
  options: { duration: 250, easing: "cubic-bezier(0.785, 0.135, 0.150, 0.860)" },
});

Betölti a definíciókat a és a összetevőket, és felülír néhány alapértelmezett animációt a párbeszédpanelen. Elég egyszerű. De az az érdekes, hogy ezt a kódot bejuttatjuk az alkalmazásunkba. Mi nem tud egyszerűen import ezt a modult. Ha ezt megtennénk, akkor a normál JavaScript-kötegeinkbe kerülne, és a hidratálás során futna. Ez okozná azt a FOUC-ot, amelyet megpróbálunk elkerülni.

Noha a Next.js számos webpack hook-ot tartalmaz a dolgok egyéni csomagolásához, én használni fogom életek helyette. Először telepítse a npm i vite majd hozzon létre a vite.config.js fájlt. Az enyém így néz ki:

import { defineConfig } from "vite";
import path from "path";

export default defineConfig({
  build: {
    outDir: path.join(__dirname, "./shoelace-dir"),
    lib: {
      name: "shoelace",
      entry: "./src/shoelace-bundle.js",
      formats: ["umd"],
      fileName: () => "shoelace-bundle.js",
    },
    rollupOptions: {
      output: {
        entryFileNames: `[name]-[hash].js`,
      },
    },
  },
});

Ezzel létrejön egy kötegfájl a webkomponens-definíciókkal shoelace-dir mappát. Tegyük át a public mappát, hogy a Next.js kiszolgálja. És nyomon kell követnünk a fájl pontos nevét is, a végén a hash-sel. Itt van egy csomóponti szkript, amely mozgatja a fájlt, és ír egy JavaScript modult, amely egy egyszerű konstanst exportál a kötegfájl nevével (ez hamarosan hasznos lesz):

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

const shoelaceOutputPath = path.join(process.cwd(), "shoelace-dir");
const publicShoelacePath = path.join(process.cwd(), "public", "shoelace");

const files = fs.readdirSync(shoelaceOutputPath);

const shoelaceBundleFile = files.find(name => /^shoelace-bundle/.test(name));

fs.rmSync(publicShoelacePath, { force: true, recursive: true });

fs.mkdirSync(publicShoelacePath, { recursive: true });
fs.renameSync(path.join(shoelaceOutputPath, shoelaceBundleFile), path.join(publicShoelacePath, shoelaceBundleFile));
fs.rmSync(shoelaceOutputPath, { force: true, recursive: true });

fs.writeFileSync(path.join(process.cwd(), "util", "shoelace-bundle-info.js"), `export const shoelacePath = "/shoelace/${shoelaceBundleFile}";`);

Íme egy kísérő npm szkript:

"bundle-shoelace": "vite build && node util/process-shoelace-bundle",

Ennek működnie kell. Nekem, util/shoelace-bundle-info.js most létezik, és így néz ki:

export const shoelacePath = "/shoelace/shoelace-bundle-a6f19317.js";

A szkript betöltése

Menjünk a Next.js-be _document.js fájlt, és húzza be a webkomponens csomagfájlunk nevét:

import { shoelacePath } from "../util/shoelace-bundle-info";

Ezután kézzel rendereljük a címke a . Itt van az egész _document.js a fájl így néz ki:

import { Html, Head, Main, NextScript } from "next/document";
import { shoelacePath } from "../util/shoelace-bundle-info";

export default function Document() {
  return (
    
      
        
      
      
        
); }

És ennek működnie kell! Cipőfűző regisztrációnk blokkoló szkriptben töltődik be, és azonnal elérhető lesz, amint oldalunk feldolgozza a kezdeti HTML-t.

A teljesítmény javítása

Hagyhatnánk a dolgokat úgy, ahogy vannak, de tegyük hozzá a Cipőfűző-csomag gyorsítótárazását. Megmondjuk a Next.js-nek, hogy tegye ezeket a cipőfűző-csomagokat gyorsítótárazhatóvá a következő bejegyzés hozzáadásával a Next.js konfigurációs fájlunkhoz:

async headers() {
  return [
    {
      source: "/shoelace/shoelace-bundle-:hash.js",
      headers: [
        {
          key: "Cache-Control",
          value: "public,max-age=31536000,immutable",
        },
      ],
    },
  ];
}

Most, a későbbi oldalunk böngészésekor azt látjuk, hogy a cipőfűző-köteg szépen gyorsítótárazott!

A DevTools Források panelje megnyílik, és a betöltött cipőfűző-csomagot mutatja.
Webösszetevők használata a Next-el (vagy bármely SSR-keretrendszerrel)

Ha a cipőfűző csomagunk valaha megváltozik, a fájl neve megváltozik (a :hash részét a fenti forrástulajdonságból), a böngésző megállapítja, hogy nincs gyorsítótárban az adott fájl, és egyszerűen frissen kéri a hálózatról.

Csomagolta

Ez sok kézi munkának tűnhetett; és az volt. Sajnálatos, hogy a webkomponensek nem kínálnak jobb, azonnali támogatást a szerveroldali megjelenítéshez.

De nem szabad megfeledkeznünk az általuk nyújtott előnyökről: jó, hogy olyan minőségi UX komponenseket használhatunk, amelyek nincsenek egy adott keretrendszerhez kötve. Nagyon jó, hogy vadonatúj keretrendszerekkel kísérletezhetünk, mint pl Szilárd, anélkül, hogy meg kellene találnia (vagy össze kellene törnie) valamilyen lapot, modálist, automatikus kiegészítést vagy bármilyen összetevőt.

Időbélyeg:

Még több CSS trükkök