استفاده از اجزای وب با Next (یا هر چارچوب SSR)

من در پست قبلی ما به Shoelace نگاه کردیم، که یک کتابخانه مؤلفه با مجموعه کاملی از مؤلفه‌های UX است که زیبا، در دسترس هستند و - شاید به طور غیرمنتظره - با اجزای وب. این بدان معنی است که آنها می توانند با هر چارچوب جاوا اسکریپت استفاده شوند. در حالی که قابلیت همکاری Web Component React در حال حاضر کمتر از حد ایده آل است، راه حل هایی وجود دارد.

اما یکی از کاستی های جدی Web Component ها عدم پشتیبانی فعلی آنها از رندر سمت سرور (SSR) است. چیزی به نام Declarative Shadow DOM (DSD) در حال کار است، اما پشتیبانی فعلی از آن بسیار کم است، و در واقع نیاز به خرید از وب سرور شما برای انتشار نشانه‌گذاری ویژه برای DSD دارد. در حال حاضر کار در حال انجام است Next.js که منتظر دیدنش هستم اما برای این پست، نحوه مدیریت اجزای وب از هر چارچوب SSR، مانند Next.js، را بررسی خواهیم کرد. امروز.

ما به انجام یک مقدار غیر پیش پا افتاده کار دستی پایان خواهیم داد، و کمی صدمه زدن به عملکرد راه اندازی صفحه ما در این فرآیند. سپس نحوه به حداقل رساندن این هزینه های عملکرد را بررسی خواهیم کرد. اما اشتباه نکنید: این راه حل بدون معاوضه نیست، بنابراین غیر از این انتظار نداشته باشید. همیشه اندازه گیری و مشخصات.

مشکل

قبل از شیرجه رفتن، بیایید یک لحظه وقت بگذاریم و در واقع مشکل را توضیح دهیم. چرا Web Components با رندر سمت سرور به خوبی کار نمی کند؟

فریم ورک‌های برنامه‌ای مانند Next.js کد React را می‌گیرند و آن را از طریق یک API اجرا می‌کنند تا اساساً آن را «string» کنند، به این معنی که اجزای شما را به HTML ساده تبدیل می‌کند. بنابراین درخت کامپوننت React بر روی سرور میزبان برنامه وب رندر می‌شود و آن HTML به همراه بقیه سند HTML برنامه وب به مرورگر کاربر ارسال می‌شود. همراه با این HTML برخی از تگ هایی که React را به همراه کد تمام اجزای React بارگیری می کنند. وقتی مرورگر اینها را پردازش می کند تگ‌ها، React درخت مؤلفه را دوباره رندر می‌کند و موارد را با HTML SSR'd که ارسال شده است مطابقت می‌دهد. در این مرحله، تمام افکت‌ها شروع به اجرا می‌کنند، کنترل‌کننده‌های رویداد سیم‌کشی می‌شوند و حالت در واقع… حاوی حالت است. در این مرحله است که برنامه وب تبدیل می شود تعاملی. فرآیند پردازش مجدد درخت کامپوننت شما در کلاینت، و سیم کشی همه چیز نامیده می شود هیدراتاسیون.

بنابراین، این چه ربطی به اجزای وب دارد؟ خوب، وقتی چیزی را رندر می کنید، همان بند کفش را بگویید جزء که ما بازدید کردیم آخرین بار:


   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.

... واکنش نشان دهید (یا صادقانه هر چارچوب جاوا اسکریپت) آن تگ‌ها را می‌بیند و به سادگی آن‌ها را ارسال می‌کند. React (یا Svelte یا Solid) مسئول تبدیل آن برچسب ها به برگه هایی با فرمت زیبا نیست. کد مربوط به آن در داخل هر کدی که دارید که آن مؤلفه های وب را تعریف می کند، قرار می گیرد. در مورد ما، آن کد در کتابخانه Shoelace است، اما کد می تواند در هر جایی باشد. آنچه مهم است این است زمانی که کد اجرا می شود.

به طور معمول، کد ثبت کننده این اجزای وب از طریق یک جاوا اسکریپت به کد نرمال برنامه شما کشیده می شود. import. این بدان معناست که این کد در بسته جاوا اسکریپت شما جمع می‌شود و در حین هیدراتاسیون اجرا می‌شود، به این معنی که، بین اولین بار مشاهده SSR'd HTML توسط کاربر و انجام هیدراتاسیون، این برگه‌ها (یا هر مؤلفه وب برای آن موضوع) محتوای صحیح را ارائه نمی‌کند. . سپس، وقتی هیدراتاسیون اتفاق می‌افتد، محتوای مناسب نمایش داده می‌شود و احتمالاً باعث می‌شود که محتوای اطراف این مؤلفه‌های وب حرکت کند و با محتوای فرم‌بندی شده مناسب مطابقت داشته باشد. این به عنوان یک شناخته می شود فلش مطالب بدون استایل، یا FOUC. در تئوری، شما می توانید نشانه گذاری را در بین همه آنها قرار دهید برچسب ها برای مطابقت با خروجی تمام شده، اما این در عمل غیرممکن است، به خصوص برای کتابخانه مؤلفه های شخص ثالث مانند Shoelace.

انتقال کد ثبت کامپوننت وب ما

بنابراین مشکل اینجاست که کدی که برای وادار کردن Web Component ها به انجام کارهایی که باید انجام دهند عملاً تا زمانی که هیدراتاسیون اتفاق نیفتد اجرا نمی شود. برای این پست، اجرای کد را زودتر بررسی خواهیم کرد. در واقع بلافاصله ما به بسته‌بندی سفارشی کد مؤلفه وب و افزودن دستی یک اسکریپت مستقیم به سند خود نگاه خواهیم کرد. بنابراین بلافاصله اجرا می شود، و بقیه سند را تا زمانی که انجام نشود مسدود می کند. این معمولاً یک کار وحشتناک است. تمام هدف رندر سمت سرور این است که نه تا زمانی که جاوا اسکریپت ما پردازش نشود، صفحه ما را از پردازش مسدود کنید. اما پس از انجام، به این معنی است که از آنجایی که سند در ابتدا HTML ما را از سرور رندر می‌کند، مؤلفه‌های وب ثبت می‌شوند و هم بلافاصله و هم همزمان محتوای مناسب را منتشر می‌کنند.

در مورد ما، ما هستیم تنها به دنبال اجرای کد ثبت کامپوننت وب خود در یک اسکریپت مسدود کننده هستیم. این کد بزرگ نیست، و ما به دنبال آن هستیم که با افزودن برخی سرصفحه‌های حافظه پنهان برای کمک به بازدیدهای بعدی، ضربه عملکرد را به میزان قابل توجهی کاهش دهیم. این راه حل کاملی نیست اولین باری که کاربر صفحه شما را مرور می کند، همیشه در حالی که آن فایل اسکریپت بارگذاری می شود، مسدود می شود. بازدیدهای بعدی به خوبی ذخیره می شود، اما این مبادله است ممکن نیست برای شما امکان پذیر باشد - تجارت الکترونیک، کسی؟ به هر حال، مشخصات، اندازه گیری، و تصمیم درست برای برنامه خود را. علاوه بر این، در آینده کاملاً امکان پذیر است که Next.js به طور کامل از DSD و Web Component ها پشتیبانی کند.

شروع

همه کدهایی که به آنها نگاه خواهیم کرد در آن قرار دارند این مخزن GitHub و در اینجا با Vercel مستقر شده است. برنامه وب برخی از اجزای بند کفش را همراه با متنی ارائه می دهد که با آبرسانی رنگ و محتوا تغییر می کند. شما باید بتوانید تغییر متن به "Hydrated" را ببینید، در حالی که مولفه های بند کفش از قبل به درستی رندر شده اند.

بسته بندی سفارشی کد کامپوننت وب

اولین قدم ما ایجاد یک ماژول جاوا اسکریپت است که تمام تعاریف کامپوننت وب ما را وارد می کند. برای اجزای بند کفشی که من استفاده می کنم، کد من به این صورت است:

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

این تعاریف را بارگذاری می کند و اجزاء، و برخی از انیمیشن های پیش فرض را برای گفتگو لغو می کند. به اندازه کافی ساده اما قطعه جالب اینجا وارد کردن این کد به برنامه ما است. ما نمی توان به سادگی import این ماژول اگر این کار را انجام می‌دادیم، در بسته‌های معمولی جاوا اسکریپت ما قرار می‌گرفت و در حین هیدراتاسیون اجرا می‌شد. این باعث FOUC می شود که ما سعی می کنیم از آن اجتناب کنیم.

در حالی که Next.js تعدادی قلاب بسته وب برای بسته‌بندی سفارشی چیزها دارد، من از آن استفاده خواهم کرد سریعا بجای. ابتدا آن را با npm i vite و سپس ایجاد کنید vite.config.js فایل. مال من به این شکل است:

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

این یک فایل باندل با تعاریف مؤلفه وب ما در قسمت ایجاد می کند shoelace-dir پوشه بیایید آن را به public پوشه به طوری که Next.js به آن سرویس دهد. و همچنین باید نام دقیق فایل را با هش در انتهای آن پیگیری کنیم. در اینجا یک اسکریپت Node وجود دارد که فایل را جابجا می کند و یک ماژول جاوا اسکریپت می نویسد که یک ثابت ساده را با نام فایل بسته صادر می کند (این به زودی مفید خواهد بود):

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

در اینجا یک اسکریپت همراه npm آمده است:

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

که باید کار کند. برای من، util/shoelace-bundle-info.js اکنون وجود دارد و به شکل زیر است:

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

در حال بارگیری اسکریپت

بیایید به Next.js برویم _document.js فایل و نام فایل بسته نرم افزاری Web Component ما را وارد کنید:

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

سپس به صورت دستی a را رندر می کنیم برچسب در . در اینجا چیزی است که کل من است _document.js فایل به نظر می رسد:

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

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

و این باید کار کند! ثبت نام بند کفش ما در یک اسکریپت مسدود کننده بارگیری می شود و بلافاصله با پردازش صفحه ما HTML اولیه در دسترس خواهد بود.

بهبود عملکرد

می‌توانیم چیزها را همان‌طور که هستند رها کنیم، اما بیایید حافظه پنهان را برای بسته بند کفش خود اضافه کنیم. به Next.js می‌گوییم که با افزودن ورودی زیر به فایل پیکربندی Next.js، این بسته‌های Shoelace را در حافظه پنهان کند:

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

اکنون، در مرورهای بعدی سایت ما، می بینیم که بسته کفش بند کفش به خوبی در حافظه پنهان می شود!

پانل DevTools Sources باز می شود و بسته بند کفش بارگذاری شده را نشان می دهد.
استفاده از اجزای وب با Next (یا هر چارچوب SSR)

اگر بسته کفش ما تغییر کند، نام فایل تغییر خواهد کرد (از طریق :hash بخشی از ویژگی منبع بالا)، مرورگر متوجه می شود که آن فایل را در حافظه پنهان ندارد و به سادگی آن را از شبکه درخواست می کند.

پسگفتار

ممکن است این کار دستی زیادی به نظر برسد. و این بود. مایه تاسف است که Web Component ها پشتیبانی خارج از جعبه بهتری برای رندر سمت سرور ارائه نمی دهند.

اما نباید مزایایی را که آنها ارائه می‌کنند فراموش کنیم: خوب است که بتوان از اجزای UX با کیفیتی که به چارچوب خاصی وابسته نیستند استفاده کرد. خیلی خوب است که بتوانید با فریمورک های کاملاً جدید آزمایش کنید جامدبدون نیاز به یافتن (یا هک کردن) نوعی برگه، مودال، تکمیل خودکار یا هر جزء دیگر.

تمبر زمان:

بیشتر از ترفندهای CSS