שימוש ברכיבי אינטרנט עם Next (או כל מסגרת SSR)

בי הפוסט הקודם הסתכלנו על Shoelace, שהיא ספריית רכיבים עם חבילה מלאה של רכיבי UX שהם יפים, נגישים, ואולי באופן בלתי צפוי - בנויים עם רכיבי רשת. זה אומר שניתן להשתמש בהם עם כל מסגרת JavaScript. אמנם יכולת הפעולה ההדדית של רכיבי האינטרנט של React היא, כיום, פחות אידיאלית, יש דרכים לעקיפת הבעיה.

אבל חסרון רציני אחד של רכיבי אינטרנט הוא חוסר התמיכה הנוכחי שלהם בעיבוד צד השרת (SSR). יש משהו שנקרא Declarative Shadow DOM (DSD) בעבודה, אבל התמיכה הנוכחית בו היא די מינימלית, והיא למעשה דורשת רכישה משרת האינטרנט שלך כדי לפלוט סימון מיוחד עבור ה-DSD. כרגע מתבצעת עבודה עבור Next.js שאני מצפה לראות. אבל עבור פוסט זה, נבחן כיצד לנהל רכיבי אינטרנט מכל מסגרת SSR, כמו Next.js, היום.

נסיים לעשות כמות לא טריוויאלית של עבודה ידנית, ו מעט פגיעה בביצועי ההפעלה של הדף שלנו בתהליך. לאחר מכן נבחן כיצד למזער את עלויות הביצועים הללו. אבל אל תטעו: הפתרון הזה אינו חף מפשרות, אז אל תצפו אחרת. תמיד למדוד ולעשות פרופיל.

הבעיה

לפני שנצלול פנימה, בואו ניקח רגע ונסביר למעשה את הבעיה. מדוע רכיבי אינטרנט אינם עובדים היטב עם רינדור בצד השרת?

מסגרות יישומים כמו Next.js לוקחות את קוד React ומריצות אותו דרך ממשק API כדי למעשה "מחרוזת" אותו, כלומר, זה הופך את הרכיבים שלך ל-HTML פשוט. אז עץ הרכיבים של React יוצג בשרת המארח את אפליקציית האינטרנט, וה-HTML הזה יישלח עם שאר מסמך ה-HTML של אפליקציית האינטרנט לדפדפן המשתמש שלך. יחד עם HTML זה יש כמה תגים שטוענים את React, יחד עם הקוד של כל רכיבי React שלך. כאשר דפדפן מעבד את אלה תגיות, React תעבד מחדש את עץ הרכיבים, ותתאים דברים ל-SSR'd HTML שנשלח למטה. בשלב זה, כל האפקטים יתחילו לפעול, מטפלי האירועים יתחברו, והמצב למעשה... יכיל מצב. בנקודה זו הופכת אפליקציית האינטרנט אינטראקטיבי. התהליך של עיבוד מחדש של עץ הרכיבים שלך בלקוח, וחיווט הכל נקרא הִידרָצִיָה.

אז מה זה קשור לרכיבי אינטרנט? ובכן, כשאתה מעבד משהו, אמור את אותו שרוך רכיב בו ביקרנו הפעם האחרונה:


   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.

...הגיב (או בכנות כל JavaScript framework) יראה את התגים האלה ופשוט יעביר אותם הלאה. React (או Svelte, או Solid) אינם אחראים להפיכת התגים הללו לכרטיסיות בפורמט יפה. הקוד עבור זה מוחבא בתוך כל הקוד שיש לך שמגדיר את רכיבי האינטרנט האלה. במקרה שלנו, הקוד הזה נמצא בספריית Shoelace, אבל הקוד יכול להיות בכל מקום. מה שחשוב זה כאשר הקוד פועל.

בדרך כלל, הקוד הרושם את רכיבי האינטרנט הללו יימשך אל הקוד הרגיל של היישום שלך באמצעות JavaScript import. זה אומר שהקוד הזה יתפוס בחבילת ה-JavaScript שלך ויתבצע במהלך הידרציה, מה שאומר שבין שהמשתמש שלך יראה לראשונה את ה-SSR'd HTML לבין הידרציה מתרחשת, הכרטיסיות האלה (או כל רכיב אינטרנט לצורך העניין) לא יציגו את התוכן הנכון . לאחר מכן, כאשר מתרחשת הידרציה, התוכן המתאים יוצג, וסביר להניח שיגרום לתוכן סביב רכיבי האינטרנט הללו לנוע ולהתאים לתוכן המעוצב כהלכה. זה ידוע בתור א הבזק של תוכן לא מעוצב, או FOUC. בתיאוריה, אתה יכול להדביק סימון בין כל אלה תגים שיתאימו לפלט המוגמר, אבל זה כמעט בלתי אפשרי בפועל, במיוחד עבור ספריית רכיבים של צד שלישי כמו Shoelace.

העברת קוד הרישום של רכיב האינטרנט שלנו

אז הבעיה היא שהקוד שיגרום לרכיבי אינטרנט לעשות את מה שהם צריכים לעשות לא יפעל עד שתתרחש הידרציה. עבור פוסט זה, נבחן את הפעלת הקוד מוקדם יותר; באופן מיידי, למעשה. נסתכל על שילוב מותאם אישית של קוד רכיב האינטרנט שלנו, והוספה ידנית של סקריפט ישירות למסמך שלנו כך שהוא פועל באופן מיידי וחוסם את שאר המסמך עד שהוא פועל. זה בדרך כלל דבר נורא לעשות. כל העניין של עיבוד בצד השרת הוא ל לֹא לחסום את הדף שלנו מעיבוד עד שה-JavaScript שלנו יעובד. אבל ברגע שזה נעשה, זה אומר שכאשר המסמך מעבד בתחילה את ה-HTML שלנו מהשרת, רכיבי האינטרנט יירשמו ויפלטו באופן מיידי וגם סינכרוני את התוכן הנכון.

במקרה שלנו, אנחנו רק מחפש להפעיל את קוד הרישום של רכיב האינטרנט שלנו בסקריפט חוסם. הקוד הזה אינו ענק, ואנו ננסה להפחית משמעותית את פגיעה בביצועים על ידי הוספת כמה כותרות מטמון שיעזרו בביקורים הבאים. זה לא פתרון מושלם. הפעם הראשונה שמשתמש גולש בדף שלך תמיד תחסם בזמן שקובץ הסקריפט הזה נטען. הביקורים הבאים יישמרו יפה, אבל הפשרה הזו אולי לא להיות אפשרי עבורך - מסחר אלקטרוני, מישהו? בכל מקרה, פרופיל, מדוד וקבל את ההחלטה הנכונה עבור האפליקציה שלך. חוץ מזה, בעתיד זה בהחלט אפשרי Next.js יתמוך באופן מלא ב-DSD וברכיבי אינטרנט.

תחילת עבודה

כל הקוד שנבחן נמצא בתוכו ריפו GitHub זה ו פרוס כאן עם Vercel. אפליקציית האינטרנט מעבדת כמה מרכיבי שרוך נעליים יחד עם טקסט שמשנה צבע ותוכן עם הידרציה. אתה אמור להיות מסוגל לראות את הטקסט משתנה ל-"Hydrated", כאשר רכיבי השרוך ​​כבר מעובדים כראוי.

קוד שילוב מותאם אישית של רכיב אינטרנט

הצעד הראשון שלנו הוא ליצור מודול JavaScript יחיד שמייבא את כל הגדרות רכיבי האינטרנט שלנו. עבור רכיבי השרוך ​​שבהם אני משתמש, הקוד שלי נראה כך:

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 מודול זה. אם היינו עושים את זה, זה היה נכלל בחבילות ה-JavaScript הרגילות שלנו ופועל במהלך הידרציה. זה יגרום ל-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 ישרת אותה. וכדאי גם לעקוב אחר השם המדויק של הקובץ, עם ה-hash בסוף שלו. הנה סקריפט Node שמזיז את הקובץ וכותב מודול JavaScript שמייצא קבוע פשוט עם שם קובץ החבילה (זה יהיה שימושי בקרוב):

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";

לאחר מכן אנו מעבדים באופן ידני את א התג ב- . הנה כל מה שלי _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:

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

כעת, בגלישה שלאחר מכן לאתר שלנו, אנו רואים את חבילת השרוך ​​מתאגרת יפה!

חלונית DevTools Sources נפתחת ומציגה את חבילת השרוכים הנטענת.
שימוש ברכיבי אינטרנט עם Next (או כל מסגרת SSR)

אם חבילת השרוכים שלנו תשתנה אי פעם, שם הקובץ ישתנה (דרך :hash חלק ממאפיין המקור שלמעלה), הדפדפן יגלה שאין לו את הקובץ הזה שמור, ופשוט יבקש אותו טרי מהרשת.

גלישה את

זה אולי נראה כמו הרבה עבודה ידנית; וזה היה. מצער שרכיבי אינטרנט אינם מציעים תמיכה טובה יותר מהקופסה עבור עיבוד בצד השרת.

אבל אל לנו לשכוח את היתרונות שהם מספקים: זה נחמד להיות מסוגל להשתמש ברכיבי UX איכותיים שאינם קשורים למסגרת ספציפית. זה נחמד להיות מסוגל להתנסות במסגרות חדשות לגמרי, כמו מוצק, בלי צורך למצוא (או לפרוץ יחד) איזושהי כרטיסייה, מודאלית, השלמה אוטומטית או כל רכיב.

בול זמן:

עוד מ טריקים של CSS