Kalian akan memahami loading WASM Oniguruma, memilih engine tokenisasi lewat opsi engine, membedakan highlight di build-time dan client-side, serta menerapkan lazy loading dan caching grammar untuk menjaga performa.

Highlight yang lambat terasa saat build berlangsung lama atau saat halaman client-side berkedut sebelum kode diwarnai. Performa Shiki ditentukan oleh tiga hal: mesin regex yang menokenisasi, tempat highlight dijalankan, dan seberapa banyak hasil yang bisa dipakai ulang.
Episode 13 membahas mekanisme loading WASM Oniguruma, opsi engine dengan package @shikijs/engine-oniguruma, perbedaan strategi SSG dan client-side, serta teknik caching grammar. Tujuannya satu: membuat highlight cepat, ringan, dan tidak menggangu pengalaman pengguna.
TextMate grammar memakai sintaks regex khusus yang tidak didukung penuh oleh regex JavaScript standar. Solusinya adalah Oniguruma, mesin regex asal Ruby yang di-porting ke WebAssembly. Shiki memanggil mesin ini lewat binding WebAssembly untuk mencocokkan aturan grammar baris demi baris.
Konsekuensinya, Shiki butuh file WASM. Sejak v4, kalian mengimpor WASM dari shiki/wasm dan menyuntikkannya ke engine:
import { createHighlighterCore } from "shiki/core";
import { createOnigurumaEngine } from "@shikijs/engine-oniguruma";
const highlighter = await createHighlighterCore({
langs: [],
themes: [],
engine: createOnigurumaEngine(import("shiki/wasm")),
});File WASM berisi seluruh logika regex Oniguruma. Dalam konteks build-time, file ini cukup dimuat sekali, dan bundler akan menyertakannya sebagai aset terpisah.
Selain Oniguruma, Shiki menyediakan engine berbasis JavaScript murni: @shikijs/engine-javascript. Engine ini menerjemahkan aturan grammar menjadi regex JavaScript, sehingga tidak perlu WASM. Trade-off-nya: beberapa pola grammar yang sangat kompleks tidak didukung, dan performa tokenisasi bisa lebih lambat untuk kode besar.
Pilih Oniguruma untuk akurasi maksimal, atau JavaScript engine saat kalian tidak bisa menyediakan WASM sama sekali, misalnya pada environment yang membatasi file eksekusi.
Pada static site generation, pipeline rehype-pretty-code berjalan saat bun run build. Shiki menokenisasi semua kode, dan hasilnya berupa HTML statis berisi span berwarna. Browser tidak perlu memuat WASM atau menjalankan regex sama sekali.
Strategi ini paling cepat di sisi klien. Halaman pertama kali dimuat sudah menampilkan kode dengan warna sempurna, tanpa flicker. Tidak ada evaluasi kode dinamis, yang juga mempermudah penerapan Content-Security-Policy yang ketat.
Konten yang dirender dinamis di browser, misalnya input pengguna atau kode yang di-fetch dari API, baru bisa di-highlight di client. Kalian memuat engine dan grammar di bundle klien:
import { codeToHtml } from "shiki";
const html = await codeToHtml(input, {
lang: "javascript",
theme: "github-dark-default",
});Client-side membutuhkan WASM, grammar, dan theme yang dikirim ke browser. Untuk kode yang pendek ini masih wajar, tetapi hindari untuk seluruh konten artikel karena menambah beban jaringan dan CPU.
Gunakan SSG jika konten statis, seperti blog dan dokumentasi. Gunakan client-side hanya untuk kode yang benar-benar dinamis, dan batasi bahasa yang dikirim ke client. Kombinasi keduanya umum: artikel di-highlight saat build, kode hasil input pengguna di-highlight saat runtime.
Tokenisasi kode yang sama berulang kali membuang waktu. Shiki menyediakan opsi cache pada highlighter untuk menyimpan hasil tokenisasi dan memakai ulang ketika kode serta bahasa yang sama dipanggil lagi:
import { createHighlighterCore } from "shiki/core";
const cache = new Map();
const highlighter = await createHighlighterCore({
langs: ["typescript"],
themes: ["github-dark-default"],
engine: createOnigurumaEngine(import("shiki/wasm")),
cache,
});Cache berupa struktur data yang mengikuti kontrak CacheStorage. Dengan cache, render ulang halaman yang memakai blok kode identik menjadi jauh lebih cepat, terutama di development server yang sering menghidupkan ulang pipeline.
Grammar dan theme sebaiknya dimuat dinamis supaya tidak menjadi satu bundle raksasa:
const highlighter = await createHighlighterCore({
langs: [() => import("@shikijs/langs/typescript")],
themes: [() => import("@shikijs/themes/github-dark-default")],
engine: createOnigurumaEngine(import("shiki/wasm")),
});Impor dinamis memecah masing-masing grammar menjadi chunk terpisah. Di build-time, chunk yang tidak dipakai dihapus bundler, sehingga ruang yang diambil hanya untuk bahasa yang benar-benar muncul di konten.
Ukur waktu build dan ukuran chunk sebelum dan sesudah optimasi. Log hasil bun run build memperlihatkan setiap chunk beserta ukurannya. Targetkan waktu build yang stabil meski jumlah post bertambah, dan pastikan tidak ada chunk grammar yang terbawa ke JavaScript klien.
Inti yang harus dibawa pulang:
@shikijs/engine-oniguruma menangani WASM, @shikijs/engine-javascript menawarkan alternatif tanpa WASM.cache menyimpan hasil tokenisasi untuk dipakai ulang.Di episode 14 kalian akan belajar keamanan dan sanitasi output: mencegah XSS dengan escape konten kode, mewaspadai meta strings yang dikontrol pengguna, menggunakan rehype-sanitize, dan menyusun Content-Security-Policy tanpa inline script.