استخدام مكونات الويب مع Next (أو أي إطار عمل SSR)

في بلدي السابقة آخر نظرنا إلى Shoelace ، وهي مكتبة مكونة تحتوي على مجموعة كاملة من مكونات UX الجميلة ، والتي يمكن الوصول إليها ، و- ربما بشكل غير متوقع- مبنية باستخدام مكونات الويب. هذا يعني أنه يمكن استخدامها مع أي إطار عمل JavaScript. في حين أن إمكانية التشغيل البيني لمكوِّن الويب الخاص بـ React ، في الوقت الحالي ، أقل من مثالية ، هناك حلول.

لكن أحد أوجه القصور الخطيرة في Web Components هو افتقارها الحالي إلى دعم العرض من جانب الخادم (SSR). هناك شيء يسمى Declarative Shadow DOM (DSD) قيد العمل ، لكن الدعم الحالي له ضئيل جدًا ، ويتطلب في الواقع شراءًا من خادم الويب الخاص بك لإصدار ترميز خاص لـ DSD. يجري العمل حاليًا من أجل Next.js التي أتطلع لرؤيتها. ولكن في هذا المنشور ، سننظر في كيفية إدارة مكونات الويب من أي إطار عمل SSR ، مثل Next.js ، اليوم.

سننتهي من القيام بقدر غير تافه من العمل اليدوي ، و قليلا الإضرار بأداء بدء تشغيل صفحتنا في هذه العملية. سننظر بعد ذلك في كيفية تقليل تكاليف الأداء هذه. لكن لا تخطئ: هذا الحل لا يخلو من المفاضلات ، لذلك لا تتوقع غير ذلك. دائما القياس والملف الشخصي.

المشكلة

قبل أن نتعمق ، دعنا نتوقف لحظة ونشرح المشكلة بالفعل. لماذا لا تعمل Web Components بشكل جيد مع العرض من جانب الخادم؟

تأخذ أطر التطبيقات مثل 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) سوف يرى تلك العلامات ويمررها ببساطة. React (أو Svelte أو Solid) ليست مسؤولة عن تحويل تلك العلامات إلى علامات تبويب جيدة التنسيق. يتم إخفاء الكود الخاص بذلك داخل أي كود لديك يحدد مكونات الويب هذه. في حالتنا ، هذا الرمز موجود في مكتبة Shoelace ، ولكن يمكن أن يكون الرمز في أي مكان. المهم هو عندما يعمل الكود.

عادةً ، سيتم سحب الكود الذي يقوم بتسجيل مكونات الويب هذه إلى الكود العادي للتطبيق الخاص بك عبر JavaScript import. هذا يعني أن هذا الرمز سينتهي به المطاف في حزمة JavaScript الخاصة بك ويتم تنفيذه أثناء الترطيب مما يعني أنه بين مشاهدة المستخدم لأول مرة لـ SSR'd HTML وحدث الترطيب ، فإن علامات التبويب هذه (أو أي مكون ويب لهذه المسألة) لن تعرض المحتوى الصحيح . بعد ذلك ، عندما يحدث الترطيب ، سيتم عرض المحتوى المناسب ، مما يتسبب على الأرجح في تحرك المحتوى حول مكونات الويب هذه وتناسب المحتوى المنسق بشكل صحيح. هذا هو المعروف باسم فلاش من المحتوى غير المصمم، أو FOUC. من الناحية النظرية ، يمكنك وضع العلامات بين كل هؤلاء علامات لمطابقة الإخراج النهائي ، ولكن هذا مستحيل من الناحية العملية ، خاصة بالنسبة لمكتبة مكونة تابعة لجهة خارجية مثل Shoelace.

نقل رمز تسجيل مكون الويب الخاص بنا

لذا فإن المشكلة تكمن في أن الكود الخاص بجعل مكونات الويب تفعل ما تحتاج إليه لن يتم تشغيله فعليًا حتى يحدث الترطيب. بالنسبة لهذا المنشور ، سننظر في تشغيل هذا الرمز عاجلاً ؛ على الفور ، في الواقع. سننظر في التجميع المخصص لرمز مكون الويب الخاص بنا ، وإضافة برنامج نصي يدويًا مباشرة إلى مستنداتنا لذلك يتم تشغيله على الفور ، ويمنع باقي المستند حتى يتم تشغيله. هذا عادة شيء فظيع لفعله. بيت القصيد من العرض من جانب الخادم هو ليس منع صفحتنا من المعالجة حتى تتم معالجة JavaScript. ولكن بمجرد الانتهاء من ذلك ، فهذا يعني أنه نظرًا لأن المستند يقوم في البداية بتقديم HTML الخاص بنا من الخادم ، فسيتم تسجيل مكونات الويب وستقوم على الفور وبصورة متزامنة بإرسال المحتوى الصحيح.

في حالتنا ، نحن م تتطلع إلى تشغيل رمز تسجيل مكون الويب الخاص بنا في برنامج نصي للحظر. هذا الرمز ليس ضخمًا ، وسوف نتطلع إلى تقليل الأداء بشكل ملحوظ عن طريق إضافة بعض رؤوس ذاكرة التخزين المؤقت للمساعدة في الزيارات اللاحقة. هذا ليس حلا مثاليا. في المرة الأولى التي يستعرض فيها المستخدم صفحتك ، سيتم حظرها دائمًا أثناء تحميل ملف البرنامج النصي. سيتم تخزين الزيارات اللاحقة بشكل جيد ، ولكن هذه المقايضة قد لا تكون مجدية بالنسبة لك - التجارة الإلكترونية ، أي شخص؟ على أي حال ، الملف الشخصي ، والقياس ، واتخاذ القرار الصحيح لتطبيقك. علاوة على ذلك ، من الممكن تمامًا في المستقبل أن يدعم Next.js DSD ومكونات الويب بشكل كامل.

بدء

كل الكود الذي سننظر فيه موجود هذا GitHub repo و منتشرة هنا مع Vercel. يعرض تطبيق الويب بعض مكونات رباط الحذاء جنبًا إلى جنب مع النص الذي يغير اللون والمحتوى عند الترطيب. يجب أن تكون قادرًا على رؤية تغيير النص إلى "رطب" ، مع عرض مكونات رباط الحذاء بشكل صحيح بالفعل.

رمز مكون ويب التجميع المخصص

خطوتنا الأولى هي إنشاء وحدة 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`,
      },
    },
  },
});

سيؤدي هذا إلى إنشاء ملف حزمة مع تعريفات Web Component الخاصة بنا في ملف shoelace-dir مجلد. دعنا ننتقل إلى ملف public مجلد بحيث يقوم Next.js بخدمته. ويجب علينا أيضًا تتبع الاسم الدقيق للملف ، مع وجود التجزئة في نهايته. إليك برنامج 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 ملف وسحب اسم ملف حزمة مكون الويب الخاص بنا:

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 عالية الجودة غير مرتبطة بإطار عمل محدد. من الجيد أيضًا أن تكون قادرًا على تجربة أطر عمل جديدة تمامًا ، مثل سوليد، دون الحاجة إلى البحث (أو الاختراق معًا) نوعًا من علامات التبويب أو الوسائط أو الإكمال التلقائي أو أي مكون.

الطابع الزمني:

اكثر من الخدع المغلق