Yazının birinci kısmına buradaki linkten ulaşabilirsiniz.

Web Componentlerinin iletimi

HTML Importlar, tekrar kullanılabilir içerikleri yükleyebilmek için tasarlanmıştır. Özellikle Web Componentlerinin dağıtımı için en ideal yoldur. HTML <template> etiketleri ve Shadow DOM ile birlikte Custom elementler. Bütün bu teknolojiler bir araya geldiğinde HTML Import artık Web Componentlerimiz için #include gibi bir hal alıyor.

Template’leri Include etmek

HTML Template ile HTML Import birbirleriyle bir uyum içindedir. <template> etiketi bizim import edilecek uygulamamızda HTML için gayet güzel bir iskelet görevi görüyor. Ayrıca içeriği <template> içerisine almak, bizim içeriğimizin kullanılana kadar durağan bir şekilde kalmasını sağlıyor. Template’in içinde kalan scriptler ise DOM’a eklenene kadar çalışmıyor.

import.html
<template>
  <h1>Merhaba Dünya!</h1>
  <!-- Resim, <template> DOM'a eklenen kadar çağrılmaz. -->
  <img src="world.png">
  <script>alert("Template aktive edildikten sonra çalışır.");</script>
</template>
index.html
<head>
  <link rel="import" href="import.html">
</head>
<body>
  <div id="container"></div>
  <script>
    var link = document.querySelector('link[rel="import"]');

    // Importtaki <template> i klonluyoruz.
    var template = link.import.querySelector('template');
    var clone = document.importNode(template.content, true);

    document.querySelector('#container').appendChild(clone);
  </script>
</body>

Custom element kaydetme

Custom element de HTML Import ile iyi uyum sağlayan başka bir Web Component teknolojilerinden biri. Daha önce importların script çalıştırabildiklerini görmüştük. O zaman neden aynı zamanda kullanıcının uğraşmaması için custom elementi de importun içinde tanımlayıp, onu DOM’a kaydetmiyoruz? Buna da otomatik kayıt diyebiliriz.

elements.html
<script>
  // <merhaba-de> elementini tanımlıyoruz ve kaydediyoruz.
  var proto = Object.create(HTMLElement.prototype);

  proto.createdCallback = function() {
    this.innerHTML = 'Merhaba, <b>' +
                     (this.getAttribute('name') || '?') + '</b>';
  };

  document.registerElement('merbaba-de', {prototype: proto});
</script>

<template id="t">
  <style>
    ::content > * {
      color: red;
    }
  </style>
  <span>Ben Shadow DOM kullanan gölge bir elementim!</span>
  <content></content>
</template>

<script>
  (function() {
    var importDoc = document.currentScript.ownerDocument; // imort edilen

    //Shadow DOM ve template kullanan
    //<shadow-element> elementini tanımlayıp kaydediyoruz.
    var proto2 = Object.create(HTMLElement.prototype);

    proto2.createdCallback = function() {
      // importta template'i elde ediyoruz.
      var template = importDoc.querySelector('#t');

      // template'i import ediyoruz.
      var clone = document.importNode(template.content, true);

      var root = this.createShadowRoot();
      root.appendChild(clone);
    };

    document.registerElement('shadow-element', {prototype: proto2});
  })();
</script>

Buradaki import iki element tanımlar ve bunları kaydeder. Bu elementler: <merhaba-de>; ve <shadow-element>;. İlk element örneği, bize temel bir custom elementi nasıl importun içinde tanımladığımızı gösteriyor. İkinci ise bize <template>; ‘ten Shadow DOM oluşturan bir custom elementin nasıl oluşturulduğunu gösteriyor. Bir custom elementi import dökümanında oluşturmanın en iyi yanı, import eden dökümanın herhangi bir bağlantıya ihtiyaç duymadan o elementi kolayca kullanabilmesi.

index.html
<head>
  <link rel="import" href="elements.html">
</head>
<body>
  <merhaba-de name="Eric"></merhaba-de>
  <shadow-element>
    <div>( Ben light dom'um )</div>
  </shadow-element>
</body>

Bana soracak olursanız HTML Import, Web Componentlerini paylaşmanın en ideal yolu.

Alt-importta bağımlılıkların kontrolü

Alt-import

Bir importun başka bir importu içermesi yararlı olabilir. Örneğin bir componenti tekrar kullanmak ve onu genişletmek istiyorsunuz, elementi yüklemek için bir import kullanırsınız.

Alttaki örnek Polymer Projesi‘nden gerçek bir örnek. Bağımlılıkları HTML Import ile belirlenmiş, bir seçiciyi ve layoutu tekrar tekrar kullanan bir tab componenti:

polymer-ui-tabs.html
<link rel="import" href="polymer-selector.html">
<link rel="import" href="polymer-flex-layout.html">

<polymer-element name="polymer-ui-tabs" extends="polymer-selector" ...>
  <template>
    <link rel="stylesheet" href="polymer-ui-tabs.css">
    <polymer-flex-layout></polymer-flex-layout>
    <shadow></shadow>
  </template>
</polymer-element>

Tam Kaynak

Uygulama kullanıcıları bu elementi alttaki kodu kullanarak import edebilir:

<link rel="import" href="polymer-ui-tabs.html">
<polymer-ui-tabs></polymer-ui-tabs>

Olur da yeni ve daha iyi bir <polymer-selector2> elementi geldiği zaman, <polymer-selector>‘ı atıp yerine yenisini getirebilirsiniz ve anında kullanmaya başlayabilirsiniz. Import ve web componentler sağolsun kullanıcılarınıza ara verdirtmek zorunda kalmayacaksınız.

Bağımlılık Yönetimi

Hepimiz JQuery’nin bir sayfada birden fazla yüklenmesinin hatalara neden olabileceğini biliyoruz. Birden fazla web componenti bir sayfada olduğunda bizim için çok büyük bir sorun yaratmayacak mı? Eğer HTML Imports kullanıyorsanız hayır! Anlaşıldığı üzere HTML Importlar aynı zamanda bağımlılıkları yönetmek için de kullanılabiliyorlar.

Kütüphaneleri HTML Importlarında eklediğimiz zaman, otomatik olarak birden fazla aynı kütüphane eklenmesi durumunda bunları teke indirir. Yani bir döküman sadece bir kere okunur ve scriptler sadece bir kere çalışır. Örnek olarak jquery kütüphanesini çağırdığımız bir jquery.html adlı importumuz olsun.

jquery.html
<script src="http://cdn.com/jquery.js"></script>

Bu import ondan sonra gelen importlarda defalarca kullanılabilir.

import2.html
<link rel="import" href="jquery.html">
<div>Merhaba, ben ikinci importum!</div>
ajax-element.html
<link rel="import" href="jquery.html">
<link rel="import" href="import2.html">

<script>
  var proto = Object.create(HTMLElement.prototype);

  proto.makeRequest = function(url, done) {
    return $.ajax(url).done(function() {
      done();
    });
  };

  document.registerElement('ajax-element', {prototype: proto});
</script>

Hatta ana dökümanın kendisi bile ihtiyaç duyuyorsa jquery.html’i include edebilir:

<head>
  <link rel="import" href="jquery.html">
  <link rel="import" href="ajax-element.html">
</head>
<body>

...

<script>
  $(document).ready(function() {
    var el = document.createElement('ajax-element');
    el.makeRequest('http://example.com');
  });
</script>
</body>

jquery.html, bir çok importta tanımlanmasına rağmen tarayıcı tarafından sadece bir kere getirilir ve işlenir.

Performans Faktörleri

HTML Importlar kesinlikle mükemmel, fakat her yeni teknolojide olduğu gibi bunu da akıllıca kullanmamız gerekiyor. Web geliştirmede en çok kullanılan teknikler bir kenarda dursun, performan açısından alttakilerin de aklınızın bir köşesinde durmasında yarar var:

Art arda importlar

Ağ isteklerini en aza indirmek her zaman önemlidir. Eğer bir çok yüksek seviye import linklerine sahipseniz, onları tek bir dosyada birleştirmeyi düşünebilirsiniz.

Vulcanize npm build aracı bize iç içe bir sürü HTML Importlarını tek bir import dosyasında birleştirmemizde yardımcı oluyor. Bunu, Web Componentleri için birleştirme buildi aşaması olarak düşünebilirsiniz.

Importlar ve tarayıcı cachelemesi

Tarayıcıların ağ yığını cachleme için uzun süredir bulunuyor. Importlar (ve sub-importlar) da bu yapıdan yararlanıyorlar. Örneğin http://cdn.com/bootstrap.html dosyasının alt kaynakları olabilir fakat merak etmeyin, hepsi cachelenecektir.

İçerikler sadece siz eklediğiniz sürece bir işe yarar

Siz onu çağırana kadar etkisiz bir içerik düşünün. Örneğin normal, dinamik olarak yaratılmış bir stil dosyası alalım:

var link = document.createElement('link');
link.rel = 'stylesheet';
link.href = 'styles.css';

Tarayıcı link DOM’a eklenene kadar style.css için bir istekte bulunmayacak.

document.head.appendChild(link); // tarayıcı styles.css isteğinde bulunur.

Dinamik olarak oluşturulmuş diğer bir markup örneği:

var h2 = document.createElement('h2');
h2.textContent = 'Booyah!';

Buradaki h2, siz onu DOM’a ekleyene kadar hiçbir anlam ifade etmez.

Aynı olay import dökümanları için de geçerlidir. Siz onu DOM’a ekleyene(append) kadar o hiçbir şey yapmaz. Sadece, import edilen dökümanda anında işlenen şeyler scriptlerdir.

Asenkron yükleme için optimize etmek
Importlar render edilmeyi duraklatır

Importlar ana dökümanın render edilmesini engellerler. Bu <link rel="stylesheet"> kodunun yaptığıyla çok benzerdir. Bunun nedeni stilize edilemmiş içeriğin gözükmesini minimuma indirmektir. Importlar da, buna çok benzer davranırlar. Bunun nedeni onların da içerisinde stil dosyaları bulunabilmesidir. Importun tamamen asenkron olarak yüklenmesini sağlamak ve render edilme olayının engellenmesini kaldırmak için async attribute’ünü kullanabilirsiniz:

<link rel="import" href="/yuklenmesi/5_saniye/alan_dosya.html" async>

async attribute’ünün varsayılan olmamasının sebebi beraberinde geliştiricilere daha fazla iş yükü getirmesidir. Varsayılan olarak senkron olmasının anlamı, içinde custom element tanımı içeren dosyaların, bir düzen halinde yüklenmesinin ve upgrade edilmesinin garanti altına alınmış olmasıdır. Asenkron tarafta ise, geliştiriciler zamanlamaları kendileri ayarlamak zorundalar.

Importlar Parse edilmeyi duraklatmaz

Importlar ana dökümanın parse edilmesini engellemezler. Importların içindeki scriptler bir sırayla işlenir fakat import eden sayfayı engellemezler. Bunun nedeni script sürerken defer’e benzer bir davranış sergilemesi. Importlarınız head etiketinin içine koymamızın faydalarından biri de parser’ın hemen işini yapmaya başlaması. Fakat ana dökümanda bulunan <script> etiketinin hala dökümanı engelleyeceğini unutmamakta yarar var. Importtan sonraki ilk <script> sayfanın render edilmesini engelleyecektir. Bunun nedeni importun içinde bulunan herhangi bir script dosyasının, importtan sonraki <script> etiketini etkileyebileceğidir:

<head>
  <link rel="import" href="/yuklenmesi/5_saniye/alan_dosya.html" async>
  <script>console.log('Sayfanın render edilmesini engelleyeceğim!');</script>
</head>

Uygulamanızdaki yapıya ve olaya bağlı olarak asenkron davranışı optimize edebileceğimiz birkaç yol bulunuyor.

Alttaki teknikler sayfanın render edilmesinin engellenmesini en aza düşürecektir.

Senaryo 1:(tavsiye edilen) head etiketinizde veya body’de inline olarak script bulunmaması

Benim tavsiyem scripti tüm HTML içeriği bittikten sonra, body kapanışından önce yazmanız olacaktır. Ama siz zaten bunları best practice gereği öyle yapıyordunuz değil mi? (:

Örnek:

<head>
  <link rel="import" href="/path/to/import.html">
  <link rel="import" href="/path/to/import2.html">
  <!-- script eklemekten kaçının -->
</head>
<body>
  <!-- script eklemekten kaçının -->

  <div id="container"></div>

  <!-- script eklemekten kaçının -->
  ...

  <script>
    // Diğer script satırları vs.

    // Import contentinin getirilmesi.
    var link = document.querySelector('link[rel="import"]');
    var post = link.import.querySelector('#blog-post');

    var container = document.querySelector('#container');
    container.appendChild(post.cloneNode(true));
  </script>
</body>

Görüldüğü gibi her şey sonda.

Senaryo 2: Kendi kendini ekleyen importlar

Diğer bir seçenek de importları kendi içerisinde import etmekti. Eğer import’un yazarı uygulamanı yazarı ile irtibata geçerse(ya da aynı kişi yazması gibi bir durumda) importlar kendi içerisinde eklenebilirler.

import.html:

<div id="blog-post">...</div>
<script>
  var me = document.currentScript.ownerDocument;
  var post = me.querySelector('#blog-post');

  var container = document.querySelector('#container');
  container.appendChild(post.cloneNode(true));
</script>

index.html:

<head>
  <link rel="import" href="/import/dosya/yolu.html">
</head>
<body>
  <!-- scripte gerek yok importun kendisi zaten eklemeleri yaptı. -->
</body>

Hatırlanması gereken şeyler

  • Importun type’ı text/html‘dir
  • Diğer kaynaklardan import edebilmemiz için kaynağın CORS-enabled olması gerekir.
  • Aynı URL’den import edilenler bir kere parse edilir. Bunun anlamı, importun içindeki script sadece ilk defada yani import edilmede çalışmasıdır. Importun tekrar eklenmesi durumunda script çalışmaz.
  • Import linki ‘Gelen içeriği buraya ekle’ anlamına gelmez. Anlamı, ‘Parser, git bana ihtiyacım olan dökümanı getir ki ben bunu ihtiyacım olduğunda kullanabileyim’dir. Scriptler import edildiği anda çalışırken stylesheetler, markuplar ve diğer kaynaklar ana dökümana eklenmeye ihtiyaç duyarlar. <style> etiketinin ana dökümana eklenmesine ihtiyaç yoktur. Bu ‘Yükle ve içeriği buraya render et’ anlamına gelen iframe ile arasındaki en büyük farktır.

Sonuç

HTML Import bize HTML/CSS/JS dosyalarını bir yerde toplamak için güzel bir olanak sunuyor. Bu özellik bizim için çok yararlı olmasıyla birlikte, Web Components dünyasında çok güçlü bir özellik olarak karşımıza çıkıyor. Geliştiriciler tüketiciler için yeniden kullanılabilir componentler oluşturabilir ve tüketiciler de bunları kendi uygulamalarında kullanabilirler.

HTML Import özünde basit bir özellik fakat çok fazla çeşitte kullanım alanında bize yardımı dokunması onu bu kadar güçlü kılıyor.

Umarım faydalı yazı olmuştur. Herkese iyi kodlamalar!