
Artikel ini sama sekali tidak menyalahkan framework SolidJS. Melainkan secara langsung, menyalahkan penulis sendiri karna salah memprediksi issue teknis di masa depan.
Pada sebuah proyek pribadi ekstensi Chrome, yang belum saya rilis, kita namakan saja “Project FRExtension“. Saya mengalami masalah pelik yang benar-benar tidak terlihat di permukaan tapi berpotensi menjadi bom waktu: Memory Leak.
Isu ini bukanlah masalah error merah di console, melainkan “sampah” memori yang menumpuk diam-diam setiap kali user berinteraksi, yang lama-kelamaan bisa membuat browser user terasa berat atau bahkan crash.
Chrome Garbage Collector: Canggih, Tapi Bukan Pesulap
Chrome sebenarnya memiliki sistem Garbage Collector (GC) bernama Orinoco yang dirancang sangat canggih untuk menanggulangi manajemen memori di setiap tab. Orinoco berjalan parallel, incremental dan juga concurrent agar tidak mengganggu main thread. Baca detailnya disini: (https://v8.dev/blog/trash-talk)
Sistem GC Orinoco di Chrome (yang berjalan di dapur V8 Engine) sebenarnya telah didesain cukup ciamik soro untuk meminimalkan “jank” (patah-patah) pada halaman web. Tapi kecanggihan itu tidak akan ada manfaatnya jika kita ngasal saat implement sesuatu yang langsung berinteraksi dengan DOM, dan tanpa rule yang tidak jelas.
GC bekerja berdasarkan prinsip “Reachability” (Keterjangkauan). Jika kode JavaScript kita (secara tidak sengaja) masih memegang “tali” referensi ke sebuah elemen HTML yang sudah dihapus dari layar, GC akan menganggap elemen itu masih berguna dan menolak membuangnya.
SolidJS: Pedang Bermata Dua
Ekstensi FRProject yang sedang saya kembangkan ini cukup unik. Ia mengharuskan saya melakukan injeksi content_script untuk mendeteksi ribuan elemen DOM, lalu menyuntikkan Shadow DOM sebagai UI overlay. Kompleksitasnya lumayan tinggi menurut saya.
Awalnya, saya setia menggunakan Pure Vanilla JS. Namun, di tengah jalan, saya tergoda melakukan transmigrasi ke SolidJS. Alasannya klise: bujukan AI dan mimpi indah tentang Developer Experience (DX). 😂
“Proyek sekompleks ini akan sulit kamu maintain, kalau kamu nekat full Vanilla JS,” begitulah bisikan manis AI. 💀
Singkat cerita dan banyak retorika, saya memilih jalur trayek SolidJS. Sintaks JSX-nya terlihat sangat friendly untuk membuat DOM, dan reaktivitasnya yang fine-grained menjanjikan performa tinggi. Saya pikir ini solusi sempurna, dimana UI dibangun pakai Solid, orkestrasinya pakai native listener Vanilla. 😁
Memory Leak dan Spaghetti Code: Jebakan “Detached Nodes”
Masalah muncul ketika saya mencoba menggabungkan siklus hidup (lifecycle) SolidJS yang reaktif dengan siklus hidup DOM manual di Chrome Extension.

Ekstensi FRProject ini memiliki fitur “Ghost Icon” yang bersifat ephemeral (muncul saat input difokuskan, hilang saat blur). Di sinilah letak kesalahannya. Saya mencoba mempermudah passing data dari controller ke view SolidJS dengan cara menempelkan fungsi setter Solid langsung ke elemen DOM.
Snippet Biang keladi
Niat hati ingin praktis, kode ini justru menciptakan Circular Reference (Lingkaran Setan) yang membuat GC lumpuh.
Potongan kode culprit:
// ghostController.tsx (Wrapper SolidJS)
// 1. Kita membuat Host Element manual
const newHost = document.createElement("div");
// 2. Kita render komponen SolidJS ke dalam Shadow DOM host tersebut
const App = () => {
// SolidJS Signal dibuat di sini (dalam reactive scope)
const [currentDraft, setDraft] = createSignal(draft);
// 💀 FATAL #1: Menempelkan Setter Solid ke DOM Node
// Tujuannya: Agar controller bisa update data tanpa unmount.
// Realitanya: DOM menahan Referensi ke Scope JavaScript Solid.
newHost._setDraft = setDraft;
return <GhostIconUI draft={currentDraft()} ... />;
};
const dispose = render(App, shadow);
// 💀 FATAL #2: Menempelkan Dispose Function ke DOM Node
// Tujuannya: Agar bisa cleanup nanti saat elemen di-remove.
// Realitanya: Scope JS menahan Referensi ke DOM.
newHost._dispose = dispose;
// Apa yang terjadi disini?:
// DOM -> memegang properti _setDraft -> memegang Scope SolidJS -> memegang Referensi DOM
// Garbage Collector melihat mereka saling pegang, jadi tidak ada yang dibuang. Benar-benar Lingkarang Setan
Saat saya cek di Chrome DevTools > Memory Profiler, saya menemukan puluhan “Detached DOM Nodes”.
Dimana elemen <div class="fr-ghost-host"> yang sudah hilang dari layar, tapi “arwahnya” masih gentayangan memenuhi RAM karena kode JavaScript masih memegang referensinya lewat closure SolidJS tadi.

Kembali ke “Vanilla” Technology
Saya akhirnya menyadari bahwa untuk komponen UI yang sangat volatile (sering muncul/hilang dalam hitungan detik) seperti ikon di ekstensi ini, Reaktivitas adalah beban, bukan aset.
Saya melakukan rewrite total komponen Ghost Icon kembali ke Pure Vanilla JS. Kuncinya adalah memutus hubungan kepemilikan antara DOM dan Data.
Vanilla and WeakMap is the Bos
Solusi ajaibnya adalah WeakMap. Struktur data ini memungkinkan kita mengasosiasikan data dengan objek DOM tanpa mencegah objek tersebut dibersihkan oleh GC.
Kode refactor ke Vanilla:
// ghostControllerVanilla.ts
// WeakMap: Jika elemen 'target' (input) dihapus oleh website host,
// entry ini otomatis HILANG dari memori. Magic!
const ghostRegistry = new WeakMap<HTMLElement, { host: HTMLElement, cleanup: () => void }>();
export const showGhostIcon = (target: HTMLElement, draft: any) => {
// Cek registry, cegah duplikasi tanpa menempel properti aneh ke DOM
if (ghostRegistry.has(target)) return;
// 1. Create Element (Pure DOM)
const host = document.createElement("div");
host.className = "fr-ghost-host";
// ... setup styling & content manual ...
// 2. Setup Listener (Named function agar bersih)
const onClick = (e) => { /* logic */ };
host.addEventListener('click', onClick);
// 3. Simpan state di WeakMap, BUKAN di properti elemen
ghostRegistry.set(target, {
host: host,
cleanup: () => {
// Pembersihan manual yang eksplisit & pasti
host.removeEventListener('click', onClick);
host.remove(); // Hapus dari DOM
// Tidak ada reactive graph yang tertinggal!
}
});
document.body.appendChild(host);
};
Kesimpulan Mahal
Pelajaran mahal yang saya dapat: Framework modern seperti SolidJS, React, atau Vue sangat hebat untuk membangun Single Page Application (SPA) yang kompleks dan persisten.
Namun, untuk widget kecil yang hidup menumpang di halaman orang lain (seperti ekstensi browser), Vanilla JS seringkali adalah pilihan yang lebih superior dari segi performa dan manajemen memori.
Terkadang, cara terbaik untuk membersihkan sampah adalah dengan tidak membuatnya sejak awal. Selamat Tahun Baru 2026 & Happy Coding!
Tinggalkan Balasan