การกำหนดมาตรฐานเทมเพลตฝั่งไคลเอ็นต์
บทนำ
แนวคิดเทมเพลตไม่ใช่แนวคิดใหม่สำหรับการพัฒนาเว็บ อันที่จริงแล้ว ภาษา/เครื่องมือเทมเพลตฝั่งเซิร์ฟเวอร์อย่าง Django (Python), ERB/Haml (Ruby) และ Smarty (PHP) นั้นมีมานานแล้ว อย่างไรก็ตาม ในช่วง 2-3 ปีที่ผ่านมา เราได้เห็นเฟรมเวิร์ก MVC เกิดขึ้นมากมาย เครื่องมือเหล่านี้ทั้งหมดจะแตกต่างกันเล็กน้อย แต่ส่วนใหญ่จะใช้กลไกทั่วไปในการแสดงผลเลเยอร์การนำเสนอ (หรือที่เรียกว่ามุมมอง DA) ซึ่งก็คือเทมเพลต
ว่ากันตามจริง เทมเพลตสวยมาก ถามไปรอบๆ ได้เลย แม้แต่คำจำกัดความก็ทำให้รู้สึกอบอุ่นและสบายใจ
"…ไม่ต้องสร้างใหม่ทุกครั้ง…" ไม่รู้คุณคิดยังไง แต่เราชอบที่จะหลีกเลี่ยงการทำงานที่ไม่จำเป็น เหตุใดแพลตฟอร์มเว็บจึงไม่มีการสนับสนุนแบบดั้งเดิมสำหรับสิ่งที่นักพัฒนาแอปให้ความสำคัญอย่างชัดเจน
ข้อกำหนดของเทมเพลต HTML ของ WHATWG คือคำตอบ ซึ่งจะกำหนดองค์ประกอบ <template>
ใหม่ซึ่งอธิบายแนวทางแบบมาตรฐานที่อิงตาม DOM สำหรับการสร้างเทมเพลตฝั่งไคลเอ็นต์ เทมเพลตช่วยให้คุณประกาศข้อมูลโค้ดมาร์กอัปที่แยกวิเคราะห์เป็น HTML ได้ แต่จะไม่มีการนำไปใช้เมื่อโหลดหน้าเว็บ แต่สามารถสร้างอินสแตนซ์ได้ภายหลังเมื่อรันไทม์ หากต้องการอ้างถึง Rafael Weinstein ให้ทำดังนี้
มันคือพื้นที่สำหรับวาง HTML จำนวนมากที่คุณไม่อยากให้เบราว์เซอร์ยุ่งเหยิง ไม่ว่าจะด้วยเหตุผลใดก็ตาม
Rafael Weinstein (ผู้เขียนข้อมูลจำเพาะ)
การตรวจหาองค์ประกอบ
ในการตรวจหา <template>
ให้สร้างองค์ประกอบ DOM และตรวจสอบว่ามีพร็อพเพอร์ตี้ .content
อยู่
function supportsTemplate() {
return 'content' in document.createElement('template');
}
if (supportsTemplate()) {
// Good to go!
} else {
// Use old templating techniques or libraries.
}
การประกาศเนื้อหาเทมเพลต
องค์ประกอบ <template>
ของ HTML แสดงถึงเทมเพลตในมาร์กอัป โดยจะมี "เนื้อหาเทมเพลต" ซึ่งโดยพื้นฐานแล้วคือ DOM ที่ไม่เปลี่ยนแปลงซึ่งโคลนได้
ให้คิดว่าเทมเพลตเป็นโครงสร้างพื้นฐานที่คุณสามารถใช้ (และใช้ซ้ำ) ได้ตลอดอายุของแอป
หากต้องการสร้างเนื้อหาที่ใช้เทมเพลต ให้ประกาศมาร์กอัปบางส่วนและตัดขึ้นบรรทัดใหม่ในองค์ประกอบ <template>
<template id="mytemplate">
<img src="" alt="great image">
<div class="comment"></div>
</template>
เสาหลัก
การรวมเนื้อหาใน <template>
จะช่วยให้เราทราบพร็อพเพอร์ตี้ที่สำคัญบางอย่าง
เนื้อหาจะเฉื่อยขึ้นจนกว่าจะเปิดใช้งาน โดยพื้นฐานแล้ว มาร์กอัปของคุณคือ DOM ที่ซ่อนอยู่และจะไม่แสดงผล
เนื้อหาภายในเทมเพลตจะไม่มีผลข้างเคียง สคริปต์ไม่ทำงาน รูปภาพไม่โหลด เสียงไม่เล่น...จนกว่าจะมีการใช้เทมเพลต
ระบบจะถือว่าเนื้อหาไม่ได้อยู่ในเอกสาร การใช้
document.getElementById()
หรือquerySelector()
ในหน้าหลักจะไม่แสดงโหนดย่อยของเทมเพลตเทมเพลตสามารถวางที่ใดก็ได้ภายใน
<head>
,<body>
หรือ<frameset>
และมีเนื้อหาประเภทใดก็ได้ที่อนุญาตในองค์ประกอบเหล่านั้น โปรดทราบว่า "ทุกที่" หมายความว่า<template>
สามารถใช้ได้อย่างปลอดภัยในที่ที่โปรแกรมแยกวิเคราะห์ HTML ไม่อนุญาต ยกเว้นองค์ประกอบ content model นอกจากนี้ยังวางเป็นรายการย่อยของ<table>
หรือ<select>
ได้ด้วย
<table>
<tr>
<template id="cells-to-repeat">
<td>some content</td>
</template>
</tr>
</table>
การเปิดใช้งานเทมเพลต
หากต้องการใช้เทมเพลต คุณต้องเปิดใช้งานเทมเพลตนั้น มิเช่นนั้น เนื้อหาของวิดีโอจะแสดงผลไม่ได้
วิธีที่ง่ายที่สุดในการดำเนินการนี้คือการสร้างสำเนาเชิงลึกของ .content
โดยใช้ document.importNode()
พร็อพเพอร์ตี้ .content
คือ DocumentFragment
แบบอ่านอย่างเดียวที่มีข้อมูลสำคัญของเทมเพลต
var t = document.querySelector('#mytemplate');
// Populate the src at runtime.
t.content.querySelector('img').src = 'logo.png';
var clone = document.importNode(t.content, true);
document.body.appendChild(clone);
หลังจากสร้างเทมเพลตแล้ว เนื้อหาของเทมเพลตจะ "เผยแพร่" ในตัวอย่างนี้ ระบบจะโคลนเนื้อหา ส่งคําขอรูปภาพ และแสดงผลมาร์กอัปสุดท้าย
เดโม
ตัวอย่าง: สคริปต์ที่ไม่มีการใช้งาน
ตัวอย่างนี้แสดงลักษณะคงที่ของเนื้อหาเทมเพลต <script>
จะทำงานเมื่อมีการกดปุ่มเท่านั้น โดยจะประทับเทมเพลต
<button onclick="useIt()">Use me</button>
<div id="container"></div>
<script>
function useIt() {
var content = document.querySelector('template').content;
// Update something in the template DOM.
var span = content.querySelector('span');
span.textContent = parseInt(span.textContent) + 1;
document.querySelector('#container').appendChild(
document.importNode(content, true)
);
}
</script>
<template>
<div>Template used: <span>0</span></div>
<script>alert('Thanks!')</script>
</template>
ตัวอย่าง: การสร้าง Shadow DOM จากเทมเพลต
ผู้ใช้ส่วนใหญ่จะแนบ Shadow DOM กับโฮสต์โดยการตั้งค่าสตริงมาร์กอัปเป็น .innerHTML
<div id="host"></div>
<script>
var shadow = document.querySelector('#host').createShadowRoot();
shadow.innerHTML = '<span>Host node</span>';
</script>
ปัญหาของแนวทางนี้คือยิ่ง Shadow DOM มีความซับซ้อนมากเท่าใด คุณก็จะต้องต่อสตริงกันมากขึ้นเท่านั้น ไม่สามารถปรับขนาดได้ ทุกอย่างจะยุ่งเหยิงอย่างรวดเร็ว และทารกก็เริ่มร้องไห้ แนวทางนี้ยังเป็นจุดกำเนิดของ XSS มาตั้งแต่แรกด้วย <template>
จะมาช่วยแก้ปัญหา
วิธีที่เหมาะสมกว่าคือการทำงานกับ DOM โดยตรงโดยการต่อท้ายเนื้อหาเทมเพลตไปยังรูทเงา
<template>
<style>
:host {
background: #f8f8f8;
padding: 10px;
transition: all 400ms ease-in-out;
box-sizing: border-box;
border-radius: 5px;
width: 450px;
max-width: 100%;
}
:host(:hover) {
background: #ccc;
}
div {
position: relative;
}
header {
padding: 5px;
border-bottom: 1px solid #aaa;
}
h3 {
margin: 0 !important;
}
textarea {
font-family: inherit;
width: 100%;
height: 100px;
box-sizing: border-box;
border: 1px solid #aaa;
}
footer {
position: absolute;
bottom: 10px;
right: 5px;
}
</style>
<div>
<header>
<h3>Add a Comment
</header>
<content select="p"></content>
<textarea></textarea>
<footer>
<button>Post</button>
</footer>
</div>
</template>
<div id="host">
<p>Instructions go here</p>
</div>
<script>
var shadow = document.querySelector('#host').createShadowRoot();
shadow.appendChild(document.querySelector('template').content);
</script>
ข้อควรระวัง
ต่อไปนี้คือข้อควรระวังบางประการที่เราพบเมื่อใช้ <template>
จริง
- หากคุณใช้ modpagespeed โปรดระวังข้อบกพร่องนี้ เทมเพลตที่กําหนด
<style scoped>
ในบรรทัดควรย้ายไปไว้ที่ส่วนหัวด้วยกฎการเขียน CSS ใหม่ของ PageSpeed - คุณไม่สามารถ "แสดงผลเทมเพลตล่วงหน้า" ได้ นั่นหมายความว่าคุณไม่สามารถโหลดเนื้อหาล่วงหน้า, ประมวลผล JS, ดาวน์โหลด CSS เริ่มต้น ฯลฯ ซึ่งกรณีนี้ต้องใช้ทั้งเซิร์ฟเวอร์และไคลเอ็นต์ เทมเพลตจะแสดงผลก็ต่อเมื่อเผยแพร่เท่านั้น
โปรดระมัดระวังเทมเพลตที่ฝังอยู่ การดำเนินการดังกล่าวอาจไม่เป็นไปตามที่คุณคาดหวัง เช่น
<template> <ul> <template> <li>Stuff</li> </template> </ul> </template>
การเปิดใช้งานเทมเพลตด้านนอกจะไม่เปิดใช้งานเทมเพลตด้านใน กล่าวคือ เทมเพลตที่ซ้อนกันกำหนดให้ผู้เผยแพร่โฆษณาย่อยเปิดใช้งานด้วยตนเองด้วย
เส้นทางสู่มาตรฐาน
อย่าลืมจุดเริ่มต้นของเรา เส้นทางสู่เทมเพลต HTML ตามมาตรฐานนั้นยาวนาน ตลอดหลายปีที่ผ่านมา เราได้คิดค้นเทคนิคที่ค่อนข้างฉลาดในการสร้างเทมเพลตที่นํากลับมาใช้ซ้ำได้ ด้านล่างนี้คือ 2 ตัวอย่างที่พบบ่อยซึ่งเราพบ เราได้รวมข้อมูลดังกล่าวไว้ในบทความนี้เพื่อเปรียบเทียบ
วิธีที่ 1: DOM นอกหน้าจอ
แนวทางหนึ่งที่ผู้คนใช้มาอย่างยาวนานคือการสร้าง DOM "นอกหน้าจอ" และซ่อน DOM นั้นไม่ให้แสดงโดยใช้แอตทริบิวต์ hidden
หรือ display:none
<div id="mytemplate" hidden>
<img src="logo.png">
<div class="comment"></div>
</div>
แม้ว่าเทคนิคนี้จะได้ผล แต่ก็มีจุดด้อยอยู่หลายประการ สรุปของเทคนิคนี้
- การใช้ DOM - เบราว์เซอร์รู้จัก DOM ถนัดใช้เลย เราสามารถโคลนไฟล์ได้อย่างง่ายดาย
- ไม่มีการแสดงผล - การเพิ่ม
hidden
จะทำให้บล็อกไม่แสดง - ไม่เฉื่อยชา แม้จะซ่อนเนื้อหาไว้ แต่จะยังคงมีการส่งคำขอเครือข่ายสำหรับรูปภาพนั้น
- การจัดรูปแบบและธีมที่ยุ่งยาก - หน้าการฝังต้องใส่
#mytemplate
ไว้หน้ากฎ CSS ทั้งหมดเพื่อกำหนดขอบเขตรูปแบบลงไปจนถึงเทมเพลต วิธีนี้ไม่เสถียรและเราไม่สามารถรับประกันว่าจะไม่เกิดข้อขัดแย้งในการตั้งชื่อในอนาคต ตัวอย่างเช่น เราจะแก้ปัญหาไม่ได้หากหน้าเว็บที่ฝังมีองค์ประกอบที่มีรหัสนั้นอยู่แล้ว
วิธีที่ 2: สคริปต์การโอเวอร์โหลด
อีกเทคนิคหนึ่งคือการโอเวอร์โหลด <script>
และจัดการเนื้อหาของ <script>
ในรูปแบบสตริง John Resig น่าจะเป็นคนแรกที่แสดงสิ่งนี้ในปี 2008 ด้วยยูทิลิตีเทมเพลตขนาดเล็ก
ปัจจุบันมีเครื่องมืออื่นๆ อีกมากมาย รวมถึงเครื่องมือใหม่ๆ เช่น handlebars.js
เช่น
<script id="mytemplate" type="text/x-handlebars-template">
<img src="logo.png">
<div class="comment"></div>
</script>
สรุปของเทคนิคนี้
- ไม่มีอะไรแสดงผล - เบราว์เซอร์ไม่แสดงผลการบล็อกนี้เนื่องจาก
<script>
คือdisplay:none
โดยค่าเริ่มต้น - ไม่มีการใช้งาน - เบราว์เซอร์ไม่แยกวิเคราะห์เนื้อหาสคริปต์เป็น JS เนื่องจากมีการตั้งค่าประเภทเป็นอย่างอื่นที่ไม่ใช่ "text/javascript"
- ปัญหาด้านความปลอดภัย - แนะนำให้ใช้
.innerHTML
การแยกวิเคราะห์สตริงรันไทม์ของข้อมูลที่ได้จากผู้ใช้อาจทําให้เกิดช่องโหว่ XSS ได้ง่าย
บทสรุป
จำได้ไหมว่า jQuery ทําให้ DOM ใช้งานได้ง่ายมาก ผลลัพธ์คือ querySelector()
/querySelectorAll()
เพิ่มลงในแพลตฟอร์ม เห็นๆ กันอยู่ว่าชนะใช่ไหม ไลบรารีที่ได้รับความนิยมในการดึงข้อมูล DOM ด้วยตัวเลือก CSS และมาตรฐานต่างๆ นำมาใช้ภายหลัง บางครั้งอาจไม่ได้ผล แต่เราก็ชอบเมื่อได้ผล
เราคิดว่า <template>
เป็นเคสที่คล้ายกัน ซึ่งทำให้วิธีที่เราใช้เทมเพลตฝั่งไคลเอ็นต์เป็นมาตรฐานเดียวกัน และที่สำคัญกว่านั้นคือทำให้เราไม่ต้องใช้แฮ็กในปี 2008 อีกต่อไป
การทําให้กระบวนการเขียนเว็บทั้งหมดมีความสมเหตุสมผลมากขึ้น บํารุงรักษาได้ง่ายขึ้น และมีคุณสมบัติครบถ้วนมากขึ้นเป็นสิ่งที่ดีเสมอ
แหล่งข้อมูลเพิ่มเติม
- ข้อกำหนดของ WhatsApp
- ข้อมูลเบื้องต้นเกี่ยวกับคอมโพเนนต์เว็บ
- <web>components</web> (วิดีโอ) - นี่เป็นงานนำเสนอที่ครอบคลุมและยอดเยี่ยมอย่างยิ่ง