Standaryzacja szablonów po stronie klienta
Wstęp
Pojęcie szablonów nie jest niczym nowym w projektowaniu stron internetowych. Języki/silniki utworzone po stronie serwera, takie jak Django (Python), ERB/Haml (Ruby) i Smarty (PHP), są dostępne od dawna. W ostatnich latach byliśmy jednak świadkami gwałtownego rozwoju platform MVC. Wszystkie nieco się różnią, ale większość ma wspólną mechanizm renderowania warstwy prezentacji (nazywanej też widokiem da): szablony.
Spójrzmy prawdzie w twarz. Szablony są fantastyczne. Śmiało, pytaj innych. Nawet jej definicja sprawia, że użytkownik jest ciepły i przytulny:
„...nie trzeba ich za każdym razem tworzyć”. Nie wiem, jak to było, ale uwielbiam unikać dodatkowej pracy. Dlaczego więc platforma internetowa nie obsługuje czymś, co dla programistów jest wyraźnie ważne?
Odpowiedzią na ich potrzebę jest specyfikacja szablonów HTML WhatWG. Definiuje nowy element <template>
, który opisuje standardowe podejście do szablonów po stronie klienta oparte na modelu DOM. Szablony umożliwiają deklarowanie fragmentów znaczników, które są analizowane jako HTML i nie są używane podczas wczytywania strony, ale można je utworzyć później w czasie działania. Cytując Rafaela Weinsteina:
To miejsce, w którym umieszcza się dużą porcję kodu HTML, którą nie chcemy, aby przeglądarka się bawiła – z dowolnego powodu.
Rafael Weinstein (autor specyfikacji)
Wykrywanie cech
Aby wykrywać cechy <template>
, utwórz element DOM i sprawdź, czy istnieje właściwość .content
:
function supportsTemplate() {
return 'content' in document.createElement('template');
}
if (supportsTemplate()) {
// Good to go!
} else {
// Use old templating techniques or libraries.
}
Deklarowanie treści szablonu
Element HTML <template>
reprezentuje szablon w Twoich znacznikach. Zawiera „zawartość szablonu” i zasadniczo obojętne fragmenty sklonowanego DOM.
Wyobraź sobie, że szablony to elementy rusztowania, których możesz używać (i ponownie wykorzystywać) przez cały okres korzystania z aplikacji.
Aby utworzyć treść opartą na szablonie, zadeklaruj znaczniki i umieść je w elemencie <template>
:
<template id="mytemplate">
<img src="" alt="great image">
<div class="comment"></div>
</template>
Filary
Umieszczenie treści w elemencie <template>
daje nam kilka ważnych właściwości.
Treść tagu pozostaje w takim stanie, dopóki nie zostanie aktywowana. Znaczniki są w formie ukrytej DOM i nie są renderowane.
Treści w szablonie nie będą miały skutków ubocznych. Skrypt nie działa, nie ładują się obrazy, dźwięk nie jest odtwarzany – dopóki nie zostanie użyty szablon.
Uznaje się, że treści nie ma w dokumencie. Użycie na stronie głównej właściwości
document.getElementById()
lubquerySelector()
nie zwróci węzłów podrzędnych szablonu.Szablony można umieszczać w dowolnym miejscu w elementach
<head>
,<body>
lub<frameset>
i mogą zawierać dowolny typ treści dozwolony w tych elementach. Pamiętaj, że „wszędzie” oznacza, że<template>
może być bezpiecznie używany tam, gdzie parser HTML zabrania... wszystkich oprócz elementów podrzędnych modelu treści. Można go też umieścić jako element podrzędny elementu<table>
lub<select>
:
<table>
<tr>
<template id="cells-to-repeat">
<td>some content</td>
</template>
</tr>
</table>
Aktywowanie szablonu
Aby użyć szablonu, musisz go aktywować. W przeciwnym razie treść nigdy nie zostanie wyrenderowana.
Najprostszym sposobem, aby to zrobić, jest utworzenie szczegółowej kopii atrybutu .content
za pomocą document.importNode()
. Właściwość .content
to obiekt DocumentFragment
tylko do odczytu zawierający lukę w szablonie.
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);
Po ostemplowaniu szablonu jego treść jest „opublikowana”. W tym przykładzie treść jest klonowana, wysyłane jest żądanie obrazu, a końcowe znaczniki są renderowane.
Przykłady
Przykład: skrypt bezwładny
Ten przykład pokazuje bezwładność treści szablonu. <script>
uruchamia się tylko po naciśnięciu przycisku, stemponując szablon.
<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>
Przykład: tworzenie obiektu Shadow DOM na podstawie szablonu
Większość osób dołącza model Shadow DOM do hosta przez ustawienie ciągu znaczników na .innerHTML
:
<div id="host"></div>
<script>
var shadow = document.querySelector('#host').createShadowRoot();
shadow.innerHTML = '<span>Host node</span>';
</script>
Problem w tym podejściu polega na tym, że im bardziej skomplikowany jest model Shadow DOM, tym większa jest konkatenacja ciągów znaków. Urządzenie się nie skaluje, wszystko robią się bałagan, a dzieci płaczą. Właśnie dlatego narodziła się
właściwa koncepcja XSS. <template>
na ratunek.
Łatwiejszą pracę z modelem DOM można zastosować bezpośrednio, dołączając treść szablonu do cienia głównego poziomu:
<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
Oto kilka problemów, które wystąpiły podczas używania usługi <template>
w terenie:
- Jeśli używasz narzędzia modpagespeed, uważaj na ten błąd. Szablony zdefiniowane w elemencie
<style scoped>
są przenoszone do nagłówka za pomocą reguł przepisywania CSS PageSpeed. - Nie ma sposobu „wstępnego renderowania” szablonu, co oznacza, że nie można wstępnie wczytać zasobów, przetworzyć pliku JS, pobrać początkowego kodu CSS itp. Dotyczy to zarówno serwera, jak i klienta. Szablon renderuje się tylko po jego opublikowaniu.
Uważaj na szablony zagnieżdżone. Działają one niezgodnie z oczekiwaniami. Na przykład:
<template> <ul> <template> <li>Stuff</li> </template> </ul> </template>
Aktywowanie szablonu zewnętrznego nie spowoduje aktywowania szablonów wewnętrznych. Oznacza to, że w przypadku szablonów zagnieżdżonych również dzieci muszą zostać aktywowane ręcznie.
Droga do standardu
Nie zapominajmy, skąd pochodziliśmy. Droga do stworzenia standardowych szablonów HTML trwa bardzo długo. Od lat wymyślamy kilka ciekawych sztuczek, aby tworzyć szablony wielokrotnego użytku. Poniżej podam 2 typowe metody, na które natknąłem się najczęściej. Umieszczam je w tym artykule, aby je porównać.
Metoda 1. DOM poza ekranem
Jednym ze sposobów, z którego korzystają od dawna, jest utworzenie „poza ekranem” DOM i ukrycie go za pomocą atrybutu hidden
lub atrybutu display:none
.
<div id="mytemplate" hidden>
<img src="logo.png">
<div class="comment"></div>
</div>
Ta technika jest skuteczna, ale ma kilka wad. Omówienie tej techniki:
- Używanie DOM – przeglądarka rozpoznaje DOM. Nieźle. Możemy go łatwo sklonować.
- Nic nie jest renderowane – dodanie
hidden
uniemożliwia wyświetlanie bloku. - Nie jest obojętna – mimo że zawartość jest ukryta, nadal wysyłane jest żądanie sieciowe dotyczące obrazu.
- Charakteryzujący styl i motywację – strona umieszczania musi poprzedzić wszystkie swoje reguły CSS znakiem
#mytemplate
, aby można było określić zakres stylów za pomocą szablonu. Jest on wrażliwy i nie ma gwarancji, że w przyszłości nie spotkamy się z kolizją z nazwami. Zwróć uwagę na przykład wtedy, gdy strona, na której umieszczasz element, zawiera już element o tym identyfikatorze.
Metoda 2. Przeciążenie skryptu
Inna metoda to przeciążenie obiektu <script>
i przetwarzanie jego zawartości pod postacią ciągu znaków. John Resig był prawdopodobnie pierwszą osobą, która pokazała to w 2008 roku za pomocą narzędzia micro Templating.
Jest ich teraz wiele, w tym również nowe dzieci, takie jak handlebars.js.
Na przykład:
<script id="mytemplate" type="text/x-handlebars-template">
<img src="logo.png">
<div class="comment"></div>
</script>
Omówienie tej techniki:
- Nic nie jest renderowane – przeglądarka nie renderuje tego bloku, ponieważ
<script>
ma domyślnie wartośćdisplay:none
. - Inert – przeglądarka nie analizuje treści skryptu jako JS, ponieważ jego typ jest ustawiony na inny niż „text/javascript”.
- Problemy dotyczące bezpieczeństwa – zachęca do korzystania z
.innerHTML
. Analiza ciągów znaków w czasie działania danych przekazywanych przez użytkowników może łatwo prowadzić do luk w zabezpieczeniach XSS.
Podsumowanie
Pamiętasz, kiedy w JQuery nie było już problemów z pracą z brakiem kodu DOM? W efekcie dodano querySelector()
/querySelectorAll()
do platformy. To oczywista wygrana, prawda? Biblioteka spopularyzowała pobieranie DOM
za pomocą selektorów CSS i zastosowała ją później. Nie zawsze tak działa, ale uwielbiam, gdy tak działa.
Wydaje mi się, że <template>
to podobny przypadek. Pozwala ustandaryzować sposób tworzenia szablonów po stronie klienta, ale co ważniejsze, eliminuje potrzebę stosowania ataków z 2008 roku.
Sprawienie, by cały proces tworzenia stron internetowych był bardziej świadomy, łatwiejszy w utrzymaniu i większy
w pełni funkcji, zawsze dobrze w mojej książce.
Dodatkowe materiały
- Specyfikacja WhatWG
- Wprowadzenie do komponentów sieciowych
- <web>components</web> (film) – Twoja wszechstronna prezentacja.