Ujednolicenie szablonów po stronie klienta
Wprowadzenie
Koncepcja szablonów nie jest nowością w programowaniu stron internetowych. Języki i silniki szablonów po stronie serwera, takie jak Django (Python), ERB/Haml (Ruby) czy Smarty (PHP), istnieją od dawna. W ciągu ostatnich kilku lat pojawiło się jednak mnóstwo 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.
Przyjrzyjmy się temu. Szablony są fantastyczne. Zapytaj. Nawet jego definicja wywołuje przyjemne skojarzenia:
„…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. Cytat z artykułu Rafaela Weinsteina:
To miejsce na dużą ilość kodu HTML, której nie chcesz, aby przeglądarka w żaden sposób nie modyfikowała.
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 zawartoś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ści w szablonie, zadeklaruj znaczniki i opakuj je w element <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.
Do momentu aktywacji zawartość jest nieaktywna. Oznacza to, że znaczniki są ukryte w DOM-ie 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 tagó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. „Wszędzie” oznacza, że<template>
może być bezpiecznie używany w miejscach, w których parsowanie HTML jest zabronione, z wyjątkiem elementów modelu treści. Może też być 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 jego zawartość nigdy nie zostanie wyświetlona.
Najprostszym sposobem jest utworzenie głębokiej kopii .content
za pomocą funkcji document.importNode()
. Właściwość .content
to tylko do odczytu DocumentFragment
zawierająca główne elementy 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 dotyczące obrazu, a następnie renderowany jest końcowy znacznik.
Prezentacje
Przykład: skrypt do wstawienia
Ten przykład pokazuje, jak działa bezczynność treści w szablonie. Funkcja <script>
jest wykonywana tylko wtedy, gdy naciśniesz przycisk, co spowoduje wydrukowanie 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 sceny domyślnej cienia 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 Shadow DOM, tym więcej łańcuchów musisz złączać. Nie da się tego skalować, szybko robi się bałagan, a dzieci zaczynają płakać. To podejście było też podstawą powstania 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:
- Jeśli używasz rozszerzenia modpagespeed, zachowaj ostrożność w związku z tym błędem. Szablony, które definiują wstawiane inline
<style scoped>
, można przenieść do sekcji head za pomocą reguł przekształcania CSS w PageSpeed. - Nie ma możliwości „wstępnego renderowania” 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.
Uważaj na zagnieżdżone szablony. Nie działają one tak, jak powinny. Na przykład:
<template> <ul> <template> <li>Stuff</li> </template> </ul> </template>
Aktywowanie szablonu zewnętrznego nie spowoduje 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 standardowych szablonów HTML była długa. Z laty opracowaliśmy 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. W tym artykule podaję je do porównania.
Metoda 1. DOM poza ekranem
Jednym ze sposobów, z których korzystano od dawna, jest tworzenie „pozaekranowego” DOM-u i ukrywanie 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:
- Używanie DOM – przeglądarka zna 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 dojdzie do kolizji nazw. Na przykład, jeśli strona z osadzeniem zawiera już element o tym identyfikatorze.
Metoda 2. Przeciążenie skryptu
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 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>
Omówienie tej techniki:
- Nic nie jest renderowane – przeglądarka nie renderuje tego bloku, ponieważ domyślnie
<script>
jestdisplay:none
. - Inert – przeglądarka nie analizuje treści skryptu jako JS, ponieważ jego typ jest ustawiony na inny niż „text/javascript”.
- Problemy z bezpieczeństwem – zachęcanie do używania
.innerHTML
. Analizowanie ciągu znaków pod względem danych przesłanych przez użytkownika w czasie wykonywania 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 oczywista wygrana, 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 się dzieje.
Myślę, że <template>
to podobny przypadek. Standardyzuje sposób tworzenia szablonów po stronie klienta, ale co ważniejsze, eliminuje konieczność stosowania obejść.
Uważam, że usprawnienie całego procesu tworzenia stron internetowych, ułatwienie jego obsługi i zapewnienie mu pełnej funkcjonalności to zawsze dobra rzecz.
Dodatkowe materiały
- Specyfikacja WhatWG
- Wprowadzenie do komponentów internetowych
- <web>components</web> (film) – fantastycznie obszerna prezentacja od Twoich kolegów.