İstemci tarafı şablon oluşturmayı standartlaştırma
Giriş
Şablon oluşturma kavramı, web geliştirme için yeni bir kavram değildir. Aslında Django (Python), ERB/Haml (Ruby) ve Smarty (PHP) gibi sunucu tarafı şablon oluşturma dilleri/motorları uzun süredir kullanılmaktadır. Ancak son birkaç yılda MVC çerçevelerinin sayısında patlama yaşandı. Hepsi birbirinden biraz farklı olsa da çoğu, sunum katmanını (görünüm) oluşturmak için ortak bir mekanizma kullanır: şablonlar.
Kabul edelim. Şablonlar harika bir araçtır. Etrafınıza sorabilirsiniz. Tanımı bile içinizi ısıtıyor:
"…her seferinde yeniden oluşturulması gerekmez…" Sizi bilmem ama ben fazladan iş yapmaktan kaçınmayı severim. Peki neden web platformunda geliştiricilerin açıkça önemsediği bir şey için yerel destek yok?
WhatWG HTML Şablonları spesifikasyonu bu sorunun yanıtıdır. İstemci tarafı şablon oluşturma için standart DOM tabanlı bir yaklaşımı açıklayan yeni bir <template> öğesi tanımlar. Şablonlar, HTML olarak ayrıştırılan, sayfa yükleme sırasında kullanılmayan ancak daha sonra çalışma zamanında örneklendirilebilen işaretleme parçalarını bildirmenize olanak tanır. Rafael Weinstein'ın sözleriyle:
Tarayıcının herhangi bir nedenle müdahale etmesini istemediğiniz büyük bir HTML yığınını yerleştirebileceğiniz bir yerdir.
Rafael Weinstein (spec author)
Özellik algılama
<template> özelliğini algılamak için DOM öğesini oluşturun ve .content özelliğinin mevcut olup olmadığını kontrol edin:
function supportsTemplate() {
return 'content' in document.createElement('template');
}
if (supportsTemplate()) {
// Good to go!
} else {
// Use old templating techniques or libraries.
}
Şablon içeriğini bildirme
HTML <template> öğesi, işaretlemenizdeki bir şablonu temsil eder. "Şablon içerikleri" barındırır. Bunlar temelde klonlanabilir DOM'un etkin olmayan parçalarıdır.
Şablonları, uygulamanızın kullanım ömrü boyunca kullanabileceğiniz (ve yeniden kullanabileceğiniz) iskele parçaları olarak düşünebilirsiniz.
Şablonlu içerik oluşturmak için bazı işaretlemeler tanımlayın ve bunları <template> öğesine sarmalayın:
<template id="mytemplate">
<img src="" alt="great image">
<div class="comment"></div>
</template>
Temel unsurlar
İçeriği <template> içine sarmak bize birkaç önemli özellik sağlar.
İçeriği, etkinleştirilene kadar etkili bir şekilde devre dışı kalır. Temel olarak, işaretlemeniz gizli DOM'dur ve oluşturulmaz.
Şablonlardaki içeriklerin yan etkisi yoktur. Şablon kullanılana kadar komut dosyası çalışmaz, resimler yüklenmez, ses çalınmaz.
İçeriğin dokümanda yer almadığı kabul edilir. Ana sayfada
document.getElementById()veyaquerySelector()kullanıldığında şablonun alt düğümleri döndürülmez.Şablonlar
<head>,<body>veya<frameset>öğelerinin herhangi bir yerine yerleştirilebilir ve bu öğelerde izin verilen her tür içeriği barındırabilir. "Her yerde" ifadesi,<template>öğesinin HTML ayrıştırıcısının izin vermediği yerlerde güvenle kullanılabileceği anlamına gelir. İçerik modeli alt öğeleri hariç tüm yerlerde kullanılabilir.<table>veya<select>öğesinin alt öğesi olarak da yerleştirilebilir:
<table>
<tr>
<template id="cells-to-repeat">
<td>some content</td>
</template>
</tr>
</table>
Şablon etkinleştirme
Bir şablonu kullanmak için etkinleştirmeniz gerekir. Aksi takdirde içeriği hiçbir zaman oluşturulmaz.
Bunu yapmanın en basit yolu, document.importNode() kullanarak .content öğesinin derin bir kopyasını oluşturmaktır. .content özelliği, şablonun içeriğini barındıran salt okunur bir DocumentFragment dizesidir.
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);
Bir şablonu damgaladıktan sonra içeriği "yayınlanır". Bu örnekte içerik kopyalanır, resim isteği yapılır ve nihai işaretleme oluşturulur.
Demolar
Örnek: Etkin olmayan komut dosyası
Bu örnek, şablon içeriğinin etkisizliğini gösterir. <script> yalnızca düğmeye basıldığında çalışır ve şablonu damgalar.
<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>
Örnek: Şablondan gölge DOM oluşturma
Çoğu kişi, bir işaretleme dizesini .innerHTML olarak ayarlayarak gölge DOM'u bir ana makineye ekler:
<div id="host"></div>
<script>
var shadow = document.querySelector('#host').createShadowRoot();
shadow.innerHTML = '<span>Host node</span>';
</script>
Bu yaklaşımın sorunu, gölge DOM'unuz ne kadar karmaşık olursa o kadar çok dize birleştirme işlemi yapmanızdır. Ölçeklenemez, işler hızla karışır
ve bebekler ağlamaya başlar. XSS de ilk olarak bu yaklaşım sonucunda ortaya çıkmıştır. <template> imdadınıza yetişir.
Daha mantıklı bir yöntem, şablon içeriğini bir gölge köke ekleyerek doğrudan DOM ile çalışmak olabilir:
<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
<template> özelliğini kullanırken karşılaştığım birkaç sorun şunlar:
- modpagespeed kullanıyorsanız bu hataya dikkat edin. Satır içi
<style scoped>tanımlayan şablonlar, PageSpeed'in CSS yeniden yazma kurallarıyla başlığa taşınabilir. - Şablonu "önceden oluşturmanın" bir yolu yoktur. Yani öğeleri önceden yükleyemez, JS'yi işleyemez, ilk CSS'yi indiremezsiniz. Bu durum hem sunucu hem de istemci için geçerlidir. Şablonlar yalnızca yayınlandığında oluşturulur.
İç içe yerleştirilmiş şablonlara dikkat edin. Beklediğiniz gibi çalışmazlar. Örneğin:
<template> <ul> <template> <li>Stuff</li> </template> </ul> </template>Dış şablonun etkinleştirilmesi, iç şablonları etkinleştirmez. Yani iç içe yerleştirilmiş şablonların alt şablonlarının da manuel olarak etkinleştirilmesi gerekir.
Standart oluşturma süreci
Nereden geldiğimizi unutmayalım. Standartlara dayalı HTML şablonlarına giden yol uzun bir yol oldu. Yıllar içinde, yeniden kullanılabilir şablonlar oluşturmak için oldukça akıllıca yöntemler geliştirdik. Aşağıda, karşılaştığım iki yaygın örnek verilmiştir. Karşılaştırma yapabilmeniz için bu makalede onlara da yer veriyorum.
1. yöntem: Ekran dışı DOM
Kullanıcıların uzun süredir kullandığı bir yaklaşım, "ekran dışı" DOM oluşturmak ve hidden özelliğini veya display:none kullanarak bunu görünümden gizlemektir.
<div id="mytemplate" hidden>
<img src="logo.png">
<div class="comment"></div>
</div>
Bu teknik işe yarasa da bazı dezavantajları vardır. Bu tekniğin özeti:
- DOM kullanma: Tarayıcı, DOM'u tanır. Bu konuda başarılıdır. Bunu kolayca klonlayabiliriz.
- Hiçbir şey oluşturulmaz:
hiddeneklenmesi, bloğun gösterilmesini engeller. - Etkin: İçeriğimiz gizli olsa bile resim için ağ isteği gönderilir.
- Zorlu stil ve tema oluşturma: Yerleştirme sayfasının, stilleri şablonla sınırlamak için tüm CSS kurallarının önüne
#mytemplateeklemesi gerekir. Bu yöntem, kırılgan bir yapıya sahiptir ve gelecekte adlandırma çakışmalarıyla karşılaşmayacağımızın garantisi yoktur. Örneğin, yerleştirme sayfasında bu kimliğe sahip bir öğe zaten varsa sorun yaşarız.
2. yöntem: Aşırı yükleme komut dosyası
Diğer bir teknik ise <script> yöntemini aşırı yüklemek ve içeriğini dize olarak değiştirmektir. John Resig, 2008'de Micro Templating yardımcı programıyla bunu ilk gösteren kişi olabilir.
Şimdi ise handlebars.js gibi yeni çıkanlar da dahil olmak üzere birçok başka kütüphane var.
Örneğin:
<script id="mytemplate" type="text/x-handlebars-template">
<img src="logo.png">
<div class="comment"></div>
</script>
Bu tekniğin özeti:
- Hiçbir şey oluşturulmaz:
<script>varsayılan olarakdisplay:noneolduğundan tarayıcı bu bloğu oluşturmaz. - Inert: Tarayıcı, türü "text/javascript" dışında bir değere ayarlandığı için komut dosyası içeriğini JS olarak ayrıştırmaz.
- Güvenlik sorunları:
.innerHTMLkullanımını teşvik eder. Kullanıcı tarafından sağlanan verilerin çalışma zamanında dize ayrıştırması, kolayca XSS güvenlik açıklarına yol açabilir.
Sonuç
jQuery'nin DOM ile çalışmayı ne kadar kolaylaştırdığını hatırlıyor musunuz? Sonuç olarak, platforma querySelector()/querySelectorAll() eklendi. Bu, açık bir kazanç, değil mi? CSS seçicilerle DOM getirmeyi popülerleştiren bir kitaplık. Standartlar daha sonra bunu benimsedi. Her zaman böyle olmuyor ama olduğunda bayılıyorum.
<template> numaralı destek kaydının benzer bir durum olduğunu düşünüyorum. İstemci tarafı şablon oluşturma yöntemimizi standartlaştırır ancak daha da önemlisi, 2008'deki hilelerimize olan ihtiyacı ortadan kaldırır.
Web yazma sürecinin tamamını daha mantıklı, daha sürdürülebilir ve daha tam özellikli hale getirmek her zaman iyi bir şeydir.
Ek kaynaklar
- WhatWG Spesifikasyonu
- Web Bileşenlerine Giriş
- <web>components</web> (video) - bendeniz tarafından hazırlanan muhteşem kapsamlı bir sunum.