รูปแบบการประกอบ (Composition Patterns)
เมื่อคุณมอง Astro เป็น shell แล้ว คำถามการออกแบบสำคัญคือ เมื่อไหร่ ที่ slice ต่างๆ จะถูกนำมารวมกัน มีสองคำตอบ และสถาปัตยกรรมที่สุกงอมมักใช้ทั้งสองอย่าง: build-time integration และ run-time integration
Build-time integration: shared packages ใน workspace
หัวข้อที่มีชื่อว่า “Build-time integration: shared packages ใน workspace”ใน build-time integration แต่ละทีมเผยแพร่ slice ของตัวเองเป็น versioned package monorepo ที่มี workspaces (npm, pnpm, หรือ Yarn) เป็นบ้านที่พบบ่อยที่สุดสำหรับสิ่งนี้ shell ขึ้นอยู่กับ @store/catalog, @store/cart และอื่นๆ ปักหมุดเวอร์ชัน แล้ว import เข้ามาเหมือน component อื่นๆ:
---// The shell imports versioned team packages.import Catalog from '@store/catalog';import Cart from '@store/cart';---<Catalog client:visible /><Cart client:load />เนื่องจากการประกอบเกิดขึ้นระหว่างการ build ของ shell คุณจึงได้สิ่งที่ compiler ทำได้ดี: การ type check ข้ามขอบเขต slice, tree-shaking และ output เดียวที่ถูก optimize แล้ว ข้อติดขัดคือความเป็นอิสระ — การเปลี่ยนแปลงของ Catalog จะไปถึงผู้ใช้ก็ต่อเมื่อ shell เลื่อนเวอร์ชัน, rebuild และ redeploy เท่านั้น build-time integration เอนเอียงไปทางความปลอดภัยและความสอดคล้องมากกว่าการ ship แบบทันทีและเป็นอิสระ
Run-time integration: ประกอบเมื่อมีการร้องขอ
หัวข้อที่มีชื่อว่า “Run-time integration: ประกอบเมื่อมีการร้องขอ”ใน run-time integration แต่ละ slice เป็น artifact ที่ deploy แยกกัน shell ไม่ได้ import ตอน build แต่ดึง slice เข้ามาเมื่อหน้าเพจถูกเรนเดอร์ (บนเซิร์ฟเวอร์) หรือหลังจากที่โหลดแล้ว (ใน browser) ทีมต่างๆ ship ตามนาฬิกาของตัวเอง และ shell หยิบเวอร์ชันใหม่ขึ้นมาในการร้องขอครั้งถัดไป — ไม่ต้อง redeploy shell ราคาที่จ่ายคือการรับประกัน ณ เวลา compile ที่อ่อนแอลงและพื้นผิวการดำเนินงานที่มากขึ้น ซึ่งจะกล่าวถึงโดยละเอียดในบทเรียนถัดไป
flowchart TD
subgraph BuildTime["Build-time integration"]
BT1["Team pkg @store/cart v1.4"] --> BT2["Shell build imports it"]
BT2 --> BT3["One optimized bundle"]
end
subgraph RunTime["Run-time integration"]
RT1["Team deploys cart service"] --> RT2["Shell fetches at request / in browser"]
RT2 --> RT3["Composed live, no shell rebuild"]
end Server islands: fragment ที่ถูกเลื่อนแยกตามทีม
หัวข้อที่มีชื่อว่า “Server islands: fragment ที่ถูกเลื่อนแยกตามทีม”server islands ของ Astro ให้ทางสายกลางที่ทรงพลังแก่คุณ ด้วยการเพิ่ม server:defer ให้กับ component คุณบอก Astro ให้เรนเดอร์ส่วนที่เหลือของหน้าเพจทันทีและ stream island นั้นเข้ามาทีหลังจากเซิร์ฟเวอร์ แต่ละ island สามารถมี fallback slot ของตัวเองที่แสดงในขณะที่กำลังโหลด:
---import Recommendations from '../islands/Recommendations.astro';---<Recommendations server:defer> <p slot="fallback">Loading recommendations…</p></Recommendations>สำหรับ micro-frontends นี่คือสิ่งที่เหมาะอย่างยิ่ง: team fragment ที่ช้าหรือถูกปรับให้เฉพาะบุคคล — recommendations, cart badge, ราคาแบบ live — จะไม่ block ส่วนที่เหลือของหน้าเพจอีกต่อไป shell ที่เร็วและ cache ได้จะ ship ออกไปก่อน และ island ที่ถูกเลื่อนของแต่ละทีมจะมาถึงด้วยตัวของตัวเอง นี่คือความขี้เกียจแยกตามทีมโดยไม่มีต้นทุน client-side framework สำหรับ fallback เลย
ลองเลย: shell ที่ import component ของทีม
หัวข้อที่มีชื่อว่า “ลองเลย: shell ที่ import component ของทีม”หน้าเพจที่รันได้ด้านล่างเป็น composition shell ขั้นต่ำ หน้านี้ import component “ทีม” หนึ่งตัวแล้วเรนเดอร์เป็น section — รูปร่างเดียวกับที่คุณจะใช้ไม่ว่าการ import จะ resolve ไปเป็น workspace package หรือ slice ในเครื่อง
---
// src/pages/index.astro — composition shell
// In a real setup, ProductCard would come
// from a versioned package like @store/catalog.
import ProductCard from '../components/ProductCard.astro';
---
<html lang="en">
<head><title>Composition shell</title></head>
<body>
<header><h1>Acme Store</h1></header>
<main>
<h2>Catalog team's slice</h2>
<ProductCard
name="Astro Mug"
price="12.00"
/>
</main>
<footer>Composed by the Astro shell.</footer>
</body>
</html>เพื่อให้สิ่งนี้รันได้ คุณจะต้องเพิ่ม src/components/ProductCard.astro ที่อ่าน Astro.props (name, price) และเรนเดอร์การ์ดออกมา shell ไม่สนใจว่า slice ถูก build ภายในอย่างไร — สนใจแค่ว่ารับ props อะไรบ้างเท่านั้น