HTML Imports (HTML İçe Aktarım) - Kısım 2
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. Buna HTML <template> etiketleri, Shadow DOM ve Custom Element’ler de dahil. Bütün bu teknolojiler bir araya geldiğinde HTML Import artık Web Component’lerimiz için bir #include gibi oluyor.
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 eklenene 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 teknolojisi. 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('merhaba-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; // import edilen doküman
// Shadow DOM ve template kullanan
// <shadow-element> elementini tanımlayıp kaydediyoruz.
var proto2 = Object.create(HTMLElement.prototype);
proto2.createdCallback = function() {
// Importtaki 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 örnek, temel bir custom elementi importun içinde nasıl tanımladığımızı gösteriyor. İkincisi ise <template>‘ten Shadow DOM oluşturan bir custom elementin nasıl tanımlandığını gösteriyor. Bir custom elementi import dokümanında oluşturmanın en iyi yanı, import eden dokü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 başka bir component’i tekrar kullanmak ya da genişletmek istiyorsanız, o elementi yüklemek için bir import kullanabilirsiniz.
Alttaki örnek Polymer Projesi’nden gerçek bir örnek. Bir seçiciyi ve layout’u tekrar kullanan, bağımlılıkları HTML Import ile yönetilen bir tab component’i:
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>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 çıkarsa, <polymer-selector>‘ı atıp yerine yenisini koyabilir ve anında kullanmaya başlayabilirsiniz. Import ve Web Component’ler sağ olsun, kullanıcılarınızın işini bozmak 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 Import’lar ile eklediğimizde, aynı kütüphane birden fazla kez eklenirse tarayıcı bunları otomatik olarak teke indirir. Yani bir doküman sadece bir kere okunur ve script’ler sadece bir kere çalışır. Örnek olarak jQuery kütüphanesini çağırdığımız jquery.html adlı bir 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 dokü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, performans 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 çok sayıda üst seviye import linkine sahipseniz, onları tek bir dosyada birleştirmeyi düşünebilirsiniz.
Vulcanize adlı npm build aracı, iç içe birçok HTML Import’u tek bir dosyada birleştirmemize yardımcı oluyor. Bunu Web Component’ler için bir birleştirme (concatenation) build adımı olarak düşünebilirsiniz.
Importlar ve tarayıcı önbelleği
Tarayıcıların ağ yığını uzun yıllardır önbellekleme konusunda iyileştirilmiş durumda. 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 önbelleğe alınacaktır.
İçerik sadece siz eklediğinizde 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 styles.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 dokümanları için de geçerlidir. Siz onu DOM’a ekleyene (append) kadar o hiçbir şey yapmaz. Import edilen dokümanda anında işlenen tek şey script’lerdir.
Asenkron yükleme için optimize etmek
Importlar render edilmeyi duraklatır
Importlar ana dokümanın render edilmesini engellerler. Bu <link rel="stylesheet"> kodunun yaptığıyla çok benzerdir. Bunun nedeni stil uygulanmamış içeriğin görünmesini en aza 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 sırayla 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 dokümanın parse edilmesini engellemezler. Importların içindeki scriptler bir sırayla işlenir fakat import eden sayfayı engellemezler. Bu sayede script’ler defer’e benzer bir davranış sergiler. Importları head etiketinin içine koymanın faydalarından biri de parser’ın içerik üzerinde hemen çalışmaya başlayabilmesi. Fakat ana dokümanda bulunan <script> etiketinin hala dokü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 indirecektir.
Senaryo 1 (tavsiye edilen): head etiketinizde veya body’de inline 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 içeriğinin 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 importun kendi içeriğini kendisinin eklemesi. Eğer importun yazarı ile uygulamanın yazarı arasında bir anlaşma varsa (ya da ikisi aynı kişiyse), import kendi içeriğini ana dokümana kendisi ekleyebilir.
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>
<!-- Script'e 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’in sadece ilk import edildiğinde ç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 dokü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 dokümana eklenmeye ihtiyaç duyarlar.
<style>etiketinin ana dokü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 pek çok farklı kullanım alanında işimize yaraması onu bu kadar güçlü kılıyor.
Umarım faydalı bir yazı olmuştur. Herkese iyi kodlamalar!