- Menjelaskan secara rinci proses membuat aplikasi web yang berjalan di browser dengan mem-porting kode C/C++ ke WebAssembly menggunakan Emscripten, berdasarkan contoh nyata solver Rubik’s Cube
- Membahas secara bertahap kendala konkret dan cara mengatasinya yang ditemui di lingkungan browser/WebAssembly, mulai dari Hello World, multithreading, callback, penyimpanan persisten, hingga modularisasi
- Berfokus pada troubleshooting praktis seperti inisialisasi asinkron JavaScript, ekspor fungsi, Web Worker dan isu Spectre, serta penyimpanan persisten IndexedDB melalui IDBFS
- Berulang kali menekankan bahwa abstraksi Emscripten dalam praktiknya sering 'bocor' (leaky abstractions), sehingga penting memahami batasan dan struktur internal platform web
- Merupakan panduan berbasis pengalaman yang memberikan bantuan praktis dan kiat nyata bagi pengembang yang ingin mem-porting pustaka C/C++ yang sudah ada ke web, melalui pengalaman langsung memindahkan codebase C yang kompleks ke web hanya dengan pengetahuan JavaScript/HTML frontend yang minimal
Pendahuluan
- Baru-baru ini dikerjakan sebuah proyek untuk mengimplementasikan algoritme solusi optimal Rubik’s Cube sebagai aplikasi web
- Mencatat proses mengompilasi solver optimasi Rubik’s Cube yang dikembangkan dalam C dengan Emscripten agar berjalan sebagai WebAssembly di browser web
- Alasan utama memakai WebAssembly adalah karena ia memungkinkan performa yang nyaris mendekati native dibandingkan JavaScript di web
- Tulisan ini bukan tutorial pengembangan web tradisional, melainkan sebuah ‘perjalanan penuh derita’ bagi pengembang yang ingin mem-porting kode C/C++ yang sudah ada ke web
- Meski pengalaman pengembangan web tidak banyak, tulisan ini tetap bisa diikuti selama memahami struktur dasar HTML, JavaScript, dan cara menggunakan developer tools di browser
Menyiapkan Lingkungan
- Semua contoh kode dapat dilihat di repositori git dan GitHub
- Perlu memasang Emscripten (lihat situs resmi untuk cara instalasi), dan bisa memakai server web seperti darkhttpd atau Python
http.server
- Contoh kode tutorial diuji di lingkungan Linux dan keluarga UNIX. Untuk pengguna Windows, direkomendasikan WSL (Windows Subsystem for Linux)
Hello World
- Jika kode Hello World dalam C dikompilasi dengan perintah
emcc -o index.html hello.c, akan dihasilkan tiga berkas: index.html (halaman web), index.wasm (bytecode WebAssembly), dan index.js (JavaScript glue code)
- Dapat dijalankan di browser maupun Node.js, dan masing-masing lingkungan punya cara pemanfaatan yang berbeda
- Jika hanya ingin menghasilkan
.wasm, gunakan opsi -sSTANDALONE_WASM
- Emscripten memang bisa menghasilkan hanya
.wasm, tetapi dalam banyak kasus JavaScript glue code tetap diperlukan
Intermezzo I: Apa itu WebAssembly?
- WebAssembly (WASM) adalah bahasa tingkat rendah yang berjalan di atas mesin virtual berperforma tinggi di dalam browser web
- WASM didukung oleh semua browser utama sejak 2017
- Awalnya Emscripten mengubah kode C/C++ menjadi subset JavaScript bernama asm.js, tetapi beralih setelah hadirnya WASM
- WASM juga memiliki representasi tekstual dan memakai struktur berbasis stack. Hingga baru-baru ini hanya mendukung arsitektur 32-bit sehingga tidak bisa memakai memori di atas 4GB, namun WASM64 kini mulai diadopsi secara bertahap oleh browser
Membangun Library
- Menunjukkan contoh dasar membangun fungsi C multiply() menjadi WASM lalu memanggilnya dari JavaScript
- Pada build default, Emscripten menambahkan underscore (_) di depan nama fungsi (misalnya
_multiply)
- Agar fungsi diekspos ke luar, perlu menentukan opsi -sEXPORTED_FUNCTIONS
- Karena inisialisasi saat memuat library berlangsung asinkron, perlu penanganan async seperti onRuntimeInitialized atau
await
- Kode latihan ada di folder repositori
01_library
Intermezzo II: JavaScript dan DOM
- Untuk mengakses dan memodifikasi elemen HTML dari JavaScript, perlu menggunakan Document Object Model (DOM)
- UI dinamis dapat dibangun dengan event listener (addEventListener), operator/fungsi bawaan, dan sebagainya
- Menjelaskan struktur dasar integrasi HTML/JavaScript untuk contoh yang memiliki input, tombol, dan area hasil
- Juga membahas cara praktis memisahkan/menggabungkan script serta isu terkait (misalnya penggunaan
defer, urutan pemuatan elemen DOM)
Modularisasi dan Pemuatan Library
- Agar library WASM bisa disertakan berkali-kali atau dipakai ulang di Node.js dan web, library dapat dibangun dalam bentuk modul dengan opsi MODULARIZE dan EXPORT_NAME
- Ekstensi
.mjs (modul ES6) direkomendasikan demi kompatibilitas Node.js
- Modul dapat dipakai baik di web maupun Node dengan gaya import MyLibrary from ...
Multithreading
- Di WebAssembly, kode multithread berbasis pthreads dapat dipindahkan untuk meningkatkan performa
- Di dalam fungsi dibuat banyak thread untuk menjalankan pekerjaan komputasi secara paralel (misalnya menghitung jumlah bilangan prima)
- Saat build diperlukan opsi -pthread dan -sPTHREAD_POOL_SIZE=
- Di browser sungguhan, perlu menambahkan header HTTP seperti Cross-Origin-Opener-Policy: same-origin dan Cross-Origin-Embedder-Policy: require-corp
- Semua contoh dapat dilihat di folder repositori
03_threads
Intermezzo III: Web Workers dan Spectre
- Multithreading Emscripten diimplementasikan dengan Web Workers (Web Workers adalah proses terpisah dengan struktur komunikasi berbasis pesan)
- Penggunaan memori bersama (SharedArrayBuffer) memiliki pembatasan keamanan
- Setelah munculnya kerentanan Spectre pada 2018, persyaratan cross-origin isolated beserta header terkait menjadi wajib
Waspadai Blocking pada Main Thread
- Jika pekerjaan panjang memblokir main UI thread di browser, pengalaman pengguna akan menurun drastis
- Untuk menghindarinya, digunakan web worker agar penanganan UI/input dan pemrosesan komputasi terpisah dengan jelas
- Komunikasi berbasis event antara main thread dan worker diimplementasikan dengan postMessage dan onmessage
- Di dalam web worker, modul Emscripten-WASM dimuat untuk menangani komputasi asinkron saja
Fungsi Callback
- Saat pointer fungsi (callback) diteruskan sebagai parameter ke fungsi C, objek fungsi JavaScript tidak dapat terhubung otomatis
- Perlu memakai addFunction(), UTF8ToString() yang disediakan Emscripten, dan saat build harus menambahkan opsi -sEXPORTED_RUNTIME_METHODS serta -sALLOW_TABLE_GROWTH
- Callback harus dipanggil hanya dari main thread agar bekerja stabil (tidak bisa diakses dari web worker)
Penyimpanan Persisten
- Untuk menyimpan data secara permanen di browser pengguna, digunakan IDBFS (sistem berkas berbasis IndexedDB) milik Emscripten
- Saat build diperlukan flag --lidbfs.js dan pengaturan awal seperti **--pre-js **
- Di kode C, fungsi I/O berkas seperti (
fopen, fread, fwrite) bisa tetap digunakan, tetapi agar data benar-benar diterapkan/disinkronkan, tetap diperlukan mapping dan sinkronisasi eksplisit di sisi JavaScript
- Karena karakteristik sandbox/kebijakan keamanan browser, akses langsung ke sistem berkas lokal hanya dimungkinkan di Node.js; di browser, penyimpanan data permanen yang aman harus memakai backend seperti IDBFS
Kesimpulan
- Melalui seluruh rangkaian tutorial ini, pembaca bisa mempelajari secara rinci cara praktis menjalankan kode native C/C++ yang kompleks di browser dengan aman dan tanpa penurunan performa hanya dengan JavaScript dan HTML seminimal mungkin
- Di lingkungan nyata, pembaca dapat mengalami langsung hambatan dan solusi di semua jalur penting seperti multithreading, callback, pemrosesan asinkron, dan integrasi storage, sekaligus memahami konfigurasi terkait dan batasan browser dalam tren terbaru
- Contoh-contoh di repositori Git yang disediakan bisa dijadikan acuan untuk diterapkan dan diperluas ke proyek sendiri
1 komentar
Komentar Hacker News
.jske.mjs, sekaligus rasa empati yang sangat terasa terhadap kenyataan bahwa apa pun ekstensi yang dipakai, tetap saja akan menabrak masalah; dari sudut pandang seseorang yang sudah memakai berbagai sistem modul mulai dari dojo, CommonJS, AMD, ESM, webpack, esbuild, rollup, dan lain-lain, rasanya benar-benar setuju 100% dengan pernyataan iniversionsnpm karena kemungkinan besar itulah versi commonjs terakhir. Jelas esm bisa disebut sistem modul yang lebih maju, tetapi bagian yang dibuat tc39 nyaris seperti sengaja tidak kompatibel dengan commonjs, seperti top-level await, benar-benar sulit dipahamiimport, ini sangat berguna sebagai semacam penyelamat. Mungkin di ekosistem JS hal ini tidak terlalu diperlukan, tetapi bagi saya sangat membantu.esm.jsjuga bisa dipakailetatauconstalih-alih kata kuncivar;varmemang masih berfungsi, tetapi kebanyakan developer JS sekarang memakai linter untuk melarang penggunaannya.varhanya mendukung function scope, sehingga ini menjadi titik yang pada akhirnya membingungkan banyak developer dari bahasa lain. Untuk isu porting aplikasi native, disebut contoh hardcode salin/tempel saat compile time dengan Ctrl-C, Ctrl-V sehingga berjalan di Linux dan Windows tetapi tidak berfungsi di Mac; di web hal seperti ini harus ditangani dengan mendeteksi event copy dan paste, dan bahkan framework seperti Unity pun pernah terlihat tidak bisa copy-paste di Mac karena tombolnya di-hardcode. Di kebanyakan game ini tidak diperlukan, tetapi saat fitur yang butuh copy-paste dibawa ke web, ini hampir pasti jadi masalahhttpddanrelaydatau bahkan memindahkannya ke domain terpisah