23 poin oleh GN⁺ 2025-06-08 | 1 komentar | Bagikan ke WhatsApp
  • 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

 
GN⁺ 2025-06-08
Komentar Hacker News
  • Harapan agar orang-orang memperhatikan bahwa ekstensinya diubah dari .js ke .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 ini
    • Peralihan dari commonjs ke esm terasa seperti transisi dari python2 ke python3, perubahan yang sangat besar, tetapi dibanding ekspektasinya manfaat yang didapat terasa kecil dan kerepotannya justru bertambah; sekarang makin banyak library yang hanya mendukung esm, jadi belakangan ini kenyataannya orang memilih versi yang paling banyak diunduh dalam sebulan terakhir di tab versions npm 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 dipahami
    • Rasanya sejarah modul di js itu benar-benar sudah mendekati trauma; sekarang import maps bahkan sudah masuk ke browser, jadi penasaran masalah menarik(?) apa lagi yang akan muncul ke depannya
    • Belakangan ini baru tahu bahwa objek Function bisa mengompilasi sembarang kode JS saat runtime, jadi di lingkungan saya yang bahkan tidak bisa memakai import, ini sangat berguna sebagai semacam penyelamat. Mungkin di ekosistem JS hal ini tidak terlalu diperlukan, tetapi bagi saya sangat membantu
    • Karena itu, semua orang seharusnya memakai bun.sh
    • Pertanyaan apakah .esm.js juga bisa dipakai
  • Kalau mau menunjuk bagian lain dari tulisan ini yang bisa menimbulkan masalah jangka panjang, disarankan memakai let atau const alih-alih kata kunci var; var memang masih berfungsi, tetapi kebanyakan developer JS sekarang memakai linter untuk melarang penggunaannya. var hanya 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 masalah
  • Keluhan bahwa multithreading di web/NodeJS sangat tidak disukai; sangat disayangkan karena alih-alih membuat nilai itu sendiri bisa ditransfer antarkonteks, misalnya antar v8 isolates, dengan primitive sinkronisasi seperti mutex atau rwlock, yang diperkenalkan justru SharedArrayBuffer yang pada praktiknya hampir tidak berguna. Sinkronisasi antarthread pada akhirnya jadi struktur yang memaksa thunking dan penyalinan data lewat lapisan RPC. Aplikasi production perusahaan kami adalah aplikasi besar yang memakai RAM 70~100GB, memang sudah begitu sejak sebelum saya membuatnya, jadi kami sedang mencari solusi aneh berupa pengelolaan langsung memory page dan struktur data kustom berbasis native code sambil meminimalkan serialisasi/deserialisasi; v8 memakai utf16 untuk encoding string sehingga menangani nilai JS dari lapisan native jadi mahal
    • Timbul pertanyaan apakah aplikasi yang memakai RAM 100GB ini benar-benar harus berupa web app; terdengar seperti hal yang seharusnya menjadi tool internal yang ditulis dalam bahasa seperti C#
  • Ekosistem ini begitu dekat dengan kekacauan sampai-sampai sebutan 'masokis' justru terasa lebih masuk akal
    • Satu komentar bahwa kekacauan itu memang sudah tersirat sejak awal
  • Tulisan ini sendiri ditulis dengan baik, dan lebih lagi mengejutkan karena memilih memulai dari jalur yang sulit dan kompleks; terasa sekali bahwa bagian tersulit adalah setup proyek. Dipuji karena langsung menabrak masalah keamanan/header, meski ada pendapat bahwa sering kali masalah yang diperkirakan adalah CORS; di perusahaan kami juga sedang build dengan emscripten/C++ dan bahkan akan menambah WebGPU/shader serta WebAudio, jadi perjalanan ke depan tampaknya akan jauh lebih berat
  • Dulu ada anggapan samar bahwa kompilasi kode di browser pasti 'lambat', tetapi OP menjelaskan dengan baik bahwa tidak demikian. Proyek Emscripten juga menekankan bahwa "berkat kombinasi LLVM, Emscripten, Binaryen, dan WebAssembly, output yang dihasilkan berukuran kecil dan berjalan hampir secepat native" (emscripten.org)
    • Hari ini terasa seperti mengalami 'sindrom bus kuning'; sampai minggu lalu saya bahkan belum tahu Emscripten, lalu saat memasang SDL ke proyek saya menemukan komentar target APPLE, MSVC, EMSCRIPTEN di CMake, dan tepat hari ini saya melihat Emscripten lagi di hn, jadi sekarang rasanya sudah waktunya benar-benar meluangkan waktu untuk mendalaminya
    • Muncul pertanyaan apakah ungkapan "hampir secepat native" itu sangat subjektif, karena tidak menemukan data angka nyata di dokumentasinya tentang seberapa cepat sebenarnya
  • Tulisan ini bermanfaat, dan penulis komentar juga punya rencana mengompilasi compiler yang ditulis dalam C ke WebAssembly untuk dijadikan web playground; sebagai referensi, browser modern sekarang bisa memakai SQLite lewat JavaScript, dan muncul rasa penasaran apakah ini juga memungkinkan di wasm. Jika emscripten bisa menjembatani pemanggilan API sqlite dari kode C ke database sqlite browser, itu akan sangat ideal dan layak ditelusuri lebih lanjut
  • Muncul pertanyaan mengapa SSL memakai port 48, apakah ada alasan khusus
    • Jawaban bahwa port itu dipilih secara acak dari nama H48; web app ini memerlukan header HTTP tambahan, jadi dipakai port lain agar bisa diimplementasikan tanpa memengaruhi seluruh situs. Juga dijelaskan bahwa ini diarahkan ulang ke https://h48.tronto.net, dan ke depannya sedang dipikirkan untuk lebih memperbaiki konfigurasi OpenBSD httpd dan relayd atau bahkan memindahkannya ke domain terpisah