Eleventy (11ty) ile Blog Oluşturma

Yeni Nesil JAMStack Statik Site Üretecini Tanıyın

16.07.2020 Perşembe
JAMStack
26 dakika

Twitter’da takip edenler, arada JAMStack paylaşımları yaptığımı bilir; ama 2018’deki GatsbyJS yazımdan beri JAMStack konulu, ne Türkçe, ne de İngilizce, hiçbir makalem olmadı. Bu süre zarfında JAMStack, statik site geliştirenler için popüler olmaya devam etti; hatta giderek daha fazla tercih edilir oldu.

Kısaca bilgi tazaleyelim: JAMStack temelde istemci taraflı JavaScript, sunucu taraflı ihtiyaçlar için bir veya daha fazla API ve önceden oluşturulmuş statik HTML (Markup) anlamına geliyor.

JAMStack = JavaScript + API + Markup = ⚡

Buna göre:

  • Sitenizin statik HTML olarak ve CDN (içerik dağıtım ağı) üzerinden sunulması gerekiyor. Elinizde istekleri işleyecek bir sunucu yok. Dolayısıyla, SSR’dan, yani sunucu taraflı işlemeden farklı olarak, statik içerik anlık değil, önceden oluşturulmalı. Bu iş için kullanılan araçlaraysa statik site üreteci (SSG, static-site generator) deniyor.
  • Dinamik ihtiyaçların tümü istemci tarafında çalışan JavaScript ve HTTP üzerinden erişilen yeniden kullanılabilir API(lar) ile giderilmeli. Framework kullanımı konusunda bir sınırlama yok. Yine API’ın üçüncü parti servis mi, yoksa kendi yazacağınız fonksiyon mu olacağı size kalmış.

💬 JAMStack’in avantajlarına vesaire girmeyeceğim. İleride JAMStack hakkında detaylı bir makale yazarsam, o zaman değinirim.

Bu makalenin konusu olan Eleventy (11ty), bir statik site üreteci ve JAMStack’in markup, yani HTML oluşturma kısmını çözüyor. Daha meşhur statik site üreteçleri de var; ama Eleventy en çok gelecek vaat edenlerden biri. Neden mi?

Eleventy’nin Avantajları

Eleventy rakiplerinin çoğuna kıyasla aşağıdaki avantajları barındırıyor:

  • İnanılmaz sade; aşırı kolay. Yok böyle bir geliştirici deneyimi.
  • Başlamak çok çabuk. Sıfır konfigürasyonla bile birçok şeyi başarabiliyorsunuz.
  • Çok sayıda şablon motoruyla (template engine) çalışabiliyor. Hangisini severseniz artık…
  • İstemci taraflı JavaScript’e ihtiyaç duymuyor. Yani hidrasyon yok.
  • Eklentiler bulmak/yapmak mümkün. Basit JavaScript’le, teoride sınırsız özellik ekleyebilirsiniz.
  • Framework bağımsız; ama bu Framework kullanmaya engel değil. Örneğin, şu an Eleventy için Vue eklentisinin üzerinde çalışılıyor.
  • Yazılımdan anlayanlar tarafından övgü alıyor. Bkz. Eleventy hakkında söylenenler

Gelelim herkesin kütüphane seçerkenki kriterine, yani GitHub ve npm durumuna. Öncelikle, her ne kadar bugüne dek 37 yazılımcının katkısı olmuşsa da, geliştirme ağırlıklı olarak tek kişiye bakıyor. Lakin, benzer durumu herkesin favorisi Hugo için de söz konusu aslında. Zach Leatherman‘ı yıllardır takip eden biri olarak söyleyebilirim ki, Eleventy’nin arkasındaki isim sıradan bir yazılımcı değil. Öte yandan, GitHub yıldızı (şu an 6.3k) ve haftalık npm indirme sayılarında (son hafta 14k) da Eleventy önde gelen rakiplerinin gerisinde. Bununla birlikte, bu sayılar hiç fena değil. Dahası, son iki yılın npm trend grafiği şu şekilde:

Eleventy npm indirme sayılarındaki iki senelik artışı gösteren grafik
Kaynak: npm trends

Bu yükselişin önümüzdeki günlerde artarak devam edeceğini düşünüyorum. O yüzden, Eleventy’yi detaylıca inceleyen bir makale yazmaya karar verdim. Biraz uzun oldu; kusura bakmayın. Başlıyoruz.

Öngereksinimler

Bir Eleventy projesi oluşturmak için ihtiyacınız olan her şey muhtemelen sisteminizde mevcuttur:

Bu makaleyi anlamak için bilmeniz gerekenlerse azdır:

  • Markdown ve biraz YAML (front matter için)
  • JavaScript (temel seviye)
  • Şablon motorlarının nasıl çalıştığı (Örn. Handlebars, Pug, Nunjucks, Liquid)

 Yeni Eleventy Projesi Oluşturma

Eleventy projeleri herhangi bir Node projesinden farklı değildir. Bir package.json‘unuz olur ve npm ile paketler yükleyip, çalıştırılabilir (executable) betikleri (script) çağırırsınız.

Gerekli Paketleri Yükleme

Yeni bir npm projesi oluşturmak için terminalinize girip aşağıdaki kodu çalıştırmanız yeterli:

mkdir blog && cd blog && npm init -y

🔎 Terminale aşina değilseniz, şunları yaptık:

  • Bulunduğumuz dizinde blog isimli bir alt dizin oluşturduk.
  • Yeni oluşturduğumuz dizine gittik.
  • Varsayılan ayarlarla yeni bir Node projesi başlattık.

Terminal kullanmanıza lüzum yok; elle de yapabilirsiniz.

Konsolda şuna benzer bir şey görmeniz lazım:

Wrote to /Users/armanozak/Projects/blog/package.json:

{
  "name": "blog",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "keywords": [],
  "author": "",
  "license": "ISC"
}

Terminalden ayrılmayın. Projemize Eleventy’yi ekleyeceğiz. Sadece derleme zamanında kullanacağımız için -D parametresiyle kuruyorum:

npm install -D @11ty/eleventy

😉 Paketlerle ilgili güvenlik uyarısı verirse, bir bakın tabi, ama çok da fazla şey etmeyin.

Eleventy’yi Çalıştırma

Bundan sonraki işimizi hızlandırmak adına npm betiği (script) eklesek iyi olacak; o yüzden en sevdiğiniz editörde package.json dosyasını açın:

code package.json

Noktalarla işaretli yerlere denk gelen içeriği silmeden, package.json‘a sadece aşağıda gördüğünüz satırı ekleyin:

{
  ...
  "scripts": {
    "eleventy": "eleventy",
    ...
  },
  ...
}

Editörü şimdilik kapatıp terminale dönün ve aşağıdaki komutu çalıştırın:

npm run eleventy

Ne oldu? Hiçbir şey. 😀

> blog@1.0.0 eleventy /Users/armanozak/Projects/blog
> eleventy

Wrote 0 files in 0.02 seconds (v0.11.0)

Henüz içerik oluşturmadık, o yüzden. Oluşturalım, bir de öyle deneyelim. Terminalde şunu çalıştırın:

echo "# Merhaba Dünya\!\n\nArtık 11ty kullanıyorum." \
> index.md

Ya da index.md isimli bir dosya oluşturup, içine aşağıdakileri yazın:

# Merhaba Dünya!

Artık 11ty kullanıyorum.

Şimdi tekrar Eleventy’yi çalıştıralım. Yalnız, bu sefer, sonucu da görmek için, komutu biraz değiştirelim:

npm run eleventy -- --serve

Eleventy projeyi derleyecek ve aşağıdaki çıktıyı verecek:

> blog@1.0.0 eleventy /Users/armanozak/Projects/blog
> eleventy "--serve"

Writing _site/index.html from ./index.md.
Wrote 1 file in 0.34 seconds (v0.11.0)
Watching…
[Browsersync] Access URLs:
 -------------------------------------
       Local: http://localhost:8080
    External: http://192.168.1.21:8080
 -------------------------------------
          UI: http://localhost:3001
 UI External: http://localhost:3001
 -------------------------------------
[Browsersync] Serving files from: _site

Geliştirme sunucusu başlattığından bahsediyor. Bu sunucu, değişiklik yaptığnız dosyaları takip edip, yeniden derleme yapar ve sonuçları anında tarayıcıya yansıtır. Yaptığınız değişikliğe göre bazen geliştirme sunucusunu durdurup yeniden başlatmak gerekebilir. Tarayıcınızda http://localhost:8080 adresini açıp bakın:

Eleventy ile ilk derleme sonucu: Merhaba Dünya

Voila! Bu noktada, terminalinizde ls -F1 . _site komutunu çalıştırırsanız, şu dosya ve klasörleri göreceksiniz.

.:
_site/
index.md
node_modules/
package-lock.json
package.json

_site:
index.html

Eleventy, derlediği dosyaları _site dizininin altına yerleştiriyor ve --serve parametresi verildiğinde bu dosyaları sunuyor. Nitekim, index.md‘yi index.html‘e çevirmiş durumda.

📘 Eleventy’nin derlerken okuyacağı kaynağı ve çıktıyı yerleştireceği dizini belirlemenin iki yolu var:

  1. CLI komutuna vereceğiniz --input ve --output parametreleriyle
  2. Konfigürasyon dosyasında

Eleventy’de Sayfa Düzeni (Layout) Ekleme

Eleventy kullanarak Markdown dosyalarını kolayca derleyebiliyoruz, bunu anladık. Ne var ki, sitemizin bir tasarıma ve dolayısıyla sayfa düzenlerine ihtiyacı var.

Eleventy’de Şablonlar

Sayfa düzenine başlamadan önce Eleventy’nin en beğendiğim özelliklerinden birini inceleyelim. Geliştirme sunucusunu kapatmadan, editörünüzde index.md dosyasını açın ve içeriğini şöyle değiştirin:

---
name: Dünya
---

# Merhaba {{ name }}!

Artık 11ty kullanıyorum.

Şimdi tarayıcınızı açıp sayfayı yenileyin. Bir şeyin değişmediğini göreceksiniz. Burada olan şu: Eleventy, Markdown dosyalarını şablon motorundan geçiriyor, dolayısıyla ön bilgilerdeki (front matter) değişkenleri şablonun içinde kullanabiliyorsunuz. Bu özellik, birazdan çok işimize yarayacak.

📘 Eleventy, Markdown dosyalarını derlerken, varsayılan olarak Liquid şablon motorunu kullanıyor; ama siz isterseniz bunu .eleventy.js dosyasında döndüğünüz opsiyonlarda markdownTemplateEngine vererek değiştirebilirsiniz. Eleventy konfigürasyonunu ileride göreceğiz.

Tasarım yapacak durumda değiliz, o yüzden geliştiriciler için hazırlanmış olan ücretsiz DevBlog şablonunu indirin. Şablon bir zip dosyası olarak iniyor. Zip dosyasını açıp, içeriğini projenizde oluşturacağınız _includes/layouts isimli dizine taşıyın. Şöyle görünecek:

Eleventy şablonlarını eklediğimiz _includes dizininin içeriği

Eleventy, projenize ekleyeceğiniz Markdown, HTML ve şu sayfadaki tüm formatları konfigürasyon gerektirmeksizin derlerken, _includes altına yerleştirdiğiniz dosyaları derlemez. Sizin bunları bir şablonda kullanmanızı bekler. Biz de öyle yapacağız; ama önce, ana proje dizininde bir .eleventy.js ( isim nokta ile başlıyor) dosyası oluşturup, içine şu kodu ekleyelim:

module.exports = function(eleventyConfig) {
  eleventyConfig.addPassthroughCopy({
    "_includes/layouts/assets/css": "assets/css"
  });
  eleventyConfig.addPassthroughCopy({
    "_includes/layouts/assets/fontawesome": "assets/fontawesome"
  });
  eleventyConfig.addPassthroughCopy({
    "_includes/layouts/assets/images": "assets/images"
  });
  eleventyConfig.addPassthroughCopy({
    "_includes/layouts/assets/js": "assets/js"
  });
  eleventyConfig.addPassthroughCopy({
    "_includes/layouts/assets/plugins": "assets/plugins"
  });
};

Bu Eleventy’nin kullandığı varsayılan konfigürasyon dosyası ve yaptığımıza “passthrough” deniyor. Eleventy, şablon olarak derleyemediği dosyaları es geçer, yani taşımaz veya kopyalamaz. Biz burada Eleventy’ye “Şu dizinleri olduğu gibi verdiğimiz hedeflere kopyala.” demiş olduk. Tabi, yaptığımız manüel bir işlem, ama passthrough kopyalamanın farklı yolları da mevcut.

📘 Eleventy’nin derlerken okuduğu kaynağın değiştirilebildiğinden bahsetmiştim. Passthrough kopyalamada verdiğiniz adresler projenizin ana dizinine göreli olmalıdır, kaynak olarak gösterdiğiniz dizine değil.

Temanın index.htmlindeki tüm yollar göreli verilmiş. Ana sayfada problem olmaz; ama diğer sayfalarda sıkıntı çıkaracaktır. Dosyada src ve href özelliklerini bulup, dosya yollarını mutlak (absolute) hale getirin.

Devam edelim. Editörünüzde tekrar index.md dosyasını açın ve içeriğini şöyle değiştirin:

---
title: Ad Soyad'ın Kişisel Blogu
description: Dilediğiniz bir açıklama metni girin. Meta taglerine ekleyeceğiz bunları.
layout: layouts/index.html
---

İçeriği sildik, ön bilgileri de layout, title ve description olacak şekilde değiştirdik. Eğer şu an tarayıcınıza bakarsanız, aşağıdaki resimle karşılaşmanızı beklerim.

Eleventy ile derlenen DevBlog index sayfası

Eleventy’de _includes dizininin görevi, şablonlarınızı barındırmak. Ön bilgiye layout isimli bir değişken ekleyip, bu şablonlardan birine yok belirttiğinizde, Eleventy o içeriği bu şablona göre derliyor.

Tarayıcınızda kodu incelerseniz, title ve description‘ın henüz yerleştirilmediğini göreceksiniz. Bunu değiştirmek için _includes/layouts/index.html dosyasını açın ve <head>elemanının içinde duran <title> ve <meta name=“description”> elemanlarını bulup aşağıdaki şekilde değiştirin:

<title>{{ title }}</title>
<meta name="description" content="{{ description }}" />

Tarayıcıdan tekrar kontrol ederseniz, HTML etiketlerinin değiştiğini göreceksiniz. Buradan çıkarılacak önemli bilgi şu: Ön bilgilerde tanımlanan değişkenleri, sadece tanımlandıkları dosyada değil, HTML ve Markdown da dahil, tüm şablonlarımızda kolayca kullanabiliyoruz. Çünkü, Eleventy bunları hiyerarşiye göre yukarıdan aşağıya doğru iletiyor. Buna da “data cascade” deniyor ve değişken ezmeye dayalı çok basit bir mantığı var. Sonraki bölümde değineceğiz.

Bu bölümü bitirmeden sayfa düzeni zincirlemeyi görelim. HTML’de iki tane <section> elemanı var. Bunların haricinde kalan tüm elemanları _includes/layouts/_layout.html oluşturup içine taşıyın. Taşıdığınız HTML’de main-wrapper‘ı bulun ve şöyle değiştirin:

<div class="main-wrapper">
  {{ content }}

  <!-- footer burada, silmeyin!!! -->
</div>

Son olarak, _includes/layouts/index.html‘i yeniden açın ve en üste ön bilgi ekleyin:

---
layout: layouts/_layout.html
---

<!-- HTML burada, silmeyin!!! -->

Derleme bittiğinde, hiçbir şeyin değişmediğini, index.html içeriğinin (content) _layout.html içerisine yerleştirilerek derlendiğini görmeniz lazım.

Şu anda index.md → index.html → _layout.html şeklinde bir şablon (en soldaki) ve sayfa düzeni (diğerleri) zinciri oluştu ve buna “layout chaining” deniyor. Zincirin bir önceki halkasını {{ content }} olarak kullanmak suretiyle bu zinciri uzatmak mümkün.

📘 Seçtiğiniz şablon motoruna göre, “template inheritance” kavramından da yararlanabilirsiniz. Ayrıca {{ content }} kullanımı da şablon motoruna göre değişiyor. Detayları burada bulabilirsiniz.

Eleventy’de Veri Kaynakları

Hiyerarşide öncelik sırasına göre veri tipleri şöyle:

Verinin akış yönü listenin aşağısından yukarıya doğru. Bu da “Ön bilgiler global veriyi ezer.” demek. Yine şablon ön bilgileri sayfa düzeni ön bilgilerini eziyor. Hepsine değinmem mümkün değil; ama global veri kaynağını hemen ayarlayalım. Önce terminalde şu kodu çalıştırın:

mkdir _data && \
echo '{ "author": "Ad Soyad", "title": "adsoyad.com" }' \
> _data/site.json

Ya da _data dizini ve onun altına site.json oluşturup, içine de aşağıdakini yapıştırabilirsiniz.

{
  "author": "Ad Soyad",
  "title": "adsoyad.com"
}

Şimdi _includes/layouts/_layout.html dosyasını açın ve aşağıdaki HTML etiketlerini bulup değiştirin:

<title>{{ title }} | {{ site.title }}</title>
<meta name="author" content="{{ site.author }}" />

Eleventy, _data dizinini global veri kaynağı olarak kullandığı için, bu dizine yerleştirilen veriyi tüm şablonlarımızda kolayca tüketebiliyoruz. İlişkiyi muhtemelen kurmuşsunuzdur; ama burada veriye site.title ve site.author şeklinde erişmemizin sebebiyse dosya adının site.json olması.

Peki size global verilerin tümünün ön işlemeden (preprocessing) geçirildiğini söylesem? Aşağıdaki satırları package.json‘a ekleyin:

{
  ...
  "author": {
    "name": "Ad Soyad",
    "url": "https://www.adsoyad.com"
  },
  ...
}

Tekrar _data/site.json‘u açın ve şöyle değiştirin:

{
  "author": "{{ pkg.author.name }}",
  "title": "{{ pkg.author.url | remove: 'https://www.' }}"
}

Nasıl ama? 🚀 Global verimizde package.json‘u kullanmakla kalmadık, bir de Liquid’in filtrelerinden yararlandık.

Şimdi başka bir şey deneyelim. Blog listesi _includes/layouts/index.html‘de kaldı. Onu bulup <div class=“container”> elemanının içini (elemanı silmeden) aşağıdaki gibi değiştirin:

{%- for post in collections.all -%}
<div class="item mb-5">
  <div class="media">
    <img
      class="mr-3 img-fluid post-thumb d-none d-md-flex"
      src="{{ post.data.cover.src }}"
      alt="{{ post.data.cover.alt }}"
    />
    <div class="media-body">
      <h3 class="title mb-1">
        <a href="{{ post.url }}">{{ post.data.title }}</a>
      </h3>
      <div class="meta mb-1">
        <span class="date">
          {{ post.date | date: "%Y-%m-%d" }}
        </span>
        <span class="time"></span>
        <span class="tags"></span>
      </div>
      <div class="intro">
        {{ post.data.page.excerpt | strip | append: "…" }}
      </div>
      <a class="more-link" href="{{ post.url }}">Devamını oku &rarr;</a>
    </div>
  </div>
</div>
{%- endfor -%}

Burada neler döndüğüne yakından bakalım:

  • {%- for post in collections.all -%} ile {%- endfor -%} arasında kalan kısım, tahmin edeceğiniz üzere, tekrarlayacak. Eleventy, HTML dosyalarını varsayılan olarak Liquid ile derliyor; ama konfigürasyon dosyasında htmlTemplateEngine özelliğini dönerek bunu değiştirebiliyorsunuz. Bkz. HTML Dosyaları için Varsayılan Şablon Motoru
  • collections bize Eleventy tarafından sağlanan özel bir veri tipi. post.data.page şeklinde eriştiğimiz bilgiler de öyle. Eleventy tarafından sağlanan veri tiplerine şuradan ulaşabilirsiniz.
  • collections.all, bütün içeriklerinizin bulunduğu bir liste. Bunun yerine, makalelerinize post isimli bir tag ekleyip collections.post şeklinde de erişebilirsiniz. Koleksiyonlar hakkında bilgi için burayı okuyun.
  • Makale ve diğer sayfalar oluşturduğumuz veriye post.data.title şeklinde erişebiliyoruz. Öte yandan Eleventy’nin bizim için oluşturduğu veriye de post.url veya şeklinde erişebiliyoruz.
  • | date: "%Y-%m-%d" bir Liquid filtresi. Filtreler, veriyi, orijinalini değiştirmeksizin, görsel olarak dönüştürmeyi sağlayan basit fonksiyonlardır. Buradaki date Liquid filtresinin adı, “%Y-%m-%d” ise tarih formatını belirleyen filtre parametresi.
  • post.data.page.excerpt, adından da anlaşılabileceği üzere içeriğimizden bir alıntı. Nasıl oluşturulduğuna bakacağız.
  • | strip | append: "…" iki filtrenin ard arda kullanılabileceğini gösteriyor. Önce strip ile boşlukları atmışız, sonra append ile üç nokta (ellipsis) eklemişiz.

Bunu yaptıktan sonra, _includes/layouts/layout.html üzerinde biraz oynayın. İsmi ve resmi değiştirin, kendiniz hakkında kısa bilgi verin, sosyal medya bağlantılarını güncelleyin, menüyü Türkçeleştirin, tema değiştirin (<link> elemanı var), vs. Eleventy yaptığınız her değişiklikte yeniden derleme yapacaktır. Nihayet şuna benzer bir görüntüyla karşılaşmanız lazım:

Eleventy ile şablon üzerinde değişiklik yapınca ortaya çıkan görüntü: Renk ve içerik tamamen değişmiş

Ana sayfa, blog yazısı gibi listelenmiş; çünkü o da collections.all‘a dahil. Hariç tutmak için index.md dosyasını açıp ön bilgilere eleventyExcludeFromCollections: true ekleyin. Listeden silinmiş olması lazım.

📘 Başka yöntemler de var; ama bunu kullanarak, işiniz bitene dek makalelerinizin keşfedilmesine engel olabilirsiniz.

Dikkatinizi çektiyse, bütün bilgiler eksik olmasına karşın post.date ve post.url çalışıyor; çünkü Eleventy bunları oluşturduğunuz dosyadan çıkarım yaparak (date için oluşturma tarihi, url için dosya yolunu kullanarak) sağlıyor. Birazdan diğer bilgileri de sağlayacağız.

Şimdi bir blog yazısı oluşturalım. Terminalinizde aşağıdaki komutu çalıştırın:

mkdir -p _posts/merhaba-dunya && touch $_/2020-07-15-index.md

Ya da elle _posts/merhaba-dunya/2020-07-15-index.md yolunda bir dosya oluşturun ve içine aşağıdakini yerleştirin:

---
title: Merhaba Dünya!
description: "Eleventy ile blog oluşturuyorum."
tags:
  - JAMStack
  - 11ty
cover:
  src: 11ty.png
  alt: Eleventy logosu
layout: layouts/blog-post.html
---

Lorem ipsum dolor sit amet consectetur adipisicing elit. Ducimus iusto sint commodi quo minus sapiente pariatur veritatis <!-- özet --> dolorem exercitationemid inventore est consequatur nisi, fugiat similique neque ullam magni saepe.

Veniam dolor odit recusandae ut accusantium laboriosam delectus, optio facere pariatur at, consequatur provident ipsum voluptatibus ratione, totam repudiandae modi! Enim quisquam itaque obcaecati officia placeat nam, corrupti quo sit?

Similique suscipit voluptatibus magnam enim, officiis reiciendis sunt at, eveniet temporibus earum veniam nisi provident molestias laboriosam sapiente nemo! Alias excepturi nisi quibusdam minima ex vitae dolore, consequuntur non provident.

Aşağıdaki resmi de yeni oluşturduğunuz dosyanın yanına koyun ve adını 11ty.png yapın:

Eleventy logo: 11ty

Blog listesine yeniden bakacak olursanız, “Merhaba Dünya!” başlıklı makalenin listelendiğini göreceksiniz. Araya yerleştirdiğimiz <!— özet —> şeklindeki HTML yorumu, metnin buradan öncesinin page.excerpt olarak kullanılabilmesini sağlayacak; ama bunun için .eleventy.js dosyasına aşağıdakini eklememiz gerekiyor:

module.exports = function(eleventyConfig) {
  eleventyConfig.setFrontMatterParsingOptions({
    excerpt: true,
    excerpt_separator: "<!-- özet -->"
  });

  // diğer konfigürasyonlar burada, silmeyin!!!
};

Blog listesinde ayırdığınız kısma kadar olan içerik gösterilecek; ama bağlantıya tıkladığınızda DevBlog’un örnek makalesi açılacaktır. Bunu düzeltmek için _includes/layouts/blog-post.html dosyasını açın ve içeriğini şöyle değiştirin:

---
permalink: "/{{ page.fileSlug }}/index.html"
layout: layouts/_layout.html
---

<article class="blog-post px-3 py-5 p-md-5">
  <div class="container">
    <header class="blog-post-header">
      <div class="title mb-2 h1">{{ title }}</div>
      <div class="meta mb-3">
        <span class="date">{{ page.date | date: "%Y-%m-%d" }}</span>
        <span class="time"></span>
        <span class="tags"></span>
      </div>
    </header>

    <div class="blog-post-body">
      {% if cover %}
      <figure>
        <img
          class="img-fluid"
          src="/img/{{ cover.src }}"
          alt="{{ cover.alt }}"
        />
        {% if cover.credit %}
        <figcaption class="mt-2 text-center image-caption">
          Fotoğraf:
          <a href="{{ cover.credit.url }}" target="_blank" rel="noopener noreferrer">
            {{- cover.credit.name -}}
          </a>
        </figcaption>
        {% endif %}
      </figure>
      {% endif %}

      {{ content }}
  </div>
</article>

Buradaki önemli şeylere bir göz atalım:

  • permalink: "/{{ page.fileSlug }}/index.html" blog yazısı sayfalarının nereye derleneceğini gösteriyor. Yani, makalemiz _sites/merhaba-dunya/index.html adresine yerleşecek. Bunu her dosyada ayrı ayrı yapmak mümkün; ama biz burada “data cascade” kavramından yararlandık; böylece bu şablonu kullanan tüm makaleler kendi “slug”larını koruyacak.
  • {% if cover %} ve {% endif %} arasında kalan kısım sadece ön bilgilerden cover gelirse gösterilecek. Fotoğraf sahibine atıf da aynı şekilde çalışıyor, eğer cover.credit varsa görünecek.

Eleventy ile oluşturduğumuz blog sayfasındaki resim hatası

Resim hatası almamız çok normal, çünkü henüz resimlerin _site dizinine taşınmasını sağlamadık. Yine .eleventy.js dosyasını açın ve aşağıdakini ekleyin:

module.exports = function(eleventyConfig) {
  eleventyConfig.addPassthroughCopy({
    "_posts/**/*.{jpg,png}": "img"
  });

  // diğer konfigürasyonlar burada, silmeyin!!!
};

Derleme tamamlandığında resim gelmiş olmalı:

Eleventy ile oluşturduğumuz blog sayfası

🔎 Siz bunu denerken, 15 Temmuz 2020 geçmişte kalacak. Makale tarihinin sabit ve 2020-07-15 olmasının sebebi, Eleventy’nin bu veriyi dosya adından çözümlemesi.

Eleventy’ye Shortcode Ekleme

Muhtemelen fark etmişsinizdir, aslında şu kod blog sayfamızda tekrarlıyor olabilir:

<figure>
  <img
    class="img-fluid"
    src="/img/{{ cover.src }}"
    alt="{{ cover.alt }}"
  />
  {% if cover.credit %}
  <figcaption class="mt-2 text-center image-caption">
    Fotoğraf:
    <a href="{{ cover.credit.url }}" target="_blank" rel="noopener noreferrer">
      {{- cover.credit.name -}}
    </a>
  </figcaption>
  {% endif %}
</figure>

Eleventy’de tekrardan kurtulmanın en güzel yolu shortcode eklemek. Shortcode, şablonlarınızda kullanabileceğiniz birer fonksiyon aslında. Eleventy, bu fonksiyonları çağırıyor va dönen HTML’i içeriğe dahil ediyor. Dolayısıyla shorcode kullanarak şablonlarda tekrardan kaçabiliyorsunuz.

İki tip shortcode var: Single ve Paired. Single, sadece değişken alırken, paired başladığı ve bittiği yerin arasında kalan içeriği (content) de parametre olarak alıyor.

Bir örnek yapalım. Terminalinizde aşağıdaki komutu çalıştırın:

mkdir _shortcodes && touch $_/img-fluid.js

Ya da elle _shortcodes/img-fluid.js yolunda bir dosya oluşturup içine aşağıdaki kodu yerleştirin:

const md = require("markdown-it")({
  html: true // Eleventy varsayılanı
});

module.exports = imgFluid;

function imgFluid(content, src, alt = "") {
  if (!src) return "";

  const caption = content
    ? `<figcaption class="mt-2 text-center image-caption">${md.renderInline(content)}</figcaption>`
    : "";

  return `<figure><img class="img-fluid" src="/img/${src}" alt="${alt}" />${caption}</figure>`;
}

Sonra .eleventy.js dosyasını açıp şunları ekleyin:

const imgFluid = require("./_shortcodes/img-fluid");

module.exports = function(eleventyConfig) {
  // diğer konfigürasyonlar burada, silmeyin!!!

  eleventyConfig.addPairedShortcode("imgFluidWithCaption", imgFluid);
  eleventyConfig.addShortcode("imgFluid", imgFluid.bind(null, null));
};

Burada ne döndüğünü anlatayım:

  • img-fluid.js dosyasından bir fonksiyon export ettik.
  • content bu fonksiyonun ilk parametresi ve “paired” kullanımda gerekli.
  • require kullanarak fonksiyonumuzu import ettik.
  • addPairedShortcode metoduyla fonksiyonu doğrudan pair shortcode olarak ekledik.
  • addShortcode metoduyla da fonksiyonu single shortcode olarak ekledik.
  • .bind(null, null) ile contentin single kullanımda null olmasını sağladık. Buna JavaScript’te partially applied function deniyor.

📘 addPairedShortcode ve addShortcode “universal”, yani şu şablon motorlarının hepsinde geçerli:

  • Liquid (*.liquid)
  • Nunjucks (*.njk)
  • JavaScript (*.11ty.js)
  • Handlebars (*.hbs, async function hariç)

Şimdi _includes/layouts/blog-post.html dosyasını açıp içerisinde imgFluidi kullanın:

<!-- front matter burada, silmeyin!!! -->

<article class="blog-post px-3 py-5 p-md-5">
  <div class="container">
    <!-- header burada, silmeyin!!! -->

    <div class="blog-post-body">
      {% if cover %}
      {% imgFluid cover.src, cover.alt %}
      {% endif %}

      {{ content }}
  </div>
</article>

Hiçbir şey değişmeyecek. Farklı bir senaryo için _posts/merhaba-dunya/2020-07-15-index.md dosyasını açın; 2. ve 3. paragrafların arasına aşağıdakini ekleyin:

{% imgFluidWithCaption cover.src cover.alt %}
Kaynak: [Arman Özak](https://www.armanozak.com/eleventy-ile-blog-olusturma/)
{% endimgFluidWithCaption %}

Ortaya çıkan sonuç resimdekine benzemeli:

Eleventy ile shortcode kullanarak figure altında figcaption gösterimi

Gördüğünüz gibi, shortcode content aldığında çok güçlü bir kullanıma kavuşuyor. Almadığındaysa filtrelerden pek farkı yok. Haydi filtre yapalım ve sayfalarımızda boş kalan verileri dolduralım.

Eleventy’ye Filtre Ekleme

Okuma süresi ve makale etiketleri boş kaldı. Onları eklemek için veriyi dönüştürmemiz ve öyle göstermemiz gerekiyor. Bunun için şablon filtrelerinden yararlanabiliriz. Aşağıdaki komutu terminalinizde çalıştırın:

mkdir _filters && touch $_/reading-time.js

Veya elle _filters/reading-time.js yolunda bir dosya oluşturup içine şu kodu ekleyin:

module.exports = readingTime;

const SPEED = 165; // kelime/dk

function readingTime(text) {
  if (!text) return 0;

  return Math.round(String(text).split(/\s+/).length / SPEED) || 1;
}

Yine .eleventy.js dosyasını açıp aşağıdakileri ekleyin:

// diğer importlar burada, silmeyin!!!
const readingTime = require("./_filters/reading-time");

module.exports = function(eleventyConfig) {
  // diğer konfigürasyonlar burada, silmeyin!!!

  eleventyConfig.addFilter("reading_time", readingTime);
};

Son olarak, <span class=“time”> elemanını bulup (2 yerde var; index.html ve blog-post.html) aşağıdaki gibi değiştirin:

<!-- index.html -->
<span class="time">
  {{ post.templateContent | strip_html | reading_time }}dk okuma süresi
</span>

Blog sayfasındaysa contenti kulanıyoruz:

<!-- blog-post.html -->
<span class="time">
  {{ content | strip_html | reading_time }}dk okuma süresi
</span>

🔎 Ana sayfada post.templateContent kullandık; çünkü post.data.content sayfa düzeni içeriğini de barındırıyor.

Artık ilk boşluğun olduğu yerde “1dk okuma süresi” yazıyor olması lazım. Eğer makalenizdeki paragrafları çoğaltırsanız, dakikaların da arttığını göreceksiniz. 165 sayısıyla oynadığınızda da süre güncellenecektir.

Etiketleri de yapalım. Terminalde aşağıdaki komutu çalıştırın:

touch _filters/category-tags.js

Veya elle _filters/category-tags.js yolunda bir dosya oluşturup aşağıdaki kodu ekleyin:

module.exports = categoryTags;

const TAGS = {
  JAMStack: {
    color: "info",
    url: "#"
  },
  "11ty": {
    color: "secondary",
    url: "#"
  }
};

function categoryTags(tags) {
  return tags.reduce((html, tag) => {
    if (!tag in TAGS) return html;

    const { color, url } = TAGS[tag];

    return `${html}<a href="${url}" class="badge badge-pill badge-${color} text-white mr-1">${tag}</a>`;
  }, "");
}

Sonra .eleventy.js dosyasını açıp aşağıdaki satırları ekleyin:

// diğer importlar burada, silmeyin!!!
const categoryTags = require("./_filters/category-tags");

module.exports = function(eleventyConfig) {
  // diğer konfigürasyonlar burada, silmeyin!!!

  eleventyConfig.addFilter("category_tags", categoryTags);
};

Son olarak, <span class=“tags”> elemanını bulup (2 yerde var; index.html ve blog-post.html) aşağıdaki gibi değiştirin:

<!-- index.html -->
<span class="tags">{{ post.data.tags | category_tags }}</span>

Yine, blog sayfasındaki sadece tags olacak:

<!-- blog-post.html -->
<span class="tags">{{ tags | category_tags }}</span>

Şu an resimdekine benzer bir ekran görüyor olmalısınız:

Eleventy filtreleriyle makale sayfasına okuma süresi ve kategori eklenmiş

Tebrikler! Blog sayfası bitti. Şimdi blogumuzu biraz zenginleştirelim.

Eleventy Eklentileri (Plugin)

Bu devirde, “syntax highlighting” bulunmayan geliştirici blogu olur mu? Olmaz. Hemen bir eklenti kuralım.

npm install -D @11ty/eleventy-plugin-syntaxhighlight

Kurulum bitince .eleventy.js dosyasını açıp aşağıdaki satırları ekleyin:

// diğer importlar burada, silmeyin!!!
const syntaxHighlight = require("@11ty/eleventy-plugin-syntaxhighlight");

module.exports = function(eleventyConfig) {
  // diğer konfigürasyonlar burada, silmeyin!!!

  eleventyConfig.addPlugin(syntaxHighlight);
};

Son olarak _includes/layouts/blog-post.html dosyasına PrismJS sitilini ekleyin:

---
permalink: "/{{ page.fileSlug }}/index.html"
layout: layouts/_layout.html
---

<article class="blog-post px-3 py-5 p-md-5">
  <!-- article burada, silmeyin!!! -->
</article>

<script>
  !(function (d) {
    var l = d.createElement('link');
    l.rel = 'stylesheet';
    l.href = 'https://cdn.jsdelivr.net/npm/prismjs@1.20.0/themes/prism-tomorrow.min.css';
    d.head.appendChild(l);
  })(document);
</script>

İşlem tamam! Artık, Markdown şablonlarınızda kod blokları yazabilirsiniz. PrismJS onları güzel güzel renklendirecektir. Diğer Eleventy eklentileri için şu sayfaya bakabilirsiniz.

🔎 DevTools’u açıp gelen HTML cevabına bakarsanız, kod bloklarının PrismJS’ten geçirilmiş bir şekilde geldiğini göreceksiniz. Yani istemci tarafında PrismJS’e ait bir JavaScript yok. Bu, alışık olduğunuz istemci tarafında işlemeden daha performanslı bir yöntem.

Eleventy’de Yardımcı Kütüphaneler

Listeleme sayfaları için küçük resim ihtiyacı var; ama elbette bunu elle yapmak istemeyiz. Bunun için yardımcı kütüphaneden faydalanacağız. Terminalinizde şu komutu çalıştırın:

npm install -D @11ty/eleventy-img

Kurulum bitince, aşağıdakini çalıştırın:

touch _shortcodes/img-thumb.js

Yahut elle _shortcodes/img-thumb.js yolunda bir dosya oluşturup içine şu kodu ekleyin:

const Image = require("@11ty/eleventy-img");

module.exports = imgThumb;

async function imgThumb(src, alt) {
  const { webp } = await Image("./_site/img/" + src, {
    formats: ["webp"],
    widths: [160],
    outputDir: "./_site/img/"
  });

  const { url, width, height } = webp.pop();

  return `<img src="${url}" width="${width}" height="${height}" alt="${alt}" class="mr-3">`;
}

Bu kod WEBP formatında 160px genişliğinde “hash”lenmiş imajlar üretip hem çıktıya iletiyor, hem de <img> elemanını ona göre dönüyor. Fonksiyonu tüketmek için .eleventy.js dosyasını açıp aşağıdaki satırları ekleyin:

// diğer importlar burada, silmeyin!!!
const imgThumb = require("./_shortcodes/img-thumb");

module.exports = function(eleventyConfig) {
  eleventyConfig.addPassthroughCopy({
    "_posts/**/*.{jpg,png}": "img"
  });

  eleventyConfig.addShortcode("imgThumb", imgThumb);

  // diğer konfigürasyonlar burada, silmeyin!!!
};

Passthrough kopyalamadan sonra olması önemli; çünkü kopyalanan resimleri ebatlıyoruz. Nihayet, _includes/layouts/index.html‘i yeniden açın ve döngüyü bulup aşağıdaki gibi değiştirin:

{%- for post in collections.all -%}
<div class="item mb-5">
  <div class="media">
    {% imgThumb post.data.cover.src, post.data.cover.alt %}

    <div class="media-body">
      <!-- media body burada, silmeyin!!! -->
    </div>
  </div>
</div>
{%- endfor -%}

Ekranda böyle görünecektir:

Eleventy'de yaratılan kucuk resim listenin solunda

Eleventy ile Sayfalama (Pagination)

Eleventy, içinde sayfalama dahil gelir. Sizin bunun için uğraşmanıza hiç gerek yoktur. Çabucak _posts/merhaba-dunya klasörünün 5’ten fazla kopyasını çıkarın. Aşağıdaki komut işinize yarayabilir.

for i in {1..9}; do cp -R _posts/merhaba-dunya "_posts/post-$i"; done

Ardından da _includes/layouts/index.html dosyasını açıp içini aşağıdaki gibi değiştirin:

---
pagination:
  data: collections.all
  size: 5
layout: layouts/_layout.html
---

<!-- ilk section burada, silmeyin!!! -->

<section class="blog-list px-3 py-5 p-md-5">
  <div class="container">
    {%- for post in pagination.items -%}
    <!-- makale buarada, silmeyin!!! -->
    {%- endfor -%}

    <nav class="blog-nav nav nav-justified my-5">
      {% if pagination.href.previous %}
      <a
        class="nav-link-prev nav-item nav-link rounded-left"
        href="{{ pagination.href.previous }}"
      >
        Öncekiler <i class="arrow-prev fas fa-long-arrow-alt-left"></i>
      </a>
      {% endif %} {% if pagination.href.next %}
      <a
        class="nav-link-next nav-item nav-link rounded"
        href="{{ pagination.href.next }}"
      >
        Sonrakiler <i class="arrow-next fas fa-long-arrow-alt-right"></i>
      </a>
      {% endif %}
    </nav>
  </div>
</section>

Bu kadar. Artık blog listesi için sayfalamanız var. 🤯 Daha detaylı sayfalama için buraya bakabilirsiniz.

Eleventy Projesini Yayına Alma

Diyelim ki diğer işleri de bitirdiniz. Blogunuzu ücretsiz bir şekilde yayına almak için projeyi GitHub, Bitbucket veya GitLab‘a atıp, Netlify veya Vercel gibi bir platformda birkaç ayar yapmanız yeterli.

İzinler, domain ayarları falan gerekecektir tabi; ama “build” adımlarını reponuza ekleyeceğiniz bir dosyayla da platforma iletebilirsiniz. Netlify için netlify.toml dosyası şuna benzer bir şey olsa gerek mesela:

[build]
  command = "npx @11ty/eleventy"
  publish = "_site"

Siz yine şurayı bir okuyun, bazı fikirler verebilir.

📘 Projenize “README” eklemeyi düşünüyorsanız, .eleventyignore isimli bir dosya oluşturup içine README.md yazmayı unutmayın, yoksa Eleventy onu da derler.

Kapanış

Bu kadar uzun makalede kapanışa herhalde gerek yoktur. Eleventy çok güzel efendim. Tavsiye eder, kolaylıklar dilerim.

Bitti. 🤝

Loading...
Levent Arman Özak

Levent Arman Özak