HTML's Yeni Şablon Etiketi

İstemci tarafı şablonları standart hale getirme

Giriş

Şablon oluşturma kavramı web geliştirme konusunda yeni bir kavram değildir. Hatta Django (Python), ERB/Haml (Ruby) ve Smarty (PHP) gibi sunucu tarafı şablon oluşturma dilleri/motorları uzun süredir kullanımda. Ancak son birkaç yılda MVC çerçevelerinde çok büyük bir artış yaşandı. Hepsi biraz farklı olsa da çoğu sunum katmanlarını (da view) oluşturmak için kullanılan ortak bir mekanizmaya sahiptir.

Kabul edelim. Şablonlar harika. Devam et, etrafa sor. Tanımı bile size sıcak ve rahat bir his verir:

"...her seferinde yeniden oluşturulması gerekmez..." Sizi bilmiyorum, ama fazladan iş yapmaktan kaçınmayı seviyorum. Öyleyse web platformu, geliştiricilerin açıkça önemsediği bir şey için yerel destek sunmuyor?

Bunun yanıtı, WhatWG HTML Şablonları spesifikasyonu'dur. İ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üklenirken kullanılmayan, ancak daha sonra çalışma zamanında örneklenebilen işaretleme parçalarını bildirmenize olanak tanır. Rafael Weinstein'dan alıntı yapacak olursak:

Herhangi bir nedenle tarayıcının karıştırmasını istemediğiniz büyük bir HTML dosyasını yerleştirebileceğiniz yerlerdir.

Rafael Weinstein (spesifikasyon yazarı)

Özellik Algılama

<template> özelliğini algılamak için DOM öğesini oluşturun ve .content özelliğinin mevcut olduğundan emin olun:

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"ni içerir. Bu aslında klonlanabilir DOM'un hareketsiz parçalarıdır. Şablonları, uygulamanızın kullanım ömrü boyunca kullanabileceğiniz (ve yeniden kullanabileceğiniz) yapı yapıları olarak düşünün.

Şablonlu içerik oluşturmak için bazı işaretlemeler bildirin ve <template> öğesine sarmalayın:

<template id="mytemplate">
    <img src="" alt="great image">
    <div class="comment"></div>
</template>

Sütunlar

İçeriği bir <template> içinde sarmalamak bize birkaç önemli özellik sağlar.

  1. İçeriğin, etkinleştirilinceye kadar etkin bir şekilde etkisizdir. Esas olarak, işaretlemeniz gizli DOM'dir ve oluşturulmaz.

  2. Şablon içindeki hiçbir içeriğin yan etkisi yoktur. Şablon kullanılana kadar Komut dosyası çalışmaz, resimler yüklenmez, ses çalınmaz.

  3. İçerik, belgede yer almadığı kabul edilir. Ana sayfada document.getElementById() veya querySelector() kullanılması, şablonun alt düğümlerini döndürmez.

  4. Şablonlar <head>, <body> veya <frameset> içinde herhangi bir yere yerleştirilebilir ve bu öğelerde izin verilen her tür içeriği içerebilir. "Herhangi bir yer", <template> öğesinin HTML ayrıştırıcısının izin vermediği yerlerde (içerik modeli alt öğeleri hariç tümü hariç) güvenli bir şekilde kullanılabileceği anlamına gelir. Ayrıca, <table> veya <select> öğelerinin alt öğesi olarak da yerleştirilebilir:

<table>
  <tr>
    <template id="cells-to-repeat">
      <td>some content</td>
    </template>
  </tr>
</table>

Şablonu 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 bağırsaklarını içeren salt okunur bir DocumentFragment özelliğidir.

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 şablon damgalandıktan sonra içeriği "yayınlanır". Bu özel örnekte içerik klonlanır, resim isteği yapılır ve son işaretleme oluşturulur.

Demolar

Örnek: Inert komut dosyası

Bu örnekte, şablon içeriğinin hareketsizliği gösterilmektedir. <script> yalnızca düğmeye basıldığında çalışır ve şablonun damgalanmasını sağlar.

<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, .innerHTML değerine bir işaretleme dizesi ayarlayarak Gölge DOM'yi ana makineye ekler:

<div id="host"></div>
<script>
  var shadow = document.querySelector('#host').createShadowRoot();
  shadow.innerHTML = '<span>Host node</span>';
</script>

Bu yaklaşımdaki sorun, Gölge DOM'unuz ne kadar karmaşık hale gelirse dize birleştirme işlemi de o kadar fazla olur. Ölçeklenmiyor, işler hızla harika hale geliyor ve bebekler ağlamaya başlıyor. Aynı zamanda XSS'in nasıl ortaya çıktığı da bu yaklaşımla doğrusu. <template> yardım alın.

Daha akıllıca bir yaklaşım, şablon içeriğini bir gölge köke ekleyerek doğrudan DOM ile çalışmaktır:

<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>

Anladım

<template>'ı vahşi ortamda kullanırken karşılaştığım birkaç hatayı burada bulabilirsiniz:

  • modpagespeed kullanıyorsanız bu hata konusunda dikkatli olun. Satır içi <style scoped> özelliğini tanımlayan şablonların çoğu PageSpeed'in CSS yeniden yazma kurallarıyla başa taşınır.
  • Bir şablonu "önceden oluşturma" mümkün değildir. Yani öğeleri önceden yükleyemez, JS'yi işleyemez, ilk CSS'yi indiremezsiniz. Bu, hem sunucu hem de istemci için geçerlidir. Bir şablon yalnızca yayınlandığı zaman oluşturulur.
  • İç içe yerleştirilmiş şablonlar konusunda dikkatli olun. Beklediğiniz gibi davranmazlar. Örneğin:

    <template>
      <ul>
        <template>
          <li>Stuff</li>
        </template>
      </ul>
    </template>
    

    Dış şablonun etkinleştirilmesi iç şablonların etkinleştirilmesini sağlamaz. Diğer bir deyişle, iç içe yerleştirilmiş şablonlar, alt öğelerinin de manuel olarak etkinleştirilmesini gerektirir.

Standarda giden yol

Nereden geldiğimizi unutmayalım. Standartlara dayalı HTML şablonlarına giden yol uzun bir süredir. Yıllar içinde, yeniden kullanılabilir şablonlar oluşturmak için çok akıllıca bazı püf noktaları bulduk. Aşağıda, en sık karşılaştığım iki durum belirtilmiştir. Karşılaştırma amacıyla bunları bu makaleye ekledik.

1. Yöntem: Ekran dışı DOM

İnsanların uzun zamandır kullandığı yaklaşımlardan biri, "ekran dışı" DOM oluşturmak ve hidden özelliğini veya display:none özelliğini kullanarak bunu görünümden gizlemektir.

<div id="mytemplate" hidden>
  <img src="logo.png">
  <div class="comment"></div>
</div>

Bu teknik işe yarıyor olsa da bazı dezavantajları vardır. Bu tekniğin özeti:

  • DOM kullanılıyor: Tarayıcı DOM'yi bilir. Bu işte iyidir. Bu dosyayı kolayca klonlayabiliriz.
  • Hiçbir şey oluşturulmaz - hidden eklenmesi, blokun gösterilmesini engeller.
  • Hareketsiz değil: İçeriğimiz gizli olsa bile resim için bir ağ isteği yine de yapılır.
  • Zorlu stil ve tema: Bir yerleştirme sayfası, stilleri şablona göre kapsamak için tüm CSS kurallarının önüne #mytemplate yerleştirmelidir. Bu çok kırılgandır ve ileride adlandırma sorunlarıyla karşılaşmayacağımızın garantisi yoktur. Örneğin, yerleştirme sayfasında bu kimliğe sahip bir öğe zaten varsa hassasız.

2. Yöntem: Komut dosyasını aşırı yükleme

Diğer bir teknik de <script> öğesini aşırı yüklemek ve içeriğini dize olarak değiştirmektir. John Resig, muhtemelen bunu 2008 yılında micro Templating yardımcı programı ile ilk gösteren kişidir. Şimdi ise blokta handlebars.js gibi yeni çocuklar da dahil olmak üzere pek çok yeni araç 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 olarak display:none olduğundan tarayıcı bu bloku oluşturmaz.
  • Asla: Tarayıcı, türü "text/javascript" dışında bir şeye ayarlandığından komut dosyası içeriğini JS olarak ayrıştırmaz.
  • Güvenlik sorunları: .innerHTML kullanımını teşvik eder. Kullanıcı tarafından sağlanan verilerin çalışma zamanı dizesini ayrıştırması, XSS güvenlik açıklarına kolayca yol açabilir.

Sonuç

jQuery'nin, DOM Dead ile çalışmayı basitleştirdiği zamanı hatırlıyor musunuz? Sonuç olarak querySelector()/querySelectorAll() platforma eklendi. Bariz bir kazanç, değil mi? Bir kitaplık, DOM getirme işlemini CSS seçicilerle ve standartlarla yaygınlaştırdı. Daha sonra bu yöntem benimsendi. Bu yaklaşım her zaman böyle olmayabilir ama böyle olmasını seviyorum.

<template> de benzer bir durum. İstemci taraflı şablon oluşturma yöntemimizi standartlaştırıyor fakat daha da önemlisi, 2008'de yaptığımız saldırıların ihtiyacını ortadan kaldırıyor. Web yazarlık sürecinin tamamının daha akıllı, sürdürülebilir ve tüm özelliklere sahip olması, kitabımda her zaman faydalı bir özellik olacaktır.

Ek kaynaklar