— sekitar 8 menit membaca

11ty: Related Books

Shortcode untuk menampilkan related books dengan memanfaatkan JSON data

Artikel ini sudah berusia lebih dari 2 tahun, besar kemungkinan tidak akan bisa dipakai di versi Eleventy maupun Lume terbaru.

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/kusaeni/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/kusaeni/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) {});
  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"));
  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 #1 #

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();
});

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();
});

Update #2 #

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/kusaeni/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>`;
});

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 },
);

Update #

Saya sudah tidak pakai kode diatas untuk menampilkan relasi artikel, tapi mempergunakan metode shortcode karena lebih mudah dan cepat saat build.

site.helper(
  "relasi",
  function (desc, coverImg, title, penulis, url) {
    const coverUrl = "https://ik.imagekit.io/kusaeni/tr:n-cover/buku/";
    return `<div class="relasi m-auto">
            <img class="relaimg" src="${coverUrl}${coverImg}">
            <div class="relasi_meta">
            <div class="juduldkk">
            <h4><a class="hRelasi" href="${url}">${title}</a></h4>
            <p class="author">${penulis}</p>
            </div>
            <p>${desc}</p>
            </div>
        </div>`;
  },
  {
    body: !!"true",
    type: "tag",
  },
);

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



Artikel terkait #11ty

11ty: Reader Bar

Menambahkan Reader Bar sebagai indikator halaman dan bagaimana cara memodif ikasinya

Draft artikel di Eleventy

11ty tidak memiliki fungsi draft built in, namun dengan cara ini memungkinkan fungsi itu tersedia


kembali ke atas