การปรับเทมเพลตฝั่งไคลเอ็นต์ให้เป็นมาตรฐาน
เกริ่นนำ
แนวคิดในการจัดทำเทมเพลตไม่ได้เป็นเรื่องใหม่สำหรับการพัฒนาเว็บ อันที่จริงแล้ว การกำหนดภาษา/เครื่องมือฝั่งเซิร์ฟเวอร์ เช่น Django (Python), ERB/Haml (Ruby) และ Smarty (PHP) นั้นมีให้ใช้งานมานานแล้ว อย่างไรก็ตาม ในช่วง 2-3 ปีที่ผ่านมา เราได้เห็น เฟรมเวิร์ก MVC เพิ่มขึ้นมาก เทมเพลตทั้งหมดมีความแตกต่างกันเล็กน้อย แต่ส่วนใหญ่ก็มีกลไกทั่วไปในการแสดงผลเลเยอร์การนำเสนอ (หรือที่เรียกว่ามุมมอง da)
ว่ากันตามจริง เทมเพลตก็ยอดเยี่ยม ลองถามดูได้เลย แม้แต่คำจำกัดความก็จะช่วยให้คุณรู้สึกอบอุ่นและผ่อนคลาย ดังนี้
"... ไม่จำเป็นต้องสร้างใหม่ทุกครั้ง... " ไม่รู้เกี่ยวกับคุณเลย แต่ผมชอบ หลีกเลี่ยงงานเพิ่มเติม ทำไมแพลตฟอร์มเว็บจึงขาด การสนับสนุนดั้งเดิมสำหรับสิ่งที่นักพัฒนาซอฟต์แวร์ให้ความสำคัญ
ข้อกำหนดเทมเพลต HTML ของ WhatsApp คือคำตอบ โดยจะกำหนดองค์ประกอบ <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>
และมีเนื้อหาประเภทใดก็ได้ที่ได้รับอนุญาตในองค์ประกอบเหล่านั้น โปรดทราบว่า "anywhere" หมายความว่า สามารถใช้<template>
ได้อย่างปลอดภัยในที่ที่โปรแกรมแยกวิเคราะห์ HTML ไม่อนุญาต...แต่ทำได้โมเดลเนื้อหาเด็กเท่านั้น และยังกำหนดเป็นหน่วยย่อยของ<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>
รับทราบ
นี่คือ Gocha บางอย่างที่ฉันพบเมื่อใช้ <template>
ในธรรมชาติ:
- หากคุณใช้ modpagespeed โปรดระวังข้อบกพร่องนี้ เทมเพลตที่กำหนด
<style scoped>
ในบรรทัด โดยเทมเพลตจำนวนมากจะย้ายไปไว้ที่ส่วนหัวโดยใช้กฎการเขียน CSS ใหม่ของ PageSpeed - ไม่มีวิธี "แสดงผลล่วงหน้า" เทมเพลต ซึ่งหมายความว่าคุณจะไม่สามารถโหลดเนื้อหาล่วงหน้า, ประมวลผล JS, ดาวน์โหลด CSS เริ่มต้น ฯลฯ ได้ ซึ่งจะมีผลกับทั้งเซิร์ฟเวอร์และไคลเอ็นต์ เทมเพลตเดียวจะแสดงผลก็ต่อเมื่อเทมเพลตมีการเผยแพร่
โปรดระวังเทมเพลตที่ซ้อนกัน และไม่ทำงานตามที่คาดไว้ เช่น
<template> <ul> <template> <li>Stuff</li> </template> </ul> </template>
การเปิดใช้งานเทมเพลตด้านนอกจะไม่ใช้เทมเพลตภายในที่ใช้งานอยู่ กล่าวคือ เทมเพลตที่ซ้อนกัน กำหนดให้ต้องเปิดใช้งานผู้เผยแพร่โฆษณาย่อยด้วยตนเองด้วย
ถนนสู่มาตรฐาน
อย่าลืมรู้ว่าเรามาจากที่ใด การสร้างเทมเพลต HTML ที่อิงตามมาตรฐาน มีมาอย่างยาวนาน ในช่วงหลายปีที่ผ่านมา เราได้มีเคล็ดลับเจ๋งๆ ในการสร้างเทมเพลตที่ใช้ซ้ำได้ ข้างล่างนี้เป็น 2 ปัญหาที่พบได้ทั่วไป เราได้รวมเอาพวกเขามาไว้ในบทความนี้เพื่อเปรียบเทียบ
วิธีที่ 1: DOM นอกหน้าจอ
แนวทางหนึ่งที่ผู้คนใช้กันมานานคือการสร้าง DOM แบบ "นอกหน้าจอ" และซ่อนไม่ให้เห็นโดยใช้แอตทริบิวต์ hidden
หรือ display:none
<div id="mytemplate" hidden>
<img src="logo.png">
<div class="comment"></div>
</div>
แม้ว่าเทคนิคนี้จะได้ผล แต่ก็มีข้อเสียหลายประการ บทสรุปของเทคนิคนี้
- การใช้ DOM - เบราว์เซอร์จะรู้จัก DOM ทำได้ดีเลย เราสามารถโคลนได้ง่ายๆ
- ไม่มีการแสดงผล - การเพิ่ม
hidden
จะป้องกันไม่ให้ระบบแสดงการบล็อก - ไม่เฉื่อย - แม้ว่าเนื้อหาของเราจะซ่อนอยู่ แต่ยังส่งคำขอเครือข่ายสำหรับรูปภาพอยู่
- การจัดรูปแบบและธีมที่ไม่ซับซ้อน - หน้าที่ฝังต้องนำหน้ากฎ CSS ทั้งหมดด้วย
#mytemplate
เพื่อกำหนดขอบเขตสไตล์ไปจนถึงเทมเพลต ปัญหานี้ไม่รัดกุมและไม่มีการรับประกันว่าเราจะไม่พบความขัดแย้งในการตั้งชื่อในอนาคต เช่น เราแนะนำให้ใช้วิธีนี้หากหน้าที่ฝังมีองค์ประกอบที่มีรหัสนั้นอยู่แล้ว
วิธีที่ 2: สคริปต์ทำงานหนักเกินไป
อีกเทคนิคหนึ่งคือ <script>
มากเกินไป และบิดเบือนเนื้อหาเป็นสตริง John Resig อาจเป็นบุคคลแรกที่แสดงสิ่งนี้ในปี 2008 ด้วยยูทิลิตี Templating ขนาดเล็ก
ตอนนี้ก็ยังมีคนอื่นๆ อีกมากมาย รวมถึงเด็กใหม่ๆ ในบล็อกอย่าง handlebars.js
เช่น
<script id="mytemplate" type="text/x-handlebars-template">
<img src="logo.png">
<div class="comment"></div>
</script>
บทสรุปของเทคนิคนี้
- ไม่มีอะไรแสดงผล - เบราว์เซอร์ไม่แสดงผลการบล็อกนี้เนื่องจาก
<script>
คือdisplay:none
โดยค่าเริ่มต้น - Inert - เบราว์เซอร์จะไม่แยกวิเคราะห์เนื้อหาสคริปต์เป็น JS เพราะมีการตั้งค่าประเภทเป็นอย่างอื่นที่ไม่ใช่ "text/javascript"
- ปัญหาด้านความปลอดภัย - สนับสนุนให้ใช้
.innerHTML
การแยกวิเคราะห์สตริงรันไทม์ของข้อมูลที่ได้จากผู้ใช้อาจทําให้เกิดช่องโหว่ XSS ได้ง่ายๆ
บทสรุป
จำได้ไหมว่าเวลาที่ jQuery ทำให้ทำงานได้กับ DOM Dead เป็นเรื่องง่ายใช่ไหม ผลที่ได้คือมีการเพิ่ม querySelector()
/querySelectorAll()
ลงในแพลตฟอร์ม เป็นผู้ชนะที่เห็นได้ชัดใช่ไหม ไลบรารีที่ได้รับความนิยมในการดึงข้อมูล DOM
ด้วยตัวเลือก CSS และมาตรฐานในภายหลัง ซึ่งอาจไม่ได้ผลเสมอไป แต่ฉันชอบมาก
ฉันคิดว่า <template>
เป็นกรณีที่คล้ายกัน โดยมาตรฐานนี้เป็นมาตรฐานในการสร้างเทมเพลตฝั่งไคลเอ็นต์
แต่ที่สำคัญกว่านั้นคือ ไม่ต้องใช้เคล็ดลับจากปี 2008 อีกต่อไป
การทำให้กระบวนการเขียนเว็บทั้งหมดง่ายยิ่งขึ้น ดูแลรักษาได้มากขึ้น และมีฟีเจอร์ครบถ้วนอยู่เสมอเป็นสิ่งที่ดีในหนังสือของฉัน
แหล่งข้อมูลเพิ่มเติม
- ข้อกำหนดของ WhatWG
- ข้อมูลเบื้องต้นเกี่ยวกับคอมโพเนนต์เว็บ
- <web>components</web> (video) - เป็นการนำเสนอที่ครอบคลุมที่สุดของคุณอย่างแท้จริง