02 Mei 2021

11ty: Related Books

Shortcode untuk menampilkan related books dengan memanfaatkan JSON data

Di halaman bacaan saya ingin menampilkan relasi buku terkait dengan review buku yang saya tulis. Tampilan yang diinginkan adalah seperti Twitter Cards dengan gambar dan deskripsi. Gambarnya nanti bisa diisi coverImg dari masing - masing artikel baca yang sudah saya tulis.

Membuat basis data dalam JSON

Hal pertama yang dilakukan adalah membuat basis data dan menyimpannya dalam format JSON. Caranya adalah dengan membuat file baru & memasuk-kan data collection dengan format menyesuaikan bentuk format valid dari JSON.

---
permalink : /baca/data.json
---

[{% for post in collections.baca %}
{
    "title": "{{ post.data.title }}",
    "date": "{{ post.data.date }}",
    "url": "{{ post.url }}",
    "ringkasan": "{{ post.data.ringkasan }}",
    "penulis": "{{ post.data.penulis }}",
    "coverImg": "{{ post.data.coverImg }}",
    "resensi": "{{ post.data.resensi }}"
}{{ '' if loop.last else ',' }}
{% endfor %}]

title, date, url, ringkasan dan seterusnya adalah field yang sudah saya tulis di YAML/frontmatter pada setiap artikel baca. Tampilan frontmatter seperti ini :

---
    layout: isi/buku.njk
    title : 'Sewu Dino'
    date: 2020-08-17
    ringkasan: 'Pertempuran antar keluarga dari Trah Pitu yang memakan banyak korban'
    keywords: 'Sewu Dino, Janur Ireng, Ranjat Kembang, Trah Pitu, Simpleman, Horor, Santet'
    coverImg : 'https://ik.imagekit.io/hjse9uhdjqd/tr:n-cover/buku/sewuDino_lV8ZEwbP7.jpg'
    penulis: 'Simpleman'
    genre: 
        - Thriller
        - Mistery
        - Jawa
    format: 'Papperback - 230 halaman'
    bahasa: 'Bahasa Indonesia, Bahasa Jawa'
    isbn: '978-602-220-348-3'
    tahun: 2020
    resensi: 'Dia adalah Dela Atmojo, anak yang harus kamu rawat sampai waktunya tiba. Ia dikirimi kutukan santet sewu dino. Santet yang sudah merenggut nyawa hampir seluruh anggota keluarga Atmojo.'
    rating: 3
    beli: https://shopee.co.id/bukune
    dimana: Bukune
    tags: baca
---

Saya mengambil beberapa field yang penting dan hendak dipakai nantinya. Sedangkan hasilnya adalah sebagai berikut

{
    "title": "Sewu Dino",
    "date": "Mon Aug 17 2020 07:00:00 GMT+0700 (Western Indonesia Time)",
    "url": "/baca/sewuDino/",
    "ringkasan": "Pertempuran antar keluarga dari Trah Pitu yang memakan banyak korban",
    "penulis": "Simpleman",
    "coverImg": "https://ik.imagekit.io/hjse9uhdjqd/tr:n-cover/buku/sewuDino_lV8ZEwbP7.jpg",
    "resensi": "Dia adalah Dela Atmojo, anak yang harus kamu rawat sampai waktunya tiba. Ia dikirimi kutukan santet sewu dino. Santet yang sudah merenggut nyawa hampir seluruh anggota keluarga Atmojo."
}

Setelah eleventy di build maka akan tersedia 1 file baru dengan nama data.json dengan path /baca/data.json. File inilah yang nanti akan di-jadikan basis data untuk menentukan relasi artikel.

11ty Shortcodes

Setelah basis data tersedia, selanjutnya adalah membuat fungsi javascript untuk memanggil basis data tersebut. Disini saya memper-gunakan paket node-fetch. Namun sebelum itu perlu menentukan bentuk dari shortcodes yang akan dipakai.

  1. Bentuk shortcodenya. Saya ingin agar bentuk tagsnya adalah sebagai berikut :
 {% related "judul" %}

dimana related akan menjadi fungsi pemanggil shortcodes dan judul men-jadi string query untuk mencari field di dalam JSON Array.

Sehingga di file eleventy.js saya menambahkan syntax berikut :

eleventyConfig.addLiquidShortcode("related", async function(judul){}

Saya sebenarnya adalah pengguna Nunjucks, namun karena default render markdown di eleventy mempergunakan Liquid. Maka shortcodes saya mempergunakan Liquid

Namun bisa juga mempergunakan global shortcodes dengan kode eleventyConfig.addShortcode yang bisa jalan di semua template tags

  1. Ambil basis data dan buat fungsi query

Seperti yang sudah saya sebutkan diatas, saya mempergunakan node-fetch untuk membantu mengambil basis data. Maka yang harus dilakukan pertama kali adalah memasang paket node-fetch:

$ yarn add node-fetch

# atau

$ npm install node-fetch

Pengguna axios bisa mempergunakannya sebagai pengganti fetch. Silakan menyesuaikan kode dibawah dengan fungsi di axios.

kemudian buat fungsi di dalam shortcodes untuk mengambil basis data :

try {
 const response = await fetch('https://kusaeni.com/baca/data.json', {
  method: 'GET',
  headers: {
    'Content-Type': 'application/json'
  } });
 const data = await response.json();
 } catch(reason) {
   console.log(reason)
}

hasil dari response disimpan sebagai JSON.

Opsi lain adalah mempergunakan JSON.parse dengan fs

 const fs   = require('fs')
 const data = JSON.parse(fs.readFileSync("./baca/data.json"));

Dengan JSON.parse, proses build tidak lagi membutuhkan akses internet karena akan data dibaca dari lokal. Dengan syarat file JSON yang dimaksud adalah file existing yang tidak dibuat pada saat proses build.

  1. Kemudian buat fungsi query untuk mengambil data berdasarkan value judul dengan mempergunakan findIndex
const relasi = function (buku, judul) {
 const index = buku.findIndex(function (novel, index) {
  return novel.title.toLowerCase() === judul.toLowerCase()
 })
return buku[index]
 };

const hasilData = await relasi(data, judul);

Kita sebut ini sebagai kode pertama, silakan lihat di seksi update untuk kode kedua dan ketiga sebagai alternatif.

Disini string judul harus diamankan dengan membuat judul menjadi huruf kecil semua toLowerCase() untuk menghindari kesalahan tipo saat mengetik judul.

Update

Kode diatas terlihat komplek sekali, ada kode lebih sederhana namun ketika saya coba membuat waktu build sedikit lebih lama.

const response = await fetch('https://kusaeni.com/baca/data.json');
const data     = await response.json();
const hasilData= data.find(function(caridata) {
    return caridata.title.toLowerCase() === judul.toLowerCase()
}); 

Sebutlah ini sebagai kode kedua.

Sampai disini jika tags {% related "judul" %} dimasukkan ke dalam artikel, maka pada saat build/serve, eleventy akan mengambil data.json dan mengfilternya berdasarkan query judul yang dimasukkan. Hasilnya bisa diliat di log di konsol.

  1. Untuk menampilkan data tersebut di posisi tags disisipkan, maka perlu ditambahkan kode berikut :
return `<div class="flex">
<img class="shadow-md" src="${hasilData.coverImg}"  >
<div class="flex-1"> 
  <a href="${hasilData.url}">${hasilData.title}</a>
  <dl>
    <dt>${hasilData.penulis} </d> 
    <dd>${rese} ...</dd>
  </dl>
</div>
</div>`;

Karena node-fetch menghasilkan promise maka return perlu diakses dengan tambahan .then() callback, sehingga keseluruhan shortcodesnya menjadi seperti ini :

eleventyConfig.addLiquidShortcode("related", async function(judul){
try {
const response = await fetch('https://kusaeni.com/baca/data.json',{
  method: 'GET',
  headers: {
    'Content-Type': 'application/json'
  }});
const data = await response.json();
const relasi = function (buku, judul) {
  const index = buku.findIndex(function (novel, index) {
    return novel.title.toLowerCase() === judul.toLowerCase()
  })
return buku[index]
};
const hasilData = await relasi(data, judul);
 var rese = hasilData.resensi.substr(0, 200)
 return `<div class="flex">
    <img class="shadow-md" src="${hasilData.coverImg}">
    <div class="flex-1"> 
    <a href="${hasilData.url}">${hasilData.title}</a>
    <dl><dt>${hasilData.penulis} </dt> 
      <dd>${rese} ...</dd></dl>
    </div></div>`;} catch (err) {console.log(err)}
const print = async () => {
  const p = await hasilData;
  console.log(p)
};
print()
});

Jika ingin mempergunakan kode kedua, silakan disesuaikan. Saya menambahkan fungsi rese untuk memotong karakter di resensi agar tidak lebih dari 200 karakter

Update

Saya menambahkan fungsi yang sama untuk menampilkan relasi artikel di collection jurnal dengan sedikit perbedaan yaitu tanpa coverImg dan tanpa mempergunakan fetch JSON. Meskipun kode diatas bisa juga diaplikasikan di collection apa saja, namun saya tidak memakainya dengan alasan performa.

Di jurnal saya hanya ingin menampilkan relasi artikel dengan format judul, url, dan desk atau deskripsi. Saya memakai fungsi lain shortcodes yaitu paired shortcodes. Seperti diatas, tulis kode berikut di file .eleventy.js

eleventyConfig.addPairedShortcode("prelated", 
    function(desk, judul, url){
    return `<div class="relasi-artikel">
        <h4 class="header-relasi">Artikel terkait</h4>
        <a class="link" href="${url}" title="${judul}">${judul}</a>
        <p class="desk-relasi">${desk}</p>
    </div>`;
});

Sesuai namanya paired maka shortcodes ini akan membuat template tags baru dengan tags buka dan tutup.

{% prelated "11ty Reader Bar", "/jurnal/11tyReaderBar" %}
11ty Reader Bar : sebuah plugin shortcodes untuk menampilkan readerbar di eleventy
{% endprelated %}

Dengan catatan :

  • "11ty Reader Bar" akan diproses sebagai variable judul,
  • "/jurnal/11tyReaderBar" sebagai url,
  • "11ty Reader Bar : sebuah plugin shortcodes untuk menampilkan readerbar di eleventy" sebagai desk

Hasil dari paired shortcode diatas adalah sebagai berikut:

Shortcode ini bisa juga dipergunakan untuk menggantikan shortcodes dengan parse JSON. Hanya saja setiap hendak menyisipkan related books harus mengetikkan secara manual setiap data yang ingin ditampilkan.

Kesimpulan dan catatan

Alhamdulillah dengan fungsi shortcodes ini saya bisa menampilkan relasi bacaan sesuai dengan keinginan, namun ada beberapa hal yang perlu diper-hatikan saat mempergunakan shortcodes ini, diantaranya :

  1. Untuk mengurangi kesalahan dalam query data berdasarkan judul, maka judul perlu dibuat lowerCase semua. Namun hal ini tidak menjadi solusi jika penulisan judulnya salah karena salah ketik atau salah spasi,

  2. Proses ini harus mengambil data.json dan melakukan parse serta query satu per satu artikel yang memiliki shortcodes related membuat waktu build menjadi lebih lama, sekitar 19 - 30 ms dimana sebelumnya sekitar 9 - 17 ms.

Saya melakukan DEBUG build eleventy dengan hasil sebagai berikut:

  • Untuk kode query pertama membutuhkan waktu sekitar 8.02 detik untuk memproses 28 files,
  • Dan butuh waktu 9,02 detik untuk kode yang kedua.

Uji coba dilakukan dengan mengnonatifkan plugin Eleventy Lazy Images. Jika plugin ini diaktifkan akan membutuhkan waktu sekitar 1 - 2 detik lebih lama. Ini akan menjadi masalah saat mulai melakukan build dengan jumlah halaman yang banyak.

Saat dibuild di Netlify membutuhkan tambahan waktu untuk proses, rata - rata menjadi sekitar 20 - 30 detik (Netlify biasanya butuh 2,5 kali waktu build time) untuk selesai. Jika build time ini konstan, maka jatah build di Netlify bisa menjadi sampai dengan 900 kali setiap bulannya.

  1. Saya belum menemukannya, namun jika ada perintah untuk membuild files tertentu saja atau files terupdate saja tentu akan memangkas waktu untuk build secara signifikan.

  2. Jika waktu build begitu berharga, maka solusi yang paling mendekati adalah mempergunakan paired shortcodes yang tidak perlu melakukan fetch dan proses query data.

eleventyConfig.addPairedShortcode("relatedpair", 
function(resensi, coverImg, judul, url){
 let coverUrl = "https://ik.imagekit.io/hjse9uhdjqd/tr:n-cover/buku/"
 return `<div class="flex"> 
            <img class="shadow-md" src="${coverUrl}${coverImg}">
            <div class="flex-1"> <b><a href="${url}" title="${judul}">${judul}</a></b><dd>${resensi} ...</dd>
            </div>
        </div>`; });

Sebutlah sebagai kode ketiga

Namun kelemahannya adalah harus memasukkan sendiri detil yang ingin ditampilkan di dalam shortcode itu. Sedikit merepotkan tapi terbayar dengan gegasnya saat build.

  1. Karena harus fetch JSON data untuk proses build membutuhkan akses internet, jika tidak ada akses internet maka proses build akan gagal. Namun saat proses di Netlify bukan menjadi masalah.

Dengan mempergunakan paired shortcodes ini, waktu build dipangkas hampir 300% yang awalnya sekitar 9 - 10 detik menjadi 2 - 3 detik saja.

Chart Perbandingan Build Time

🔥 Lume

Untuk pengguna Lume bisa juga mempergunakan fungsi kode diatas. Jika di 11ty dinamakan sebagai shortcodes maka di Lume disebut dengan helper.

Edit file _config.js dan tambahkan kode berikut :

const site = lume();

site.helper("related", async function(judul) {
    try {
     const response   = await fetch('https://kusaeni.com/baca/data.json');
     const data     = await response.json();
     const result   = data.find(function(search) {
    return search.title.toLowerCase() === judul.toLowerCase();
    })
    return `<div class="some">
      <img src="${result.coverImg}"><b>${result.title}</b> ${result.penulis}
            </div>`
    }catch(err) {console.log(err)};

     const printResult = async () => {
     const print = await result;
      console.log(print);
    };
    printResult();}, {type: "tag", async: true})

Sedangkan template tags yang dipergunakan sama yaitu {% related "title" %}. Untuk shortcodes prelated pun sama, ini dikarenakan Lume memang menjadikan 11ty sebagai patokan atau inspirasi.


Berlangganan Artikel

Dapatkan notifikasi saat artikel baru diterbitkan, langsung ke dalam inbox-mu

Saya tidak akan mengirimkan spam dan tidak akan menjual alamat email anda. Jika sudah tidak ingin berlangganan, anda bisa berhenti berlangganan dengan mudah kapan saja.

Masukkan alamat email anda untuk memulai berlangganan artikel.

Layanan ini didukung oleh Buttondown.