Thẻ mẫu mới của HTML

Chuẩn hoá mẫu phía máy khách

Giới thiệu

Khái niệm tạo mẫu không phải là khái niệm mới trong lĩnh vực phát triển web. Trên thực tế, các ngôn ngữ/công cụ tạo mẫu phía máy chủ như Django (Python), ERB/Haml (Ruby) và Smarty (PHP) đã xuất hiện từ lâu. Tuy nhiên, trong vài năm qua, chúng tôi đã thấy sự bùng nổ của các khung MVC xuất hiện. Tất cả đều hơi khác nhau, nhưng hầu hết đều có chung một cơ chế chung để hiển thị lớp trình bày (còn gọi là chế độ xem da): các mẫu.

Hãy đối mặt với nó. Mẫu thì quá tuyệt. Hãy tiếp tục, hỏi xung quanh. Ngay cả định nghĩa của âm thanh đó cũng khiến bạn cảm thấy ấm áp và dễ chịu:

"...không cần phải tạo lại mỗi lần..." Tôi không biết về bạn, nhưng tôi thích tránh việc phải làm thêm. Tại sao nền tảng web lại thiếu sự hỗ trợ thuần tuý dành cho vấn đề mà nhà phát triển rõ ràng quan tâm?

Thông số kỹ thuật về mẫu HTML WhatsApp chính là câu trả lời. Phần tử này xác định một phần tử <template> mới mô tả cách tiếp cận dựa trên DOM chuẩn để tạo mẫu phía máy khách. Mẫu cho phép bạn khai báo các mảnh đánh dấu được phân tích cú pháp dưới dạng HTML, không dùng đến khi tải trang, nhưng có thể tạo thực thể sau này trong thời gian chạy. Để trích dẫn Rafael Weinstein:

Đó là nơi bạn đặt một lượng lớn HTML mà bạn không muốn trình duyệt xử lý vì bất kỳ lý do gì.

Rafael Weinstein (tác giả cụ thể)

Phát hiện tính năng

Để phát hiện tính năng <template>, hãy tạo phần tử DOM và kiểm tra xem thuộc tính .content có tồn tại hay không:

function supportsTemplate() {
    return 'content' in document.createElement('template');
}

if (supportsTemplate()) {
    // Good to go!
} else {
    // Use old templating techniques or libraries.
}

Khai báo nội dung mẫu

Phần tử HTML <template> đại diện cho một mẫu trong mã đánh dấu của bạn. Thư viện này chứa "nội dung mẫu"; về cơ bản, cố định các đoạn của DOM có thể sao chép. Hãy coi mẫu như là những mảnh ghép có thể sử dụng (và tái sử dụng) trong suốt thời gian hoạt động của ứng dụng.

Để tạo nội dung theo mẫu, hãy khai báo một số mã đánh dấu và gói mã đánh dấu đó vào phần tử <template>:

<template id="mytemplate">
    <img src="" alt="great image">
    <div class="comment"></div>
</template>

Các yếu tố chính

Việc gói nội dung trong <template> cung cấp cho chúng ta một vài thuộc tính quan trọng.

  1. Nội dung của mẫu này sẽ được lưu trữ hiệu quả cho đến khi được kích hoạt. Về cơ bản, mã đánh dấu của bạn sẽ ẩn DOM và không hiển thị.

  2. Mọi nội dung trong mẫu sẽ không có hiệu ứng phụ. Tập lệnh không chạy, hình ảnh không tải, âm thanh không phát,...cho đến khi mẫu được sử dụng.

  3. Nội dung được coi là không có trong tài liệu. Việc sử dụng document.getElementById() hoặc querySelector() trong trang chính sẽ không trả về các nút con của mẫu.

  4. Mẫu có thể được đặt ở bất kỳ đâu bên trong <head>, <body> hoặc <frameset> và có thể chứa bất kỳ loại nội dung nào được phép trong các phần tử đó. Xin lưu ý rằng "bất cứ nơi nào" có nghĩa là <template> có thể được dùng một cách an toàn ở các vị trí mà trình phân tích cú pháp HTML không cho phép... tất cả trừ phần tử con mô hình nội dung. Bạn cũng có thể đặt tệp này dưới dạng phần tử con của <table> hoặc <select>:

<table>
  <tr>
    <template id="cells-to-repeat">
      <td>some content</td>
    </template>
  </tr>
</table>

Kích hoạt mẫu

Để sử dụng một mẫu, bạn cần phải kích hoạt mẫu đó. Nếu không, nội dung của lớp đó sẽ không bao giờ hiển thị. Cách đơn giản nhất để thực hiện việc này là tạo bản sao sâu của .content bằng document.importNode(). Thuộc tính .content là một DocumentFragment chỉ đọc có chứa các phần phụ của mẫu.

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);

Sau khi con dấu một mẫu, nội dung của mẫu đó "sẽ xuất hiện trực tuyến". Trong ví dụ cụ thể này, nội dung được sao chép, yêu cầu hình ảnh được thực hiện và thẻ đánh dấu cuối cùng sẽ hiển thị.

Bản thu thử

Ví dụ: Tập lệnh Inert

Ví dụ này minh hoạ tính tĩnh của nội dung mẫu. <script> chỉ chạy khi người dùng nhấn nút này và đóng mẫu.

<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>

Ví dụ: Tạo DOM bóng từ mẫu

Hầu hết mọi người đều đính kèm DOM tối vào máy chủ lưu trữ bằng cách đặt một chuỗi đánh dấu thành .innerHTML:

<div id="host"></div>
<script>
  var shadow = document.querySelector('#host').createShadowRoot();
  shadow.innerHTML = '<span>Host node</span>';
</script>

Vấn đề của phương pháp này là DOM bóng của bạn càng phức tạp thì bạn càng thực hiện nhiều nối chuỗi. Nó không tăng cỡ, mọi thứ trở nên lộn xộn và em bé bắt đầu khóc. Phương pháp này cũng là cách XSS ra đời ngay từ đầu! <template> đến nơi giải cứu.

Một cách sáng suốt hơn là làm việc trực tiếp với DOM bằng cách thêm nội dung mẫu vào một gốc bóng (shadow):

<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>

Trò chơi lấy bối cảnh

Sau đây là một số yêu cầu tôi gặp phải khi sử dụng <template> ngoài đời thực:

  • Nếu bạn đang sử dụng modpagespeed, hãy cẩn thận với lỗi này. Các mẫu xác định <style scoped> cùng dòng, nhiều mẫu được chuyển lên phần đầu bằng quy tắc ghi lại CSS của PageSpeed.
  • Không có cách nào để "kết xuất trước" một mẫu, nghĩa là bạn không thể tải trước các thành phần, xử lý JS, tải CSS ban đầu xuống, v.v. Hoạt động này áp dụng cho cả máy chủ và máy khách. Thời điểm duy nhất một mẫu kết xuất là khi mẫu đó được phát hành.
  • Hãy cẩn thận với các mẫu lồng nhau. Chúng không hoạt động như bạn mong đợi. Ví dụ:

    <template>
      <ul>
        <template>
          <li>Stuff</li>
        </template>
      </ul>
    </template>
    

    Việc kích hoạt mẫu bên ngoài sẽ không hoạt động các mẫu bên trong. Điều này có nghĩa là các mẫu lồng nhau yêu cầu các con của chúng cũng phải được kích hoạt theo cách thủ công.

Con đường đạt được tiêu chuẩn

Đừng quên chúng ta đã xuất thân từ đâu. Con đường đến với các mẫu HTML dựa trên tiêu chuẩn đã trải qua một chặng đường dài. Trong những năm qua, chúng tôi đã tìm ra một số thủ thuật khá thông minh để tạo các mẫu có thể sử dụng lại. Dưới đây là hai lý do phổ biến mà tôi đã gặp. Tôi sẽ đưa chúng vào bài viết này để bạn so sánh.

Phương pháp 1: DOM ngoài màn hình

Một phương pháp mà mọi người đã áp dụng trong một thời gian dài là tạo DOM "ngoài màn hình" và ẩn nó khỏi khung hiển thị bằng cách sử dụng thuộc tính hidden hoặc display:none.

<div id="mytemplate" hidden>
  <img src="logo.png">
  <div class="comment"></div>
</div>

Mặc dù kỹ thuật này có hiệu quả, nhưng vẫn có một số nhược điểm. Tóm tắt của kỹ thuật này:

  • Sử dụng DOM – trình duyệt biết DOM. Nó rất tốt. Chúng ta có thể dễ dàng sao chép dữ liệu đó.
  • Không có gì được hiển thị – việc thêm hidden sẽ ngăn khối hiển thị.
  • Không trơ – mặc dù nội dung của chúng ta bị ẩn, yêu cầu mạng vẫn được thực hiện cho hình ảnh.
  • Tuỳ chỉnh kiểu và sắp xếp theo chủ đề gây khó chịu – một trang nhúng phải thêm #mytemplate vào tiền tố của mọi quy tắc CSS để thu hẹp phạm vi các kiểu cho mẫu. Việc này rất dễ gặp lỗi và không có gì đảm bảo rằng chúng ta sẽ không gặp phải xung đột khi đặt tên trong tương lai. Ví dụ: chúng ta sẽ lưu trữ thông báo nếu trang nhúng đã có một phần tử với mã nhận dạng đó.

Phương pháp 2: nạp chồng tập lệnh

Một kỹ thuật khác là nạp chồng <script> và sử dụng nội dung của nó dưới dạng một chuỗi. John Resig có thể là người đầu tiên đưa ra trình bày này vào năm 2008 bằng Tiện ích Micro Khuôn mẫu của mình. Bây giờ, đã có nhiều thành phần khác, bao gồm cả một số thành phần mới trong khối như handlebars.js.

Ví dụ:

<script id="mytemplate" type="text/x-handlebars-template">
  <img src="logo.png">
  <div class="comment"></div>
</script>

Tóm tắt của kỹ thuật này:

  • Không có kết xuất nào – trình duyệt không kết xuất khối này vì <script>display:none theo mặc định.
  • Inert – trình duyệt không phân tích cú pháp nội dung tập lệnh dưới dạng JS vì loại trình duyệt được đặt thành một giá trị không phải là "text/javascript".
  • Vấn đề bảo mật – khuyến khích sử dụng .innerHTML. Việc phân tích cú pháp chuỗi trong thời gian chạy của dữ liệu do người dùng cung cấp có thể dễ dàng dẫn đến các lỗ hổng bảo mật XSS.

Kết luận

Bạn có nhớ thời điểm jQuery làm việc với DOM đơn giản trở nên đơn giản không? Kết quả là querySelector()/querySelectorAll() đã được thêm vào nền tảng. Rõ ràng là chiến thắng phải không? Một thư viện phổ biến phương thức tìm nạp DOM bằng các bộ chọn và tiêu chuẩn CSS sau này đã sử dụng nó. Cách này không phải lúc nào cũng hoạt động theo cách đó, nhưng tôi rất thích cách này.

Tôi nghĩ <template> cũng tương tự như vậy. Nó chuẩn hoá cách chúng ta tạo mẫu phía máy khách, nhưng quan trọng hơn, nó giúp loại bỏ nhu cầu cho các bản tấn công năm 2008. Trong cuốn sách của tôi, việc làm cho toàn bộ quá trình sáng tác nội dung web trở nên tinh tế hơn, dễ bảo trì hơn và đầy đủ tính năng hơn.

Tài nguyên bổ sung