Uso de componentes web con Next (o cualquier marco SSR) | consejos CSS

En mi artículo anterior, analizamos Shoelace, que es una biblioteca de componentes con un conjunto completo de componentes UX que son hermosos, accesibles y, quizás inesperadamente, creados con componentes web. Esto significa que se pueden usar con cualquier marco de JavaScript. Aunque la interoperabilidad de los componentes web de React es, en la actualidad, menos que ideal, existen soluciones alternativas.

Pero una deficiencia grave de los componentes web es su falta actual de compatibilidad con la representación del lado del servidor (SSR). Hay algo llamado Declarative Shadow DOM (DSD) en proceso, pero su soporte actual es bastante mínimo y en realidad requiere que su servidor web opte por emitir un marcado especial para DSD. Actualmente hay trabajo en progreso para Next.js que no puedo esperar para ver. Pero para este artículo, veremos cómo administrar componentes web desde cualquier marco SSR, como Next.js, Este Dia.

Terminaremos haciendo una buena cantidad de trabajo manual, y levemente dañar el rendimiento de inicio de nuestra página en el proceso. Luego veremos cómo minimizar estos costos de rendimiento. Pero no se equivoque: esta solución no está exenta de compromisos, así que no espere nada más. Mide y perfila siempre.

El problema

Antes de sumergirnos, tomemos un momento y expliquemos el problema. ¿Por qué los componentes web no funcionan bien con la representación del lado del servidor?

Los marcos de aplicaciones como Next.js toman el código React y lo ejecutan a través de una API para "encadenarlo", lo que significa que convierte sus componentes en HTML sin formato. Por lo tanto, el árbol de componentes de React se representará en el servidor que aloja la aplicación web y ese HTML se enviará junto con el resto del documento HTML de la aplicación web al navegador de su usuario. Con este código HTML, hay algunos <script> etiquetas que cargan React, así como el código para todos sus componentes de React. Cuando un navegador procesa estos <script> etiquetas, React representará el árbol de componentes y hará coincidir los elementos con el SSR HTML enviado. En este punto, todos los efectos comenzarán a ejecutarse, los controladores de eventos se conectarán y el estado en realidad contendrá... estado. Es en este punto que la aplicación web se convierte en interactivo. El proceso de reprocesar su árbol de componentes en el cliente y conectar todo se llama hidratación.

Entonces, ¿qué tiene esto que ver con los componentes web? Bueno, cuando devuelvas algo, di lo mismo <sl-tab-group> componente que visitamos la última vez:

<sl-tab-group ref="{tabsRef}">
  <sl-tab slot="nav" panel="general"> General </sl-tab>
  <sl-tab slot="nav" panel="custom"> Custom </sl-tab>
  <sl-tab slot="nav" panel="advanced"> Advanced </sl-tab>
  <sl-tab slot="nav" panel="disabled" disabled> Disabled </sl-tab>

  <sl-tab-panel name="general">This is the general tab panel.</sl-tab-panel>
  <sl-tab-panel name="custom">This is the custom tab panel.</sl-tab-panel>
  <sl-tab-panel name="advanced">This is the advanced tab panel.</sl-tab-panel>
  <sl-tab-panel name="disabled">This is a disabled tab panel.</sl-tab-panel>
</sl-tab-group>

…reaccionar (o honestamente no importa cual Framework JavaScript) verá estas etiquetas y simplemente las pasará. React (o Svelte o Solid) no es responsable de convertir estas etiquetas en pestañas bien formateadas. El código para esto está oculto dentro de cualquier código que tenga que defina estos componentes web. En nuestro caso, este código está en la biblioteca de Shoelace, pero podría estar en cualquier parte. lo importante es cuando se ejecuta el código.

Normalmente, el código que registra estos componentes web se insertará en el código normal de su aplicación a través de JavaScript. import. Esto significa que este código terminará en su paquete de JavaScript y se ejecutará durante la hidratación, lo que significa que entre la primera vez que su usuario ve el HTML SSR y la hidratación, esas pestañas (o cualquier componente web, por cierto) no mostrarán el contenido. correcto. Luego, cuando se produzca la hidratación, se mostrará el contenido correcto, lo que probablemente hará que el contenido se mueva entre esos componentes web y se ajuste al contenido con el formato adecuado. Esto se conoce como flash de contenido sin estiloo FOUC. En teoría, podría pegar marcas entre todos estos <sl-tab-xyz> etiquetas para que coincidan con el resultado final, pero esto es casi imposible en la práctica, especialmente para una biblioteca de componentes de terceros como Shoelace.

Moviendo nuestro código de registro de componente web

Entonces, el problema es que el código que permite que los componentes web hagan lo que deben hacer no se ejecutará hasta que se haya producido la hidratación. Para este artículo, veremos cómo ejecutar este código anteriormente; inmediatamente, de hecho. Veremos la agrupación personalizada de nuestro código de componente web y la adición manual de secuencias de comandos directamente a nuestro documento. <head> por lo que se ejecuta inmediatamente y bloquea el resto del documento hasta que lo haga. Esto es normalmente algo terrible de hacer. El objetivo de la representación del lado del servidor es no bloquear el procesamiento de nuestra página hasta que se procese nuestro JavaScript. Pero una vez hecho esto, significa que debido a que el documento inicialmente representa nuestro HTML desde el servidor, los componentes web se registrarán e inmediatamente y sincrónicamente emitirán el contenido correcto.

En nuestro caso, somos sólo buscando ejecutar nuestro código de registro de componente web en un script de bloqueo. Este código no es enorme y buscaremos reducir drásticamente el rendimiento agregando encabezados de caché para facilitar las futuras visitas. No es una solución perfecta. La primera vez que un usuario navega a su página siempre se bloqueará mientras se carga este archivo de script. Las visitas posteriores se ocultarán bien, pero este compromiso puede que no ser factible para usted - comercio electrónico, alguien? De cualquier manera, perfile, mida y tome la decisión correcta para su aplicación. Además, en el futuro es muy posible que Next.js sea totalmente compatible con DSD y componentes web.

Comenzar

Todo el código que vamos a ver está en este repositorio de GitHub y se implementó aquí con Vercel. La aplicación web representa algunos componentes de Shoelace con texto que cambia de color y contenido al hidratarse. Debería poder ver el cambio de texto a "Hidratado", con los componentes Shoelace ya representados correctamente.

Código de componente web de agrupación personalizada

Nuestro primer paso es crear un único módulo de JavaScript que importe todas nuestras definiciones de componentes web. Para los componentes de Shoelace que uso, mi código se ve así:

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)" },
});

Carga las definiciones de <sl-tab-group> y <sl-dialog> componentes y reemplaza algunas animaciones predeterminadas para el cuadro de diálogo. Suficientemente simple. Pero lo interesante aquí es integrar este código en nuestra aplicación. Nosotros no puedo meramente import este módulo. Si hiciéramos eso, se incluiría en nuestros paquetes de JavaScript normales y funcionaría mientras se hidrata. Esto provocaría el FOUC que estamos tratando de evitar.

Aunque Next.js tiene una serie de ganchos de paquete web para agrupar cosas personalizadas, usaré Vite en su lugar. Primero, instálelo con npm i vite luego crea un vite.config.js carpetas. El mío se ve así:

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`,
      },
    },
  },
});

Esto creará un archivo de paquete con nuestras definiciones de componentes web en el shoelace-dir carpetas. Vamos a moverlo a la public carpeta para que Next.js la sirva. Y también debemos hacer un seguimiento del nombre exacto del archivo, con el hash al final. Aquí hay una secuencia de comandos de Node que mueve el archivo y escribe un módulo de JavaScript que exporta una constante simple con el nombre del archivo del paquete (esto será útil en breve):

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}";`);

Aquí hay un script npm complementario:

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

Deberia de funcionar. Para mí, util/shoelace-bundle-info.js ahora existe y se ve así:

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

Cargando el guión

Pasemos a Next.js _document.js y extraiga el nombre de nuestro archivo de paquete de componentes web:

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

Luego renderizamos manualmente un <script> faro en el <head>. Este es mi set _document.js el archivo se parece a:

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

export default function Document() {
  return (
    <Html>
      <Head>
        <script src={shoelacePath}></script>
      </Head>
      <body>
        <Main />
        <NextScript />
      </body>
    </Html>
  );
}

¡Y debería funcionar! Nuestro registro de Shoelace se cargará en un script de bloqueo y estará disponible inmediatamente cuando nuestra página procese el código HTML inicial.

Mejora del rendimiento

Podríamos dejar las cosas como están, pero agregar almacenamiento en caché para nuestro paquete Shoelace. Le indicaremos a Next.js que haga que estos paquetes de Shoelace se puedan almacenar en caché agregando la siguiente entrada a nuestro archivo de configuración de Next.js:

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

Ahora, en navegaciones posteriores en nuestro sitio, ¡vemos que el paquete Shoelace se almacena bien en caché!

El panel Fuentes de DevTools está abierto y muestra el paquete Shoelace cargado.

Si nuestro paquete Shoelace cambia, el nombre del archivo cambiará (a través de la :hash parte de la propiedad de origen anterior), el navegador encontrará que no tiene este archivo almacenado en caché y simplemente lo solicitará de la red.

Envolver

Puede haber parecido mucho trabajo manual; y fue. Es lamentable que los componentes web no ofrezcan un mejor soporte listo para usar para la representación del lado del servidor.

Pero no debemos olvidar las ventajas que aportan: es bueno poder utilizar componentes UX de calidad que no estén atados a un framework específico. También es bueno poder experimentar con nuevos marcos, como Solid, sin necesidad de encontrar (o piratear juntos) algún tipo de pestaña, modal, autocompletar o cualquier otro componente.

Si quieres conocer otros artículos parecidos a Uso de componentes web con Next (o cualquier marco SSR) | consejos CSS puedes visitar la categoría Estilo.

Deja un comentario

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *

Subir

Esta página web utiliza cookies para analizar de forma anónima y estadística el uso que haces de la web, mejorar los contenidos y tu experiencia de navegación. Para más información accede a la Política de Cookies . Ver mas