Chuẩn hoá mẫu phía máy khách
Giới thiệu
Khái niệm về tạo mẫu không phải là mới đối với việ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 gần đây, chúng ta đã chứng kiến sự bùng nổ của các khung MVC. Tất cả đều có một chút khác biệt, nhưng hầu hết đều có chung một cơ chế để hiển thị lớp trình bày (hay còn gọi là thành phần hiển thị): mẫu.
Hãy đối mặt với thực tế. Mẫu là một công cụ tuyệt vời. Cứ hỏi mọi người đi. Ngay cả định nghĩa của từ này cũng mang lại cảm giác ấm áp và dễ chịu:
"…không cần phải tạo lại mỗi lần…" Không biết bạn thế nào, nhưng tôi rất thích tránh được việc phải làm thêm. Vậy tại sao nền tảng web lại thiếu hỗ trợ gốc cho nội dung mà nhà phát triển rõ ràng quan tâm?
Quy cách mẫu HTML WhatWG chính là câu trả lời. Nó xác định một phần tử <template> mới mô tả phương pháp tiếp cận dựa trên DOM tiêu chuẩn cho việc tạo mẫu phía máy khách. Mẫu cho phép bạn khai báo các đoạn mã đánh dấu được phân tích cú pháp dưới dạng HTML, không được sử dụng khi tải trang nhưng có thể được tạo thực thể sau này trong thời gian chạy. Để trích dẫn Rafael Weinstein:
Đây là nơi để đặt một lượng lớn HTML mà bạn không muốn trình duyệt can thiệp… vì bất kỳ lý do gì.
Rafael Weinstein (tác giả thông số kỹ thuật)
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 để đảm bảo thuộc tính .content tồn tại:
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ẻ này chứa "nội dung mẫu"; về cơ bản là các đoạn DOM có thể sao chép nhưng không hoạt động.
Hãy coi mẫu như những phần khung mà bạn có thể sử dụng (và sử dụng lại) trong suốt vòng đời 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ã đó trong phần tử <template>:
<template id="mytemplate">
<img src="" alt="great image">
<div class="comment"></div>
</template>
Các trụ cột
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.
Nội dung của phần tử này sẽ không hoạt động cho đến khi được kích hoạt. Về cơ bản, mã đánh dấu của bạn là DOM ẩn và không hiển thị.
Mọi nội dung trong mẫu sẽ không có tác dụ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.
Nội dung được coi là không có trong tài liệu. Việc sử dụng
document.getElementById()hoặcquerySelector()trong trang chính sẽ không trả về các nút con của một mẫu.Bạn có thể đặt mẫu ở bất cứ đâu trong
<head>,<body>hoặc<frameset>và mẫu có thể chứa bất kỳ loại nội dung nào được cho phép trong các phần tử đó. Lưu ý rằng "bất cứ nơi nào" có nghĩa là<template>có thể được sử dụng một cách an toàn ở những nơi mà trình phân tích cú pháp HTML không cho phép… tất cả ngoại trừ các phần tử con của mô hình nội dung. Bạn cũng có thể đặt thẻ này làm thẻ 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 kích hoạt mẫu đó. Nếu không, nội dung của nó 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 một bản sao sâu của .content bằng document.importNode(). Thuộc tính .content là một DocumentFragment chỉ có thể đọc, chứa nội dung 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 bạn đóng dấu một mẫu, nội dung của mẫu đó sẽ "được phát hành". 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à mã đánh dấu cuối cùng được hiển thị.
Bản minh hoạ
Ví dụ: Tập lệnh không hoạt động
Ví dụ này minh hoạ tính chất không hoạt động của nội dung mẫu. <script> chỉ chạy khi bạn nhấn nút, đóng dấu 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 Shadow DOM từ một mẫu
Hầu hết mọi người đều đính kèm Shadow DOM vào một máy chủ lưu trữ bằng cách đặt một chuỗi mã đá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 đề với phương pháp này là Shadow DOM càng phức tạp,
bạn càng phải nối nhiều chuỗi. Cách này không thể mở rộng, mọi thứ nhanh chóng trở nên lộn xộn và trẻ bắt đầu khóc. Đây cũng là cách XSS ra đời! <template> sẽ giúp bạn.
Một cách hợp lý 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:
<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>
Gotchas
Dưới đây là một vài điểm cần lưu ý mà tôi gặp phải khi sử dụng <template> trong thực tế:
- 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 có thể được chuyển đế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 thành phần, xử lý JS, tải CSS ban đầu xuống, v.v. Điều này áp dụng cho cả máy chủ và máy khách. Mẫu chỉ hiển thị khi đượ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 kích hoạt các mẫu bên trong. Nghĩa là các mẫu lồng nhau yêu cầu bạn cũng phải kích hoạt các mẫu con theo cách thủ công.
Hành trình hướng đến một tiêu chuẩn chung
Đừng quên chúng ta bắt đầu từ đâu. Hành trình đến với các mẫu HTML dựa trên tiêu chuẩn đã kéo dài. Qua nhiều năm, chúng tôi đã nghĩ 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 trường hợp phổ biến mà tôi từng gặp. Tôi sẽ đưa các chỉ số này vào bài viết này để 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 đã sử dụng trong thời gian dài là tạo DOM "ngoài màn hình" và ẩn DOM đó khỏi chế độ xem bằ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 về kỹ thuật này:
- Sử dụng DOM – trình duyệt biết DOM. Mô hình này rất giỏi việc đó. Chúng ta có thể dễ dàng sao chép nó.
- Không có nội dung nào được hiển thị – việc thêm
hiddensẽ ngăn khối hiển thị. - Không trơ – mặc dù nội dung của chúng ta bị ẩn, nhưng yêu cầu mạng vẫn được thực hiện cho hình ảnh.
- Tạo kiểu và giao diện phức tạp – trang nhúng phải thêm tiền tố
#mytemplatevào tất cả quy tắc CSS để thu hẹp phạm vi kiểu xuống mẫu. Cách này không ổn định và không có gì đảm bảo rằng chúng ta sẽ không gặp phải xung đột về tên trong tương lai. Ví dụ: chúng ta sẽ gặp rắc rối nếu trang nhúng đã có một phần tử có mã nhận dạng đó.
Phương thức 2: Tập lệnh nạp chồng
Một kỹ thuật khác là nạp chồng <script> và thao tác với nội dung của nó dưới dạng chuỗi. John Resig có lẽ là người đầu tiên cho thấy điều này vào năm 2008 với
tiện ích Micro Templating của mình.
Giờ đây, có nhiều công cụ khác, bao gồm cả một số công cụ mớ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 về kỹ thuật này:
- Không có nội dung nào được hiển thị – trình duyệt không hiển thị khối này vì
<script>làdisplay:nonetheo 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 của tập lệnh được đặt thành một giá trị khác với "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 lỗ hổng XSS.
Kết luận
Bạn có nhớ khi jQuery giúp việc làm việc với DOM trở nên cực kỳ đơn giản không? Kết quả là querySelector()/querySelectorAll()
đã được thêm vào nền tảng. Rõ ràng là có lợi, đúng không? Một thư viện đã phổ biến việc tìm nạp DOM bằng bộ chọn CSS và các tiêu chuẩn sau này đã áp dụng thư viện này. Không phải lúc nào cũng được như vậy, nhưng tôi rất thích khi điều đó xảy ra.
Tôi nghĩ <template> cũng là một trường hợp tương tự. 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ó loại bỏ nhu cầu về các bản hack năm 2008 của chúng ta.
Theo tôi, việc làm cho toàn bộ quy trình biên soạn web trở nên hợp lý hơn, dễ bảo trì hơn và có nhiều tính năng hơn luôn là một điều tốt.
Tài nguyên khác
- Quy cách WhatWG
- Giới thiệu về thành phần web
- <web>thành phần</web> (video) – một bản trình bày toàn diện tuyệt vời của chính bạn.