Kasus untuk Virtual File System di Node.js
Node.js terikat erat dengan filesystem asli. Lapisan virtual file system bisa membuka jalan untuk testing, sandboxing, dan deployment di edge.

Coba jalankan aplikasi Node.js tanpa filesystem asli dan lihat betapa cepatnya semuanya runtuh. require() membaca file. fs.readFile() membaca file. Template engine membaca file. Config loader membaca file. Library logging menulis file. Seluruh ekosistem Node.js mengasumsikan bahwa filesystem bergaya POSIX ada, bisa ditulis, dan merupakan cara utama untuk mengakses kode dan data.
Ini masih oke di tahun 2010, saat Node.js berjalan di server dengan disk lokal. Sekarang makin bermasalah karena kita ingin menjalankan JavaScript di tempat yang tidak punya filesystem, hanya bisa dibaca, atau tidak boleh dipercaya: edge runtime, sandbox WebAssembly, serverless function, dan lingkungan pengembangan berbasis browser.
Di Mana Asumsi Filesystem Mulai Gagal
Edge runtime. Cloudflare Workers, Deno Deploy, dan Vercel Edge Functions menjalankan JavaScript di edge — di mesin yang dekat dengan pengguna, dengan infrastruktur minimal. Lingkungan ini sering tidak menyediakan filesystem yang bisa ditulis, atau hanya menyediakan filesystem in-memory terbatas yang tidak bertahan antar-request. Modul Node.js yang memanggil fs.writeFileSync akan crash. Modul yang memakai fs.existsSync untuk mengecek file konfigurasi berperilaku tidak terduga.
WebAssembly. Menjalankan Node.js di dalam sandbox WebAssembly mengharuskan operasi filesystem dipetakan ke virtual filesystem runtime Wasm (biasanya WASI). Pemetaan ini belum sempurna — permission file, symlink, dan path khusus platform tidak bisa diterjemahkan dengan rapi. Abstraksi VFS akan membuat pemetaan ini eksplisit, bukan dilakukan secara ad hoc.
Testing. Unit testing untuk kode yang membaca file konfigurasi, memuat template, atau menulis log harus memakai mocking modul fs (rapuh, karena tiap library memakai API fs yang berbeda) atau membuat direktori sementara berisi fixture (lambat, flaky, dan meninggalkan sampah di disk). Virtual filesystem memungkinkan tes berjalan sepenuhnya di memori, deterministik, tanpa menyentuh filesystem asli.
Security sandboxing. Saat menjalankan kode yang tidak dipercaya — plugin, skrip pengguna, langkah build CI/CD — kita ingin mengontrol akses filesystem. Saat ini, hal ini membutuhkan sandboxing level OS (container, namespace) atau monkey-patching modul fs. VFS memungkinkan kebijakan filesystem yang granular: kode ini boleh membaca /app tapi tidak /etc, boleh menulis ke /tmp tapi tidak ke /app.
Seperti Apa Bentuk VFS
Virtual file system bukan konsep baru. Sistem operasi sudah memakai lapisan VFS sejak 1980-an — VFS di Linux memungkinkan ext4, NFS, dan procfs hidup berdampingan di balik API yang sama. Idenya untuk Node.js sama: memisahkan antarmuka filesystem (fs.readFile, require(), dan sebagainya) dari implementasi filesystemnya.
// Hypothetical VFS API
import { createVFS, MemoryFS, ReadOnlyFS, OverlayFS } from 'node:vfs';
// In-memory filesystem for testing
const testFS = new MemoryFS({
'/app/config.json': '{"port": 3000}',
'/app/templates/index.html': '<h1>Hello</h1>',
});
// Read-only view of the real filesystem
const readOnly = new ReadOnlyFS('/');
// Overlay: reads from real FS, writes go to memory
const sandbox = new OverlayFS(readOnly, new MemoryFS());
// Run code with a specific filesystem
const vfs = createVFS(sandbox);
vfs.run(() => {
// Inside this context:
// fs.readFileSync('/etc/hosts') → reads real file
// fs.writeFileSync('/tmp/log.txt') → writes to memory overlay
// require('./module') → resolves against the VFS
const app = require('./app');
app.start();
});
Wawasan kuncinya adalah VFS mencegat semua akses filesystem — bukan hanya pemanggilan fs yang eksplisit, tetapi juga require(), import(), __dirname, dan process.cwd(). Inilah yang membuatnya berguna: kita bisa mengarahkan seluruh akses filesystem aplikasi tanpa mengubah aplikasinya.
Masalah Resolusi Modul
Keterikatan terdalam antara Node.js dan filesystem ada pada resolusi modul. Saat kamu menulis require('express'), Node.js menelusuri pohon direktori ke atas untuk mencari direktori node_modules/express, membaca file package.json, me-resolve symlink, dan mengikuti field main atau exports. Proses ini melakukan puluhan panggilan filesystem hanya untuk satu require.
Yarn PnP (Plug'n'Play) sebagian sudah menyelesaikan ini dengan mengganti node_modules dengan file manifest yang memetakan nama modul ke arsip zip. Ini lebih cepat (tanpa penelusuran direktori) dan lebih deterministik (tanpa kejutan hoisting), tetapi harus melakukan monkey-patching terhadap resolusi modul Node, yang merusak tools yang berasumsi bahwa node_modules ada.
VFS yang layak akan menjadikan pendekatan Yarn PnP sebagai fitur kelas satu. Resolusi modul akan melewati VFS, yang bisa mengimplementasikan pemetaan apa pun — arsip zip, modul in-memory, URL remote, atau penelusuran direktori node_modules tradisional. Strategi berbeda untuk lingkungan berbeda, dengan API yang sama untuk kode aplikasi.
Karya Terdahulu
Runtime lain sudah memecahkan variasi masalah ini.
- Deno memuat modul dari URL secara default dan menyimpannya di cache direktori terkelola. Tidak ada
node_modulesdan tidak ada resolusi modul berbasis filesystem. Runtime yang mengontrol di mana dan bagaimana modul disimpan. - Bun memakai cache modul global dengan hardlink, sehingga mengurangi overhead filesystem. Resolusi modulnya sangat dioptimalkan dibanding milik Node.js.
- Interface
io/fsdi Go (ditambahkan di Go 1.16) mendefinisikan antarmuka filesystem yang diimplementasikan oleh filesystem OS, file yang di-embed (embed.FS), arsip zip, dan filesystem in-memory. Fungsi standard library menerima interfacefs.FS, sehingga mudah diuji tanpa file asli. - Abstraksi NIO
FileSystemdi Java mendukung provider filesystem yang bisa dipasang, termasuk implementasi in-memory untuk testing dan filesystem berbasis zip. - Pola
IFileSystemdi .NET banyak dipakai di aplikasi .NET untuk testability, meski lebih merupakan konvensi komunitas ketimbang fitur runtime.
Pendekatan Go sangat mendidik. Dengan mendefinisikan interface kecil (Open, Read, Stat) dan menggunakannya di seluruh standard library, Go membuat abstraksi filesystem menjadi sangat mudah tanpa merusak kode yang sudah ada. Node.js bisa melakukan hal serupa dengan mendefinisikan interface VFS lalu secara bertahap memigrasikan standard library dan module loader untuk memakainya.
Tantangan Kompatibilitas
Hambatan terbesar untuk VFS di Node.js bukan implementasinya — melainkan ekosistemnya. npm punya lebih dari dua juta paket, dan sebagian besar dari paket itu mengakses filesystem dengan cara yang mengasumsikan semantik POSIX, path asli, dan direktori yang bisa ditulis.
VFS yang merusak paket yang sudah ada akan langsung gagal. Ia harus bersifat opt-in dan kompatibel ke belakang: jika kamu tidak membuat VFS secara eksplisit, semuanya berjalan persis seperti sekarang. Saat kamu membuat VFS, ia harus mencegat pemanggilan filesystem secara transparan sehingga paket yang baik bisa berjalan tanpa modifikasi.
Kata 'baik' adalah kualifikasi kuncinya. Paket yang menjalankan cp atau rm lewat shell, memakai native addon yang memanggil open() libc secara langsung, atau bergantung pada /proc atau filesystem khusus OS lainnya tidak akan berjalan dengan VFS. Ini batas yang keras — setiap abstraksi bocor ketika kode melampaui batasnya dan menyentuh sistem di bawahnya.
Apa yang Sebenarnya Diusulkan
Diskusi VFS untuk Node.js bukan sekadar teori — ada proposal konkret di issue tracker Node.js. Arah saat ini melibatkan beberapa ide kunci.
Pertama, interface FileSystemProvider yang didelegasikan oleh modul fs. Provider default adalah filesystem asli. Provider kustom mengimplementasikan interface yang sama untuk filesystem in-memory, read-only, overlay, atau remote.
Kedua, integrasi dengan module loader. require() dan import() akan me-resolve modul lewat VFS, memungkinkan strategi resolusi modul yang tidak bergantung pada direktori node_modules.
Ketiga, lapisan kebijakan. VFS bisa menerapkan kontrol akses — mencegah kode membaca atau menulis path di luar cakupan yang ditentukan. Ini akan memberi Node.js kemampuan sandboxing bawaan yang mirip dengan flag --allow-read dan --allow-write milik Deno.
Apakah ini akan hadir di Node.js 24, 26, atau sama sekali tergantung pada kapasitas tim Node.js dan seberapa besar komunitas siap menerima perubahan yang breaking dalam cara akses filesystem bekerja. Tapi tekanannya nyata: edge runtime makin berkembang, target deployment WebAssembly makin banyak, dan jarak antara 'JavaScript di mana-mana' dengan 'Node.js khususnya di server dengan filesystem asli' mulai menjadi kelemahan kompetitif.
Yang Bisa Kamu Lakukan Sekarang
Kamu tidak perlu menunggu VFS di runtime untuk mendapat manfaat dari abstraksi filesystem. Untuk kode baru, bungkus akses filesystem di balik sebuah interface. Alih-alih memanggil fs.readFile secara langsung, buat interface FileStore yang menjadi dependensi kodemu. Implementasikan dengan filesystem asli untuk produksi dan map in-memory untuk tes. Ini adalah dependency inversion standar — tidak keren, tapi efektif dan kompatibel dengan runtime apa pun.
Untuk kode yang sudah ada, library seperti memfs dan unionfs menyediakan implementasi filesystem in-memory dan overlay yang mem-patch modul fs Node. Tidak sempurna — native addon dan child process akan melewatinya — tapi tetap bekerja untuk kode JavaScript murni dan membuat testing jauh lebih mudah.
Filesystem adalah potongan infrastruktur Node.js yang besar terakhir yang belum punya batas abstraksi yang bersih. Stream punya interface. HTTP punya interface. Bahkan module loader punya hook. Saat filesystem mendapat perlakuan yang sama, Node.js akan menjadi runtime yang benar-benar portable, bukan runtime yang hanya cocok untuk server. Itu perubahan yang layak diperjuangkan.


