การแสดงผลองค์ประกอบที่กำหนดเองล่วงหน้า

ฟีเจอร์เว็บ: Declarative Shadow DOM

การแสดงผลล่วงหน้าหมายถึงการส่งมาร์กอัปขององค์ประกอบที่กำหนดเอง ซึ่งรวมถึง Shadow Tree จากเซิร์ฟเวอร์ เพื่อให้คอมโพเนนต์ได้รับการจัดรูปแบบและโครงสร้างก่อนที่ JavaScript จะทำงาน องค์ประกอบพื้นฐานหลักของแพลตฟอร์มสำหรับกรณีนี้คือ Declarative Shadow DOM (DSD): โปรแกรมแยกวิเคราะห์จะแนบรูท Shadow จาก HTML เพียงอย่างเดียว และองค์ประกอบจะได้รับการอัปเกรด ("ไฮเดรต") ในภายหลังตามคลาสหากมีการลงทะเบียนไว้ ใช้รูปแบบนี้สำหรับการแสดงผลฝั่งเซิร์ฟเวอร์ การสตรีม และการหลีกเลี่ยง Flash of Unstyled Content (FOUC)

การเขียน Shadow Root แบบประกาศ

ฝัง <template> ที่มีแอตทริบิวต์ shadowrootmode เป็นองค์ประกอบย่อยแรกของโฮสต์ โปรแกรมแยกวิเคราะห์จะแนบเนื้อหาเป็น Shadow Root และนำเทมเพลตออก โดยไม่จำเป็นต้องใช้สคริปต์

<my-card>
  <template shadowrootmode="open">
    <style>
      .card { border: 1px solid; border-radius: 8px; padding: 1rem; }
    </style>
    <div class="card">
      <slot name="header"></slot>
      <div class="body"><slot></slot></div>
    </div>
  </template>

  <!-- Light DOM children are projected into the slots above. -->
  <h2 slot="header">Server-rendered title</h2>
  <p>Visible and styled before JS loads.</p>
</my-card>

เวลาที่ดื่มน้ำ

เมื่อลงทะเบียนคลาสที่ตรงกันแล้ว เบราว์เซอร์จะอัปเกรดโฮสต์ เนื่องจากมี Shadow Root อยู่แล้ว ตัวสร้างจึงต้องไม่สร้าง Shadow Root รายการที่ 2 โดยไม่ตรวจสอบก่อน ดังนี้

class MyCard extends HTMLElement {
  constructor() {
    super();
    // Reuse the server-rendered shadow root if present; only create one if absent.
    if (!this.shadowRoot) {
      this.attachShadow({ mode: 'open' });
      this.shadowRoot.append(template.content.cloneNode(true));
    }
    // Hydrate in place: attach listeners once against existing markup (not unguarded
    // in connectedCallback, which re-runs if the element moves in the DOM), and do
    // NOT re-render; that would discard the server's DOM and flash.
    this.shadowRoot.getElementById('toggle')
      ?.addEventListener('click', () => this.toggleAttribute('open'));
  }
}

ตั้งค่า shadowrootmode="open" (ไม่ใช่ closed) สำหรับเกือบทุกกรณี เพื่อให้สคริปต์และเครื่องมือเข้าถึง this.shadowRoot เพื่อเติมข้อมูลได้ หากต้องการแปลง Shadow Root ที่มีอยู่กลับเป็น HTML บนเซิร์ฟเวอร์หรือในการทดสอบ ให้ใช้ getHTML({ serializableShadowRoots: true }) และแนบรูทด้วย serializable: true

การหลีกเลี่ยง FOUC

DSD จะแสดงผลอย่างถูกต้องในการแสดงผลครั้งแรกก็ต่อเมื่อสไตล์แสดงอยู่ภายในเทมเพลตที่ประกาศ วิธีรักษาข้อมูลดังกล่าวมีดังนี้

  • ใส่ CSS ของคอมโพเนนต์ไว้ใน <template shadowrootmode> (แท็ก <style> หรือ <link>) ไม่ใช่ในชีตที่โหลดสคริปต์แยกต่างหาก
  • จับคู่กับ my-card:not(:defined) { … } เฉพาะสไตล์ที่ต้องรอ JS ซึ่งโดยปกติแล้วจะเป็นกฎป้องกันระดับหน้าเว็บ เช่น การเฟดออกในอินสแตนซ์ที่แสดงผลฝั่งไคลเอ็นต์ซึ่งพลาดการส่ง SSR โดยจะอยู่ในสไตล์ชีต Light-DOM (หน้าเว็บ) เนื่องจากยังไม่มี Shadow Root และตัวเลือก :host จะไม่มีผลจากภายนอก รูปแบบโครงสร้างภายในเทมเพลต DSD ไม่จำเป็นต้องมีการป้องกันดังกล่าว
  • อย่าแสดงผล Shadow Tree ซ้ำเมื่อเกิดการไฮเดรชัน ถือว่ามาร์กอัปของเซิร์ฟเวอร์เป็นแหล่งข้อมูลที่เชื่อถือได้และแนบลักษณะการทำงานเข้ากับมาร์กอัปนั้น

กลยุทธ์สำรอง

ในเครื่องมือที่ไม่ได้แยกวิเคราะห์ shadowrootmode เทมเพลตจะไม่มีการทำงานและเนื้อหาจะไม่แนบเป็น Shadow Root ตรวจหาองค์ประกอบนี้ (Object.hasOwn(HTMLTemplateElement.prototype, 'shadowRootMode')) และหากไม่มี ให้แนบ Shadow Root จากมาร์กอัปเดียวกันในระหว่างการอัปเกรดเพื่อให้คอมโพเนนต์ยังคงแสดงผล