การใช้ส่วนประกอบเว็บกับ Next (หรือ SSR Framework ใดๆ)

ในฉัน โพสต์ก่อนหน้านี้ เราดูที่ Shoelace ซึ่งเป็นไลบรารีส่วนประกอบที่มีส่วนประกอบ UX ครบชุดที่สวยงาม เข้าถึงได้ และอาจสร้างโดยไม่คาดคิดด้วย ส่วนประกอบของเว็บ. ซึ่งหมายความว่าสามารถใช้กับเฟรมเวิร์ก JavaScript ใดก็ได้ ในขณะที่ความสามารถในการทำงานร่วมกันของ Web Component ของ React นั้นต่ำกว่าอุดมคติในปัจจุบัน มีวิธีแก้ปัญหา.

แต่ข้อบกพร่องร้ายแรงประการหนึ่งของ Web Components คือการขาดการสนับสนุนสำหรับการแสดงผลฝั่งเซิร์ฟเวอร์ (SSR) มีบางอย่างที่เรียกว่า Declarative Shadow DOM (DSD) ในการทำงาน แต่การสนับสนุนในปัจจุบันมีน้อยมาก และจำเป็นต้องซื้อจากเว็บเซิร์ฟเวอร์ของคุณเพื่อส่งมาร์กอัปพิเศษสำหรับ DSD ขณะนี้มีงานทำเพื่อ เน็กซ์.เจส ที่ฉันรอคอยที่จะเห็น แต่สำหรับโพสต์นี้ เราจะมาดูวิธีจัดการ Web Components จากเฟรมเวิร์ก SSR เช่น Next.js ในวันนี้.

เราจะลงเอยด้วยการทำงานด้วยตนเองเล็กน้อยและ เล็กน้อย ส่งผลกระทบต่อประสิทธิภาพการเริ่มต้นของเพจในกระบวนการ จากนั้นเราจะดูวิธีลดค่าใช้จ่ายด้านประสิทธิภาพเหล่านี้ แต่อย่าพลาด: โซลูชันนี้ไม่มีการแลกเปลี่ยน ดังนั้นอย่าคาดหวังอย่างอื่น วัดและโปรไฟล์เสมอ

ปัญหา

ก่อนที่เราจะดำดิ่งลงไป ให้เราใช้เวลาสักครู่แล้วอธิบายปัญหาจริงๆ เหตุใด Web Components จึงทำงานได้ไม่ดีกับการเรนเดอร์ฝั่งเซิร์ฟเวอร์

กรอบงานแอปพลิเคชันเช่น Next.js ใช้โค้ด React และเรียกใช้ผ่าน API เพื่อ "ทำให้เป็นสตริง" ซึ่งหมายความว่าส่วนประกอบของคุณจะเปลี่ยนเป็น HTML ธรรมดา ดังนั้นโครงสร้างส่วนประกอบ React จะแสดงบนเซิร์ฟเวอร์ที่โฮสต์เว็บแอป และ HTML นั้นจะถูกส่งลงไปพร้อมกับเอกสาร HTML ของเว็บแอปที่เหลือไปยังเบราว์เซอร์ของผู้ใช้ของคุณ พร้อมกับ HTML นี้มีบางส่วน แท็กที่โหลด React พร้อมกับโค้ดสำหรับส่วนประกอบ React ทั้งหมดของคุณ เมื่อเบราว์เซอร์ประมวลผลสิ่งเหล่านี้ แท็ก React จะแสดงโครงสร้างส่วนประกอบอีกครั้ง และจับคู่สิ่งต่างๆ กับ HTML ของ SSR ที่ส่งลงมา ณ จุดนี้ เอฟเฟกต์ทั้งหมดจะเริ่มทำงาน ตัวจัดการเหตุการณ์จะเชื่อมต่อ และสถานะจะ... มีสถานะ เมื่อถึงจุดนี้เว็บแอปจะกลายเป็น การโต้ตอบ. กระบวนการของการประมวลผลโครงสร้างส่วนประกอบของคุณบนไคลเอนต์อีกครั้ง และการเดินสายทุกอย่างเรียกว่า ความชุ่มชื้น.

แล้วมันเกี่ยวอะไรกับ Web Components? เมื่อคุณแสดงอะไรบางอย่าง ให้พูดว่าเชือกผูกรองเท้าแบบเดียวกัน องค์ประกอบที่เราเยี่ยมชม ครั้งสุดท้าย:


   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) จะไม่รับผิดชอบในการเปลี่ยนแท็กเหล่านั้นให้เป็นแท็บที่มีรูปแบบสวยงาม รหัสสำหรับสิ่งนั้นซ่อนอยู่ภายในรหัสที่คุณมีซึ่งกำหนดส่วนประกอบเว็บเหล่านั้น ในกรณีของเรา รหัสนั้นอยู่ในไลบรารีเชือกผูกรองเท้า แต่รหัสสามารถอยู่ที่ใดก็ได้ ที่สำคัญคือ เมื่อโค้ดรัน.

โดยปกติ รหัสที่ลงทะเบียน Web Components เหล่านี้จะถูกดึงเข้าไปในรหัสปกติของแอปพลิเคชันของคุณผ่าน JavaScript import. นั่นหมายความว่าโค้ดนี้จะสิ้นสุดในชุด JavaScript ของคุณและดำเนินการในระหว่างการไฮเดรชั่น ซึ่งหมายความว่าระหว่างผู้ใช้ของคุณที่เห็น HTML ของ SSR และการไฮเดรชั่นเกิดขึ้น แท็บเหล่านี้ (หรือส่วนประกอบเว็บสำหรับเรื่องนั้น) จะไม่แสดงเนื้อหาที่ถูกต้อง . จากนั้น เมื่อเกิดภาวะขาดน้ำ เนื้อหาที่เหมาะสมจะแสดงขึ้น ซึ่งอาจทำให้เนื้อหารอบๆ Web Components เคลื่อนไปรอบๆ และพอดีกับเนื้อหาที่จัดรูปแบบอย่างเหมาะสม นี้เรียกว่า แฟลชของเนื้อหาที่ไม่มีสไตล์หรือ FOUC ในทางทฤษฎี คุณสามารถติดมาร์กอัปไว้ระหว่างสิ่งเหล่านั้นได้ เพื่อให้ตรงกับผลลัพธ์ที่เสร็จแล้ว แต่ในทางปฏิบัติทั้งหมดนี้เป็นไปไม่ได้ โดยเฉพาะอย่างยิ่งสำหรับไลบรารีคอมโพเนนต์ของบริษัทอื่น เช่น Shoelace

การย้ายรหัสลงทะเบียน Web Component

ดังนั้นปัญหาคือโค้ดที่ทำให้ Web Components ทำในสิ่งที่พวกเขาต้องการจะไม่ทำงานจริงจนกว่าจะเกิดการไฮเดรชั่น สำหรับโพสต์นี้ เราจะพิจารณาการรันโค้ดนั้นเร็วกว่านี้ ทันทีในความเป็นจริง เราจะพิจารณาการรวมโค้ด Web Component แบบกำหนดเอง และเพิ่มสคริปต์ลงในเอกสารของเราโดยตรง ดังนั้นมันจึงทำงานทันที และบล็อกส่วนที่เหลือของเอกสารจนกว่าจะทำงาน ปกติแล้วสิ่งนี้เป็นสิ่งที่แย่มากที่ต้องทำ จุดรวมของการเรนเดอร์ฝั่งเซิร์ฟเวอร์คือto ไม่ บล็อกเพจของเราไม่ให้ประมวลผลจนกว่า JavaScript ของเราจะได้รับการประมวลผล แต่เมื่อทำเสร็จแล้ว หมายความว่าในขณะที่เอกสารกำลังแสดง HTML ของเราจากเซิร์ฟเวอร์ในขั้นต้น ส่วนประกอบเว็บจะถูกลงทะเบียนและจะปล่อยเนื้อหาที่ถูกต้องทันทีและพร้อมกัน

ในกรณีของเรา เราคือ เพียงแค่ ต้องการเรียกใช้รหัสการลงทะเบียน Web Component ของเราในสคริปต์การบล็อก รหัสนี้มีขนาดไม่ใหญ่นัก และเราจะพยายามลดประสิทธิภาพการทำงานลงอย่างมากด้วยการเพิ่มส่วนหัวของแคชเพื่อช่วยในการเข้าชมครั้งต่อๆ ไป นี่ไม่ใช่วิธีแก้ปัญหาที่สมบูรณ์แบบ ครั้งแรกที่ผู้ใช้เรียกดูเพจของคุณจะถูกบล็อกในขณะที่โหลดไฟล์สคริปต์นั้นเสมอ การเยี่ยมชมครั้งต่อไปจะแคชอย่างดี แต่การประนีประนอมนี้ อาจจะไม่ เป็นไปได้สำหรับคุณ - อีคอมเมิร์ซใคร? อย่างไรก็ตาม โปรไฟล์ วัดผล และตัดสินใจให้ถูกต้องสำหรับแอปของคุณ นอกจากนี้ ในอนาคต Next.js จะสนับสนุน DSD และ Web Components อย่างสมบูรณ์

หากคุณยังไม่ได้เปิดบัญชี IQ Option คลิ๊กที่นี่ กรอกรายละเอียดของคุณและมันจะนำไปยังหน้าเพจที่คล้ายด้านล่างนี้

รหัสทั้งหมดที่เราจะดูอยู่ใน ที่เก็บ GitHub นี้ และ ปรับใช้ที่นี่กับ Vercel. เว็บแอปแสดงส่วนประกอบเชือกผูกรองเท้าบางส่วนพร้อมกับข้อความที่เปลี่ยนสีและเนื้อหาเมื่อขาดน้ำ คุณควรจะเห็นข้อความเปลี่ยนเป็น "Hydrated" โดยที่ส่วนประกอบ Shoelace แสดงผลอย่างถูกต้องแล้ว

การรวมรหัส Web Component แบบกำหนดเอง

ขั้นตอนแรกของเราคือการสร้างโมดูล JavaScript เดียวที่นำเข้าคำจำกัดความ Web Component ทั้งหมดของเรา สำหรับส่วนประกอบเชือกผูกรองเท้าที่ฉันใช้ รหัสของฉันมีลักษณะดังนี้:

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 มี webpack hooks จำนวนมากสำหรับสิ่งที่บันเดิลแบบกำหนดเอง ฉันจะใช้ ชีวิต แทนที่. ขั้นแรก ติดตั้งด้วย 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 ใช้งานได้ และเราควรติดตามชื่อที่แน่นอนของไฟล์ด้วย โดยจะมีแฮชอยู่ท้ายไฟล์ นี่คือสคริปต์โหนดที่ย้ายไฟล์และเขียนโมดูล 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";

จากนั้นเราเรนเดอร์ 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 ให้ทำการแคชบันเดิล Shoelace เหล่านี้ได้โดยเพิ่มรายการต่อไปนี้ในไฟล์กำหนดค่า Next.js ของเรา:

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

ในการเรียกดูเว็บไซต์ของเราในภายหลัง เราเห็นการแคชมัดเชือกผูกรองเท้าอย่างดี!

เปิดแผงแหล่งที่มาของ DevTools และแสดงมัดเชือกผูกรองเท้าที่โหลด
การใช้ส่วนประกอบเว็บกับ Next (หรือ SSR Framework ใดๆ)

หากมัดเชือกรองเท้าของเรามีการเปลี่ยนแปลง ชื่อไฟล์จะเปลี่ยนไป (ผ่านทาง :hash จากคุณสมบัติต้นทางด้านบน) เบราว์เซอร์จะพบว่าไม่มีไฟล์ที่แคชไว้ และจะขอใหม่จากเครือข่าย

ตัดขึ้น

นี้อาจดูเหมือนเป็นงานมาก; และมันก็เป็น. น่าเสียดายที่ Web Components ไม่ได้ให้การสนับสนุนแบบสำเร็จรูปที่ดีกว่าสำหรับการเรนเดอร์ฝั่งเซิร์ฟเวอร์

แต่เราไม่ควรลืมประโยชน์ที่ได้รับ: เป็นการดีที่สามารถใช้ส่วนประกอบ UX ที่มีคุณภาพซึ่งไม่ได้เชื่อมโยงกับเฟรมเวิร์กเฉพาะ ก็ยังดีที่ได้ทดลองกับเฟรมเวิร์กใหม่ๆ เช่น ของแข็งโดยไม่จำเป็นต้องค้นหา (หรือแฮ็คร่วมกัน) แท็บ กิริยาช่วย การเติมข้อความอัตโนมัติ หรือส่วนประกอบใดๆ

ประทับเวลา:

เพิ่มเติมจาก เคล็ดลับ CSS