แท็กเทมเพลตใหม่ของ HTML

การปรับเทมเพลตฝั่งไคลเอ็นต์ให้เป็นมาตรฐาน

เกริ่นนำ

แนวคิดในการจัดทำเทมเพลตไม่ได้เป็นเรื่องใหม่สำหรับการพัฒนาเว็บ อันที่จริงแล้ว การกำหนดภาษา/เครื่องมือฝั่งเซิร์ฟเวอร์ เช่น 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> จะทำให้เรามีคุณสมบัติที่สำคัญบางประการ

  1. เนื้อหาของวิดีโอจะค่อยๆ ถูกเฉื่อยชาจนกว่าจะเปิดใช้งาน พูดง่ายๆ ก็คือมาร์กอัปของคุณซ่อน DOM อยู่และไม่แสดงผล

  2. เนื้อหาภายในเทมเพลตจะไม่มีผลข้างเคียง สคริปต์ไม่ทำงาน รูปภาพไม่โหลด เสียงไม่เล่น ...จนกว่าจะมีการใช้เทมเพลต

  3. ถือว่าเนื้อหาไม่ได้อยู่ในเอกสาร การใช้ document.getElementById() หรือ querySelector() ในหน้าหลักจะไม่แสดงผลโหนดย่อยของเทมเพลต

  4. คุณสามารถวางเทมเพลตไว้ที่ใดก็ได้ภายใน <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 อีกต่อไป การทำให้กระบวนการเขียนเว็บทั้งหมดง่ายยิ่งขึ้น ดูแลรักษาได้มากขึ้น และมีฟีเจอร์ครบถ้วนอยู่เสมอเป็นสิ่งที่ดีในหนังสือของฉัน

แหล่งข้อมูลเพิ่มเติม