เหตุผลที่ย้ายจาก HTMX ไปใช้ Datastar
(everydaysuperpowers.dev)- แม้การใช้ HTMX จะช่วยลดปริมาณโค้ดได้ราว 70% แต่ก็พบปัญหาเรื่องการซิงก์ระหว่าง UI และความซับซ้อนของ การจัดการสถานะฝั่งฟรอนต์เอนด์ ที่เพิ่มขึ้น
- หลังนำ Datastar มาใช้ การพัฒนาแอปพลิเคชันแบบเรียลไทม์สำหรับผู้ใช้หลายคนทำได้ด้วย โค้ดที่กระชับและดูแลง่ายขึ้น โดยไม่ต้องใช้ WebSockets
- ขณะที่ HTMX กระจายตรรกะการทำงานไว้ตามแอตทริบิวต์ของ HTML Datastar กลับเพิ่ม ความสม่ำเสมอและความสามารถในการบำรุงรักษา ของตรรกะผ่าน โมเดลการอัปเดตที่ขับเคลื่อนโดยเซิร์ฟเวอร์
- รู้สึกว่า Datastar API มีแอตทริบิวต์น้อยกว่า และช่วยเพิ่มทั้ง ความอ่านง่ายของโค้ดและผลิตภาพ
- Datastar ใช้เทคโนโลยีเว็บเนทีฟอย่างเต็มที่ เช่น Server-Sent Events(SSE), Web Components, CSS View Transitions เพื่อให้ทำงานร่วมกันแบบเรียลไทม์และสร้างโครงสร้างคอมโพเนนต์ที่นำกลับมาใช้ซ้ำได้
บทนำและแรงจูงใจ
- ในปี 2022 David Guillot ได้แบ่งปันกรณีศึกษาที่งาน DjangoCon Europe ว่าเขา ย้าย SaaS ที่สร้างด้วย React มาเป็น HTMX แล้วลดปริมาณโค้ดได้ประมาณ 70% พร้อมปรับปรุงฟีเจอร์
- หลังจากนั้น หลายทีมก็พบว่าเมื่อย้ายจาก single-page app (SPA) ไปเป็น hypermedia app แบบหลายหน้า พวกเขาลดโค้ดลงได้และได้ทั้ง ประสบการณ์นักพัฒนาและประสบการณ์ผู้ใช้ ที่ดีขึ้น
- ผู้เขียนเองก็ย้ายโปรเจกต์จาก HTMX ไปเป็น Datastar และพบว่าโค้ดสั้นลง พร้อมทั้งสามารถพัฒนา แอปเรียลไทม์แบบหลายผู้ใช้ ได้โดยไม่ต้องใช้ WebSocket หรือการจัดการสถานะที่ซับซ้อน
ปัญหาที่เป็นจุดเริ่มต้นของการย้าย
- ระหว่างเตรียม งานบรรยาย FlaskCon 2025 ผู้เขียนพยายามซิงก์ UI โดยใช้ HTMX ร่วมกับ AlpineJS แต่กลับเจอปัญหาเรื่องการซิงก์ UI
- ไลบรารีทั้งสองเป็นเครื่องมือแยกกันที่สร้างโดยคนละผู้พัฒนา จึง สื่อสารกันเองไม่ได้ และนักพัฒนาต้องมารับหน้าที่รวมระบบด้วยตัวเอง
- กระบวนการตั้งค่าเริ่มต้นคอมโพเนนต์ในหลายจังหวะเวลาและการประสานอีเวนต์ ทำให้ต้องเขียนโค้ดและใช้เวลาดีบักมากกว่าที่คาด
- ผู้เขียนจึงลองใช้ Datastar เพราะสนใจที่มันรวมความสามารถของทั้งสองไลบรารีไว้ด้วยกัน แต่มีขนาด ต่ำกว่า 11KB
- เป็นข้อดีต่อประสิทธิภาพการโหลดหน้าเว็บสำหรับผู้ใช้บนอุปกรณ์พกพา
การออกแบบ API ของ Datastar ที่ดีกว่า
- API ของ Datastar ให้ความรู้สึก เบากว่า HTMX มาก และต้องเพิ่มแอตทริบิวต์น้อยกว่าเพื่อให้ได้ผลลัพธ์ที่ต้องการ
- HTMX ต้องใช้หลายแอตทริบิวต์ในปฏิสัมพันธ์ส่วนใหญ่
- ตั้งค่า URL, ระบุ target element, และกำหนดวิธีจัดการ response แยกกันคนละแอตทริบิวต์
- โดยทั่วไปต้องใช้ 2–3 แอตทริบิวต์ แทบทุกครั้ง และบางครั้งต้องไล่ดูตามสาย inheritance เพื่อเข้าใจว่าแอตทริบิวต์ทำงานอย่างไร
<a hx-target="#rebuild-bundle-status-button" hx-select="#rebuild-bundle-status-button" hx-swap="outerHTML" hx-trigger="click" hx-get="/rebuild/status-button"></a> - Datastar โดยทั่วไปใช้ เพียงแอตทริบิวต์เดียว เพื่อทำงานแบบเดียวกัน
<a data-on-click="@get('/rebuild/status-button')"></a>- ต่อให้กลับมาอ่านโค้ดอีกครั้งในอีกไม่กี่เดือน ก็ยังเข้าใจวิธีทำงานได้ง่าย
ความต่างของหลักการทำงาน
- HTMX เป็นไลบรารีฝั่งฟรอนต์เอนด์ ที่มุ่งขยายสเปก HTML ขณะที่ Datastar เป็นไลบรารีแบบเซิร์ฟเวอร์เป็นผู้ขับเคลื่อน ที่มุ่งสร้างแอปอัปเดตเรียลไทม์ประสิทธิภาพสูงด้วยเทคโนโลยีเว็บเนทีฟ
- HTMX นิยามพฤติกรรมผ่านการเพิ่มแอตทริบิวต์ลงบนองค์ประกอบที่ใช้ทริกเกอร์ request และแม้จะอัปเดตองค์ประกอบที่อยู่ไกลออกไปในหน้าเดียวกัน ตรรกะก็ยังถูกกระจายอยู่หลายชั้น
- Datastar ให้ เซิร์ฟเวอร์ตัดสินใจว่าจะเปลี่ยนอะไร ทำให้รวมตรรกะการอัปเดตทั้งหมดไว้ในที่เดียว
-
ตัวอย่าง HTMX
<div> <div id="alert"></div> <button hx-get="/info" hx-select="#info-details" hx-swap="outerHTML" hx-select-oob="#alert"> Get Info! </button> </div>- เมื่อกดปุ่ม จะส่ง GET request ไปที่
/infoแล้วแทนที่ปุ่มด้วยองค์ประกอบที่มี ID เป็นinfo-detailsจาก response และแทนที่องค์ประกอบในหน้าที่มี ID เป็นalertด้วยองค์ประกอบalertจาก response - ปุ่มต้องรับรู้ข้อมูลมากเกินไป และยังต้องรู้ล่วงหน้าว่าเซิร์ฟเวอร์จะส่งอะไรกลับมา จึงทำให้หลักการ "locality of behavior" ของ HTMX อ่อนลง
- เมื่อกดปุ่ม จะส่ง GET request ไปที่
-
แนวทางที่ปรับปรุงแล้วของ Datastar
<div> <div id="alert"></div> <button id="info-details" data-on-click="@get('/info')"> Get Info! </button> </div>- เซิร์ฟเวอร์ส่งกลับสตริง HTML ที่มี root element สองตัวซึ่งใช้ ID เดียวกันกับของเดิม
<p id="info-details">These are the details you are looking for…</p> <div id="alert">Alert! This is a test.</div> - เป็นตัวเลือกที่เรียบง่ายและมีประสิทธิภาพดี
- เซิร์ฟเวอร์ส่งกลับสตริง HTML ที่มี root element สองตัวซึ่งใช้ ID เดียวกันกับของเดิม
คิดในระดับคอมโพเนนต์
- แนวทางที่ดีกว่าคือการมอง HTML เป็น คอมโพเนนต์
- ทำความเข้าใจแก่นแท้ของคอมโพเนนต์นั้น
- ผู้ใช้จะดูข้อมูลเพิ่มเติมของรายการหนึ่งได้อย่างไร
- เมื่อผู้ใช้กดปุ่ม ข้อมูลจะปรากฏขึ้น หรือหากไม่มีข้อมูลก็จะแสดงข้อผิดพลาด ไม่ว่ากรณีใดคอมโพเนนต์ก็จะเข้าสู่สถานะคงที่
-
แยกคอมโพเนนต์ตามสถานะ
- สถานะ placeholder:
<!-- info-component-placeholder.html --> <div id="info-component"> <button data-on-click="@get('/product/{{product.id}}/info')"> Get Info! </button> </div> - สถานะแสดงข้อมูล:
<!-- info-component-get.html --> <div id="info-component"> {% if alert %}<div id="alert">{{ alert }}</div>{% endif %} <p>{{product.additional_information}}</p> </div> - เมื่อเซิร์ฟเวอร์เรนเดอร์ HTML แล้ว Datastar จะอัปเดตหน้าให้อัตโนมัติ
- การคิดในระดับคอมโพเนนต์ช่วย ป้องกันการเข้าสู่สถานะที่ผิดหรือทำสถานะของผู้ใช้หายไป
- สถานะ placeholder:
อัปเดตหลายคอมโพเนนต์พร้อมกัน
- จุดที่น่าประทับใจจากงานบรรยายของ David Guillot คือ เมื่อแอปอัปเดตจำนวนรายการโปรด มันยังอัปเดตองค์ประกอบตัวนับที่อยู่ไกลจากคอมโพเนนต์ต้นทางมากได้พร้อมกันด้วย
- HTMX ทำได้โดยทริกเกอร์ JavaScript event แล้วให้อีเวนต์นั้นไปทริกเกอร์ GET request กับคอมโพเนนต์ที่อยู่ไกลออกไปอีกที
- Datastar สามารถ อัปเดตหลายคอมโพเนนต์พร้อมกันได้แม้อยู่ในฟังก์ชัน synchronous
-
ตัวอย่างตะกร้าสินค้า
- คอมโพเนนต์เพิ่มสินค้าลงตะกร้า:
<form id="purchase-item" data-on-submit="@post('/add-item', {contentType: 'form'})">" > <input type=hidden name="cart-id" value="{{cart.id}}"> <input type=hidden name="item-id" value="{{item.id}}"> <fieldset> <button data-on-click="$quantity -= 1">-</button> <label>Quantity <input name=quantity type=number data-bind-quantity value=1> </label> <button data-on-click="$quantity += 1">+</button> </fieldset> <button type=submit>Add to cart</button> {% if msg %} <p class=message>{{msg}}</p> {% endif %} </form> - คอมโพเนนต์แสดงจำนวนในตะกร้า:
<div id="cart-count"> <svg viewBox="0 0 10 10" xmlns="http://www.w3.org/2000/svg"> <use href="#shoppingCart"> </svg> {{count}} </div> - ใน Django สามารถอัปเดตทั้งสองคอมโพเนนต์ได้ด้วย request เดียวกัน:
from datastar_py.consts import ElementPatchMode from datastar_py.django import ( DatastarResponse, ServerSentEventGenerator as SSE, ) def add_item(request): # omitted important state updates return DatastarResponse([ SSE.patch_elements( render_to_string('purchase-item.html', context=dict(cart=cart, item=item, msg='Item added!')) ), SSE.patch_elements( render_to_string('cart-count.html', context=dict(count=item_count)) ), ])
- คอมโพเนนต์เพิ่มสินค้าลงตะกร้า:
ปรัชญาแบบเว็บเนทีฟ
- จากชุมชน Datastar บน Discord ผู้เขียนเข้าใจว่า Datastar ไม่ใช่แค่ helper script ธรรมดา แต่คือ แนวคิดในการสร้างแอปด้วย primitive พื้นฐานของเว็บ
- ขณะที่ HTMX พยายามผลักดันสเปก HTML ไปข้างหน้า Datastar กลับสนใจ การส่งเสริมการใช้ความสามารถเว็บเนทีฟ มากกว่า
- CSS view transitions
- Server-Sent Events
- Web Components เป็นต้น
- ผู้เขียนประสบความสำเร็จอย่างมากจากการรีแฟกเตอร์คอมโพเนนต์ AlpineJS ที่ซับซ้อนให้กลายเป็น Web Components แบบเรียบง่าย แล้วนำกลับมาใช้ซ้ำในหลายจุด
- นี่เป็นแพตเทิร์นที่ยอดเยี่ยมสำหรับการสร้าง custom HTML element ที่มี locality of behavior สูงและนำกลับมาใช้ซ้ำได้ โดยไม่ต้องพึ่งเครื่องมืออย่าง React
การอัปเดตแบบเรียลไทม์สำหรับแอปหลายผู้ใช้
- แอปที่มีความสามารถด้านการทำงานร่วมกันเป็นฟีเจอร์ระดับแรกเริ่มย่อมสร้างความแตกต่างจากแอปอื่น และ Datastar ก็ช่วยแก้โจทย์นี้ได้
- นักพัฒนา HTMX ส่วนใหญ่มัก polling ดึงข้อมูลจากเซิร์ฟเวอร์ หรือไม่ก็เขียน โค้ด WebSocket แบบคัสตอม ซึ่งเพิ่มความซับซ้อน
- Datastar ใช้เทคโนโลยีเว็บที่เรียบง่ายอย่าง Server-Sent Events(SSE) ให้เซิร์ฟเวอร์ “push” การอัปเดตไปยังไคลเอนต์ที่เชื่อมต่ออยู่
- เมื่อผู้ใช้เพิ่มคอมเมนต์หรือมีการเปลี่ยนสถานะ เซิร์ฟเวอร์จะอัปเดตเบราว์เซอร์ทันที โดยต้องเพิ่มโค้ดเพียงเล็กน้อย
- สามารถสร้างแดชบอร์ดแบบเรียลไทม์ แผงแอดมิน และเครื่องมือทำงานร่วมกันได้โดยไม่ต้องเขียน JavaScript แบบคัสตอม
- หากการเชื่อมต่อฝั่งไคลเอนต์หลุด เบราว์เซอร์จะ พยายามเชื่อมต่อใหม่ให้อัตโนมัติ โดยไม่ต้องมีโค้ดเพิ่มเติม
- และยังสามารถแจ้งเซิร์ฟเวอร์ได้ด้วยว่า “อีเวนต์ล่าสุดที่ได้รับ” คืออะไร
หลีกเลี่ยงความซับซ้อนเกินจำเป็น
- ชุมชน Datastar บน Discord ช่วยให้เข้าใจวิสัยทัศน์ของ Datastar ต่อการสร้างเว็บแอป
- การอัปเดต UI แบบ push-based
- การลดความซับซ้อน
- การใช้เครื่องมืออย่าง Web Components เพื่อจัดการกรณีซับซ้อนเฉพาะจุด
- ชุมชนยังช่วยให้ผู้ใช้ใหม่ตระหนักว่าตนเองกำลังเข้าหาปัญหาอย่างซับซ้อนเกินไป
เคล็ดลับสำคัญ
- อย่ากลัวที่จะ เรนเดอร์ทั้งคอมโพเนนต์ใหม่ทั้งหมด แล้วส่งออกไป
- มันง่ายกว่าและแทบไม่กระทบประสิทธิภาพมากนัก
- ยังได้อัตราการบีบอัดที่ดีกว่า และเบราว์เซอร์ก็ parse สตริง HTML ได้เร็วมาก
- เซิร์ฟเวอร์คือแหล่งความจริงของสถานะ และทรงพลังมากกว่าเบราว์เซอร์
- ควรให้เซิร์ฟเวอร์จัดการสถานะส่วนใหญ่ และอาจไม่จำเป็นต้องใช้ reactive signals มากเท่าที่คิด
- Web Components เหมาะมากสำหรับการห่อหุ้มตรรกะไว้ใน custom element ที่มี locality of behavior สูง
- แอนิเมชันสนามดาวในส่วนหัวของ เว็บไซต์ Datastar เป็นตัวอย่างที่ดี
- องค์ประกอบ
<ds-starfield>ห่อหุ้มโค้ดทั้งหมดของแอนิเมชันสนามดาวไว้ และเปิดเผยพร็อพเพอร์ตี 3 ตัวสำหรับเปลี่ยนสถานะภายใน - Datastar จะขับเคลื่อนพร็อพเพอร์ตีเหล่านั้นเมื่อค่าจาก range input เปลี่ยน หรือเมื่อเมาส์เคลื่อนผ่านองค์ประกอบ
ศักยภาพที่ก้าวข้ามข้อจำกัด
- สิ่งที่น่าตื่นเต้นที่สุดคือศักยภาพที่ Datastar เปิดให้เป็นไปได้
- ชุมชนสร้างโปรเจกต์ที่ก้าวข้ามข้อจำกัดที่นักพัฒนาซึ่งใช้เครื่องมืออื่นมักพบเจออยู่เป็นประจำ
กรณีที่น่าจับตา
- เดโมมอนิเตอร์ฐานข้อมูล ในหน้าตัวอย่าง
- ใช้ Hypermedia เพื่อปรับปรุงทั้งความเร็วและการใช้หน่วยความจำอย่างมาก เมื่อเทียบกับเดโมที่เคยนำเสนอในงานประชุม JavaScript
- 1 พันล้านเช็กบ็อกซ์ ของ Anders Murphy
- หลังจากการทดลอง 1 ล้านเช็กบ็อกซ์เกินขีดความสามารถของเซิร์ฟเวอร์ เขาจึงใช้ Datastar เพื่อทำให้ได้ถึง 1 พันล้านบนเซิร์ฟเวอร์ราคาถูก
- เว็บแอปที่แสดงข้อมูลจากสถานีเรดาร์ทั้งหมดในสหรัฐอเมริกา
- เมื่อสัญญาณของเรดาร์เปลี่ยน จุดที่เกี่ยวข้องใน UI จะเปลี่ยนตามภายใน 100 มิลลิวินาที
- มีการอัปเดต มากกว่า 800,000 จุดต่อวินาที และผู้ใช้สามารถ scrub ย้อนหลังได้ไกลสุด 1 ชั่วโมงด้วยดีเลย์ ต่ำกว่า 700 มิลลิวินาที
- การที่สิ่งนี้เป็นไปได้ในฐานะแอป Hypermedia แสดงให้เห็นถึงสิ่งที่ Datastar ทำให้เกิดขึ้นได้
ประสบการณ์การใช้งานในปัจจุบัน
- ผู้เขียนยังอยู่ในช่วงสำรวจ Datastar และพบว่าสามารถทำ AJAX สำหรับอัปเดต UI แบบมาตรฐานที่ HTMX ทำได้อย่างรวดเร็วและง่ายดาย
- กำลังเรียนรู้และทดลองหลายแพตเทิร์นเพื่อใช้ Datastar ให้ทำได้มากขึ้น
- ผู้เขียนสนใจมานานหลายสิบปีว่าทำอย่างไรจึงจะมอบประสบการณ์ผู้ใช้ที่ดีขึ้นด้วยการอัปเดตแบบเรียลไทม์ และชอบที่ Datastar ทำให้ การอัปเดตแบบ push-based เกิดขึ้นได้แม้อยู่ในโค้ด synchronous
- ตอนเริ่มใช้ HTMX ผู้เขียนรู้สึกตื่นเต้นมาก แต่หลังจากย้ายมา Datastar ก็รู้สึกว่าไม่ได้สูญเสียอะไรไปเลย กลับกันคือ ได้อะไรมากขึ้นกว่ามาก
- หากคุณเคยรู้สึกสนุกกับ HTMX คุณก็น่าจะได้สัมผัสความก้าวกระโดดแบบเดียวกันอีกครั้งกับ Datastar และมันให้ความรู้สึกเหมือน ค้นพบว่าเว็บควรทำอะไรได้ตั้งแต่แรก
2 ความคิดเห็น
Datastar - เฟรมเวิร์กไฮเปอร์มีเดียน้ำหนักเบาสำหรับสร้างเว็บแอปแบบอินเทอร์แอ็กทีฟ
ความเห็นจาก Hacker News
<span hx-target="#rebuild-bundle-status-button" hx-select="#rebuild-bundle-status-button" hx-swap="outerHTML" hx-trigger="click" hx-get="/rebuild/status-button"></span>ดูเหมือนจะถูกเปลี่ยนเป็นโค้ด datastar แบบนี้:<span data-on-click="@get('/rebuild/status-button')"></span>และพอไปดูตัวอย่างอื่น ๆ ก็ยิ่งสับสน สรุปแล้วไม่เข้าใจจริง ๆ ว่าทำไมถึงย้ายจาก htmx ไป Datastar