Ujednolicenie szablonów po stronie klienta
Wprowadzenie
Szablony nie są niczym nowym w procesie tworzenia stron internetowych. Języki/wyszukiwarki szablonów po stronie serwera, takie jak Django (Python), ERB/Haml (Ruby) i Smarty (PHP), są dostępne od dawna. W ciągu ostatnich kilku lat obserwujemy jednak wybuch popularności frameworków MVC. Wszystkie są nieco inne, ale większość z nich korzysta z tej samej metody renderowania warstwy prezentacji (czyli widoku DA): szablonów.
Spójrzmy prawdzie w oczy. Szablony są fantastyczne. Zapytaj. Nawet definicja brzmi przytulnie:
„…nie musi być tworzony za każdym razem…” Nie wiem, jak Ty, ale ja uwielbiam unikać dodatkowej pracy. Dlaczego więc platforma internetowa nie obsługuje natywną czegoś, co jest ważne dla deweloperów?
Odpowiedzią jest specyfikacja szablonów HTML WhatWG. Określa on nowy element <template>
, który opisuje standardowe podejście oparte na DOM do tworzenia szablonów po stronie klienta. Szablony umożliwiają deklarowanie fragmentów znaczników, które są parsowane jako kod HTML, nie są używane podczas wczytywania strony, ale mogą być tworzone później w czasie wykonywania. Cytując Rafaela Weinsteina:
Umożliwiają umieszczenie dużej ilości kodu HTML, której przeglądarka wcale nie powinna bałaganić – pod żadnym pozorem.
Rafael Weinstein (autor specyfikacji)
Wykrywanie cech
Aby wykryć funkcję <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 kodzie znaczników. Zawiera ona „treści szablonu”, czyli nieaktywne fragmenty kodu DOM, które można sklonować.
Szablony możesz traktować jak elementy rusztowania, których możesz używać (i wykorzystywać ponownie) przez cały czas istnienia aplikacji.
Aby utworzyć treść utworzoną na podstawie szablonu, zadeklaruj znaczniki i umieść je w elemencie <template>
:
<template id="mytemplate">
<img src="" alt="great image">
<div class="comment"></div>
</template>
Słupy
Opakowanie treści w <template>
zapewnia nam kilka ważnych właściwości.
Treści są nieaktywne do momentu aktywacji. Krótko mówiąc, znaczniki są ukryte w modelu DOM i nie są renderowane.
Treści w szablonie nie będą miały żadnych skutków ubocznych. Skrypt się nie uruchamia, obrazy się nie wczytują, dźwięk się nie odtwarza, dopóki nie użyjesz szablonu.
Treści nie są uznawane za zawarte w dokumencie. Użycie parametrów
document.getElementById()
lubquerySelector()
na stronie głównej nie spowoduje zwrócenia węzłów podrzędnych szablonu.Szablony można umieszczać w dowolnym miejscu w elementach
<head>
,<body>
lub<frameset>
i mogą one zawierać dowolny typ treści dozwolony w tych elementach. Pamiętaj, że „w dowolnym miejscu” oznacza, że<template>
może być bezpiecznie używany w miejscach, w których nie zezwala na to parsowanie HTML, z wyjątkiem elementów potomnych modelu treści. Może on być też elementem podrzędnym elementu<table>
lub<select>
:
<table>
<tr>
<template id="cells-to-repeat">
<td>some content</td>
</template>
</tr>
</table>
Aktywowanie szablonu
Aby używać szablonu, musisz go aktywować. W przeciwnym razie jej treść nigdy nie będzie wyświetlana.
Najłatwiej to zrobić, tworząc głęboką kopię pliku .content
za pomocą document.importNode()
. Właściwość .content
to DocumentFragment
tylko do odczytu, która zawiera główną część szablonu.
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 wytłoczeniu szablonu jego zawartość staje się aktywna. W tym przykładzie treści są klonowane, wysyłane jest żądanie obrazów, a następnie renderowany jest końcowy znacznik.
Prezentacje
Przykład: skrypt do wstawienia
Ten przykład pokazuje bezwładność treści szablonu. Funkcja <script>
jest wykonywana tylko wtedy, gdy naciśniesz przycisk, co spowoduje utworzenie szablonu.
<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 modelu Shadow DOM na podstawie szablonu
Większość użytkowników dołącza Shadow DOM do hosta, ustawiając ciąg znaczników na .innerHTML
:
<div id="host"></div>
<script>
var shadow = document.querySelector('#host').createShadowRoot();
shadow.innerHTML = '<span>Host node</span>';
</script>
Problem z tym podejściem polega na tym, że im bardziej złożony jest model DOM cienia, tym więcej łańcuchów znaków trzeba ze sobą złączać. Nie da się tego skalować, szybko robi się bałagan, a dzieci zaczynają płakać. Tak właśnie powstał XSS. <template>
na ratunek.
Lepszym rozwiązaniem jest bezpośrednia praca z DOM-em przez dołączanie treści szablonu do katalogu głównego cienia:
<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 napotkałem podczas korzystania z <template>
w praktyce:
- W przypadku użycia modpagespeed uważaj na ten błąd. Szablony, które definiują wstawiane inline
<style scoped>
, można przenieść do sekcji head za pomocą reguł przekształcania kodu CSS w PageSpeed. - Nie ma możliwości „przedrenderowania” szablonu, co oznacza, że nie można wstępnie wczytywać zasobów, przetwarzać JS, pobierać początkowego CSS itp. Dotyczy to zarówno serwera, jak i klienta. Szablon jest renderowany tylko wtedy, gdy jest publikowany.
Zachowaj ostrożność w przypadku szablonów zagnieżdżonych. Nie działają one zgodnie z oczekiwaniami. Na przykład:
<template> <ul> <template> <li>Stuff</li> </template> </ul> </template>
Aktywowanie szablonu zewnętrznego nie powoduje aktywacji szablonów wewnętrznych. Oznacza to, że zagłębione szablony wymagają ręcznej aktywacji ich elementów podrzędnych.
Droga do standardu
Nie zapominajmy, skąd pochodzimy. Droga do opartych na standardach szablonów HTML została długa. Z upływem czasu udało nam się opracować kilka sprytnych sztuczek, które ułatwiają tworzenie szablonów wielokrotnego użytku. Poniżej znajdziesz 2 najczęstsze z nich, które napotkałem. Umieszczam je w tym artykule, aby je porównać.
Metoda 1. Poza ekranem DOM
Jedną ze metod stosowanych od dawna jest utworzenie DOM „poza ekranem” i ukrycie go za pomocą atrybutu hidden
lub display:none
.
<div id="mytemplate" hidden>
<img src="logo.png">
<div class="comment"></div>
</div>
Ta metoda działa, ale ma też pewne wady. Omówienie tej techniki:
- Korzystanie z modelu DOM – przeglądarka zna model DOM. I dobrze sobie z tym radzi. Możemy go łatwo sklonować.
- Nic nie jest renderowane – dodanie
hidden
uniemożliwia wyświetlanie bloku. - Nieinertne – nawet jeśli treści są ukryte, sieć nadal wysyła żądanie dotyczące obrazu.
- Błędy związane ze stylami i motywami – strona do umieszczania musi dodać do wszystkich swoich reguł CSS prefiks
#mytemplate
, aby ograniczyć zakres stylów do szablonu. Jest to rozwiązanie niestabilne i nie ma gwarancji, że w przyszłości nie wystąpią kolizje nazw. Na przykład, jeśli strona z osadzeniem zawiera już element o tym identyfikatorze.
Metoda 2. Skrypt przeciążenia
Inną techniką jest przeciążenie <script>
i zmodyfikowanie jego zawartości jako ciągu znaków. Prawdopodobnie pierwszy, który pokazał to w 2008 r., był John Resig, który opisał to w swoim narzędzie do tworzenia szablonów Micro Templating.
Obecnie istnieje wiele innych, w tym nowe, takie jak handlebars.js.
Na przykład:
<script id="mytemplate" type="text/x-handlebars-template">
<img src="logo.png">
<div class="comment"></div>
</script>
Podsumowanie tej metody:
- Nic nie jest renderowane – przeglądarka nie renderuje tego bloku, ponieważ
<script>
jest domyślnie ustawione nadisplay:none
. - Bezczynność – przeglądarka nie interpretuje treści skryptu jako JS, ponieważ jego typ jest ustawiony na wartość inną niż „text/JavaScript”.
- Problemy z bezpieczeństwem – zachęcanie do używania
.innerHTML
. Analizowanie ciągu znaków na etapie wykonywania danych dostarczonych przez użytkownika może łatwo prowadzić do podatności na ataki XSS.
Podsumowanie
Pamiętasz, jak jQuery ułatwiło pracę z DOM? W efekcie do platformy zostało dodane konto querySelector()
/querySelectorAll()
. To oczywiste, prawda? Biblioteka, która spopularyzowała pobieranie DOM za pomocą selektorów CSS i standardów, które później ją zaadoptowały. Nie zawsze tak się dzieje, ale uwielbiam, gdy tak jest.
Myślę, że <template>
to podobny przypadek. Standardyzuje sposób tworzenia szablonów po stronie klienta, ale co ważniejsze, eliminuje konieczność stosowania obejść.
W moim odczuciu zawsze warto ulepszać proces tworzenia stron internetowych, tak aby był bardziej przejrzysty, prostszy w obsłudze i bardziej funkcjonalny.
Dodatkowe materiały
- Specyfikacja WhatWG
- Wprowadzenie do komponentów sieciowych
- <web>components</web> (film) – fantastycznie obszerna prezentacja od Twoich kolegów.