layout: content/post.njk title: '11ty: Related Books' ringkasan: 'Shortcode untuk menampilkan related books dengan memanfaatkan JSON data' date: 2021-05-02 favorit: true update: true tags: jurnal kategori: jurnal code: true keywords: 'eleventy, 11ty, json, global data, tutorial, shortcode'
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.
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.
{% raw %}
---
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 %}]
{% endraw %}
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.
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.
{% raw %}
{% related "judul" %}
{% endraw %}
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
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
.
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.
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 meng*filter*nya berdasarkan query judul yang dimasukkan. Hasilnya bisa diliat di log di konsol.
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 *shortcodes*nya 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
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.
{% raw %}
{% prelated "11ty Reader Bar", "/jurnal/11tyReaderBar" %}
11ty Reader Bar : sebuah plugin shortcodes untuk menampilkan readerbar di eleventy
{% endprelated %}
{% endraw %}
Dengan catatan :
judul
,url
,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.
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 :
lowerCase
semua. Namun hal ini tidak menjadi solusi jika penulisan judulnya salah karena salah ketik atau salah spasi,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. {#build}
{% endraw %}Saya melakukan DEBUG build
eleventy dengan hasil sebagai berikut:
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.
build
files tertentu saja atau files terupdate saja tentu akan memangkas waktu untuk build
secara signifikan.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
.
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.
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.