ข้ามไปยังเนื้อหา

Run-time Integration และ Deploy

นี่คือจุดที่ micro-frontends สมกับชื่อของตัวเอง: แต่ละ slice ถูก deploy อย่างเป็นอิสระ อยู่ที่ URL ของตัวเองบน pipeline ของตัวเอง และ Astro shell เย็บ slice เหล่านั้นเข้าด้วยกัน ตอน run time ไม่ต้อง rebuild shell เมื่อทีม ship — shell เพียงโหลดสิ่งที่กำลัง live อยู่ในขณะนั้น นี่คือสี่เทคนิคหลัก เรียงจากการแยกตัวที่อ่อนที่สุดไปแข็งที่สุด

ทีมสามารถ compile slice ของตัวเองให้เป็น custom element และเผยแพร่สคริปต์เพียงตัวเดียว shell โหลดสคริปต์นั้นและใช้ element เหมือน HTML tag ใดๆ — ไม่มีการล็อกอินกับ framework เพราะ browser เป็นเจ้าของ registry ของ element:

---
// The shell loads a team's element bundle.
---
<script src="https://cart.acme.dev/cart-widget.js" type="module"></script>
<cart-widget user-id="42"></cart-widget>

Web Components คือขอบเขต run-time ที่เป็นธรรมชาติที่สุดใน Astro: เป็นเพียง HTML, ห่อหุ้มสไตล์ของตัวเองผ่าน shadow DOM และ framework ใดๆ (Lit, Stencil, Svelte, หรือแม้แต่ React ผ่าน wrapper) ก็สร้าง Web Component ออกมาได้

เมื่อ slice ต้องถูก ปิดผนึกแยกออกอย่างสมบูรณ์ — โค้ดของบุคคลที่สาม, แอป legacy, ขอบเขตความปลอดภัยที่เข้มงวด — iframe ให้เอกสารแยก, CSS แยก และ JavaScript globals แยกแก่คุณ:

<iframe src="https://legacy.acme.dev/reviews" title="Reviews"></iframe>

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

Astro shell สามารถ fetch HTML ที่ทีมเรนเดอร์ไว้แล้ว บนเซิร์ฟเวอร์ — ใน server island หรือ endpoint — แล้ว inline เข้าไปในหน้าเพจ วิธีนี้ทำให้ฝั่ง client เบา: ผู้ใช้ได้รับเอกสาร HTML หนึ่งชุดที่ประกอบเสร็จแล้ว

---
// Server-side: fetch a team's rendered fragment.
const res = await fetch('https://reviews.acme.dev/fragment');
const html = await res.text();
---
<section set:html={html}></section>

คุณยังสามารถเปิดเผยสิ่งนี้จาก endpoint ของ Astro (src/pages/api/reviews.ts) เพื่อให้ shell อื่นนำกลับมาใช้ซ้ำได้ จับคู่กับ server:defer (จากบทเรียนก่อนหน้า) เพื่อให้ fragment ที่ช้าไม่เคย block หน้าเพจ

Module Federation ให้แอปหนึ่งเรียกใช้ module จากอีกแอปหนึ่งตอน run time โดย fetch ผ่านเครือข่าย เนื่องจาก Astro สร้างบน Vite คุณจึงสามารถใช้ปลั๊กอิน @module-federation/vite เพื่อเปิดเผย module จากแอปของทีมต่างๆ แล้วเรียกใช้ใน shell ได้:

Terminal window
npm install @module-federation/vite

นี่คือตัวเลือก run-time ที่ “เป็นธรรมชาติแบบ SPA” ที่สุด — ทีมต่างๆ เปิดเผย component ที่ shell import แบบ dynamic เข้ามาโดยไม่ต้อง bundle ไว้ล่วงหน้า วิธีนี้ทรงพลังแต่ก็หนักที่สุดในการดำเนินงาน: การทำ versioning ของ shared dependency (เพื่อไม่ให้ React ถูกโหลดสองครั้ง) และการประสานงาน build config กลายเป็นงานจริงจัง

flowchart TD
  subgraph Teams["Independently deployed MFEs"]
    A["cart.acme.dev\n(Web Component)"]
    B["reviews.acme.dev\n(remote fragment)"]
    C["catalog.acme.dev\n(federated module)"]
    D["legacy.acme.dev\n(iframe)"]
  end
  Shell["Astro shell — composes at run time"]
  A --> Shell
  B --> Shell
  C --> Shell
  D --> Shell
  Shell --> Page["One stitched page"]
micro-frontends ที่ deploy อิสระถูกเย็บเข้าด้วยกันโดย Astro shell

slice ที่เป็นอิสระต่อกันบางครั้งก็ยังต้องคุยกัน — “เพิ่มสินค้าลงตะกร้าแล้ว” ต้องไปถึง cart badge ทำให้ contract เล็กและ decouple ออกจากกัน:

  • Custom events บน window: slice หนึ่งทำ window.dispatchEvent(new CustomEvent('cart:add', { detail })) และอีก slice รับฟัง ไม่ต้องมี shared bundle
  • shared store เล็กๆ อย่าง nanostores: atom เล็กตัวเดียวที่ทั้งสอง slice import เข้ามาเพื่ออ่านและเขียน shared state แบบ reactive ไม่ผูกกับ framework และมีขนาดเพียงไม่กี่ร้อยไบต์

สำหรับ shared routing ให้ Astro shell เป็นเจ้าของ URL และ layout แต่ละ slice จัดการ view state ภายในของตัวเอง แต่ยกการ navigation ระดับบนสุดให้ shell เพื่อให้ปุ่ม back และ deep link ทำงานสอดคล้องกันข้ามทีม

การ deploy อิสระคือทั้งรางวัลและภัยอันตราย กฎไม่กี่ข้อช่วยให้ยังควบคุมได้:

  • ทำ version ให้ contract ของคุณ ไม่ใช่แค่โค้ด props ที่ element รับเข้ามา, ชื่อ event, รูปร่าง HTML ของ fragment — ปฏิบัติกับสิ่งเหล่านี้เหมือน public API และทำ version ให้ด้วย
  • ปักหมุดหรือเจรจา shared dependencies หากไม่ระมัดระวัง สาม slice จะ ship สำเนา React สามชุด การตั้งค่า shared ของ Module Federation หรือเพียงแค่ทำให้เป็นมาตรฐานบน Astro islands จะหลีกเลี่ยงภาษีความซ้ำซ้อนได้
  • ปกป้องความสอดคล้อง design tokens, fonts และ accessibility ต้องถูกบังคับใช้ข้าม repo (shared CSS/token package) ไม่งั้นหน้าเพจจะค่อยๆ เพี้ยนไปจนดูเหมือนสี่ผลิตภัณฑ์ที่ต่างกัน
  • ล้มเหลวอย่างสง่างาม run-time slice อาจล่มได้ ห่อ remote fetch และ federated import ไว้เพื่อให้ทีมที่ตายไปหนึ่งทีมลดระดับลงเป็น fallback แทนที่จะเป็นหน้าเปล่า

run-time integration คือสิ่งที่ทำให้ผังองค์กรปรากฏชัดในสถาปัตยกรรม: ทุกทีมเป็นเจ้าของ URL, ship เมื่อต้องการ และ Astro shell — static, รวดเร็ว, ไม่ผูกกับ framework — ก็ประกอบทุกอย่างเข้าด้วยกันเป็นหน้าเพจเดียวอย่างเงียบๆ

เทคนิค run-time ใดให้การแยกตัวที่แข็งที่สุดระหว่าง slice กับ shell?
ปลั๊กอิน Vite ตัวใดที่ปล่อยให้ shell บริโภค module จากแอปของทีมที่ deploy อิสระตอน run time?
วิธีที่เบาในการให้สอง slice อิสระแชร์ state กันโดยไม่ต้องมี shared bundle คือ:
หลุมพรางที่แท้จริงของการ deploy อิสระที่ต้องระวังคือ: