- หลายคนพูดถึง htmx ราวกับว่ามันจะมากอบกู้เว็บจาก SPA
- Carson Gross ผู้สร้าง htmx อธิบายพลวัตนี้อย่างมีอารมณ์ขันว่าเป็น "วิภาษวิธีของเฮเกล":
- ปฏิพจน์ (thesis): MPA แบบดั้งเดิม
- ปฏิปักษ์ (antithesis): SPA
- สังเคราะห์ (synthesis): แอปพลิเคชันที่ประกอบด้วย interactive islands บนพื้นฐานของ hypermedia
- แต่ผมมองไม่เห็นภาพนั้น และก่อนหน้านี้ก็เคย "สร้าง SPA ด้วย htmx"
- นี่เป็น PoC ของแอป ToDo list แบบง่าย ๆ
- เมื่อหน้าโหลดเสร็จแล้ว จะไม่สื่อสารกับเซิร์ฟเวอร์อีกต่อไป
- ทุกอย่างถูกประมวลผลแบบโลคัลบนฝั่งไคลเอนต์
- ถ้า htmx โฟกัสที่การจัดการการแลกเปลี่ยน hypermedia ผ่านเครือข่าย แล้วสิ่งนี้ทำงานได้อย่างไร?
- มีทริกง่าย ๆ อยู่ข้อหนึ่ง: โค้ด "ฝั่งเซิร์ฟเวอร์" ทำงานอยู่ใน Service Worker
Service Worker
- ทำหน้าที่เป็นพร็อกซีระหว่างเว็บเพจกับอินเทอร์เน็ต
- สามารถดักจับและปรับแต่งคำขอเครือข่ายได้
- แก้ไขคำขอ, แคชการตอบสนองสำหรับออฟไลน์, และสร้างการตอบสนองใหม่ได้โดยไม่ต้องส่งคำขอออกนอกเบราว์เซอร์
- ความสามารถข้อสุดท้ายนี่เองคือแกนหลักของ single-page app นี้
- เมื่อ htmx ส่งคำขอเครือข่าย Service Worker จะดักจับไว้
- จากนั้น Service Worker จะรัน business logic และสร้าง HTML ใหม่
- htmx จะสลับ HTML ใหม่เข้าไปใน DOM
ข้อดีเมื่อเทียบกับ SPA แบบเดิม
- Service Worker จำเป็นต้องใช้ IndexedDB เป็นสตอเรจ
- นั่นทำให้สถานะคงอยู่ข้ามการโหลดหน้าได้
- ปิดหน้าไปแล้วกลับมาใหม่ แอปก็ยังจำข้อมูลได้
- นี่เป็นผลดีที่ได้มาแบบ "ฟรี" จากการเลือกสถาปัตยกรรมนี้
- ทำให้รองรับการทำงานแบบออฟไลน์ได้ง่าย
ข้อเสีย
- การรองรับในเครื่องมือนักพัฒนาแย่
console.log หลุดหายเป็นบางครั้ง
- การรายงานว่า Service Worker ถูกติดตั้งหรือไม่นั้นเชื่อถือไม่ได้
- Firefox ยังไม่รองรับ ES modules
- ต้องรวมโค้ดทั้งหมดไว้ในไฟล์เดียว
- ประสบการณ์การพัฒนาโดยรวม "ไม่สนุก"
ถึงอย่างนั้น htmx SPA ก็ทำงานได้ดี
รายละเอียดการใช้งานจริง
- HTML พื้นฐานเป็นโครงว่างเปล่าของ single-page app
- แท็ก
<body> ใช้ htmx เพื่อตั้งค่าตัวแอปหลัก
hx-boost="true": สั่งให้ htmx แทนที่ผลลัพธ์ของการคลิกลิงก์และการส่งฟอร์มด้วย Ajax โดยไม่ต้องนำทางทั้งหน้า
hx-push-url="false": ทำให้ htmx ไม่เปลี่ยน URL ตามการคลิกลิงก์และการส่งฟอร์ม
hx-get="./ui": สั่งให้ดึงหน้าเว็บจาก /ui มาแทนที่เมื่อหน้าโหลด
hx-target="body": สั่งให้แทนที่ผลลัพธ์ลงในองค์ประกอบ <body>
hx-trigger="load": สั่งให้ทำทั้งหมดนี้เมื่อหน้าโหลด
เอนด์พอยต์ /ui
- ส่งคืนมาร์กอัปจริงของแอป
- หลังจากนั้น htmx จะควบคุมลิงก์และฟอร์มเพื่อทำให้เกิดการโต้ตอบ
- Service Worker จัดการ routing ของคำขอด้วยไลบรารีสไตล์ Express
setFilter และ listTodos เป็นฟังก์ชันง่าย ๆ ที่ครอบ IDB Keyval ไว้
- คอมโพเนนต์
App ประกอบด้วยฟอร์มตัวกรอง รายการงาน และฟอร์มเพิ่มงาน
เอนด์พอยต์ /todos/add
- หลังบันทึกงานแล้ว จะส่งคืนการตอบสนองที่ re-render UI ใหม่
- htmx จะสลับการตอบสนองนั้นเข้าไปใน DOM
คอมโพเนนต์ Todo
- ประกอบด้วยเช็กบ็อกซ์ ปุ่มลบ และข้อความของงาน
- เช็กบ็อกซ์จะทริกเกอร์คำขอไปที่
/todos/${id}/update
- ปุ่มลบจะทริกเกอร์คำขอลบไปที่
/todos/${id}
- ข้อความของงานมีสองสถานะคือ "normal" และ "editing"
- htmx รอฟังเหตุการณ์ดับเบิลคลิก
- ส่งคำขอไปที่
/ui/todos/${id}?editable=true
- Service Worker จะส่งคืน HTML ของ
Todo ที่มี <input> อยู่ภายใน
- htmx จะแทนที่รายการงานปัจจุบันด้วย HTML จากการตอบสนอง
สรุป
- ในเชิงเทคนิค มันใช้งานได้
- แต่มันเป็นความคิดที่ดีไหม? เป็นจุดสูงสุดของแอปแบบ hypermedia จริงหรือ? ควรทิ้ง React แล้วมาสร้างแบบนี้หรือเปล่า?
- สำหรับแอปที่เป็นโลคัลทั้งหมด ความอ้อมของ htmx ให้ความรู้สึกเป็นภาระมากกว่าความปลดปล่อย
- แอปส่วนใหญ่ไม่ได้เป็นโลคัลทั้งหมด
- มองว่าแพตเทิร์น "interactive islands" ดีกว่าการแยกโค้ด "ฝั่งเซิร์ฟเวอร์" ออกเป็น Service Worker กับเซิร์ฟเวอร์จริง
- นี่เป็นความพยายามเชิงทดลองเพื่อแสดงให้เห็นว่าสามารถสร้าง single-page app แบบโลคัลทั้งหมดด้วย hypermedia ได้
ยังไม่มีความคิดเห็น