Dipublikasikan: 31 Januari 2025
Bayangkan menjalankan blog yang berfungsi penuh di browser Anda—bukan hanya frontend, tetapi juga backend. Tidak ada server atau cloud yang terlibat—hanya Anda, browser Anda, dan… WebAssembly. Dengan mengizinkan framework sisi server berjalan secara lokal, WebAssembly mengaburkan batas pengembangan web klasik dan membuka kemungkinan baru yang menarik. Dalam postingan ini, Vladimir Dementyev (Kepala Backend di Evil Martians) membagikan progres dalam membuat Ruby on Rails siap untuk Wasm dan browser:
- Cara menghadirkan Rails ke browser dalam 15 menit.
- Di balik layar wasmifikasi Rails.
- Masa depan Rails dan Wasm.
"Membuat blog dalam 15 menit" yang terkenal di Ruby on Rails kini dapat dijalankan langsung di browser Anda
Ruby on Rails adalah framework web yang berfokus pada produktivitas developer dan pengiriman hal-hal dengan cepat. Ini adalah teknologi yang digunakan oleh para pemimpin industri seperti GitHub dan Shopify. Popularitas framework ini dimulai beberapa tahun yang lalu dengan rilis video "How to build a blog in 15 minutes" yang terkenal, yang dipublikasikan oleh David Heinemeier Hansson (atau DHH). Pada tahun 2005, tidak mungkin membangun aplikasi web yang berfungsi sepenuhnya dalam waktu yang begitu singkat. Rasanya seperti sihir!
Hari ini, saya ingin menghadirkan kembali perasaan ajaib ini dengan membuat aplikasi Rails yang berjalan sepenuhnya di browser Anda. Perjalanan Anda dimulai dengan membuat aplikasi Rails dasar dengan cara biasa, lalu mengemasnya untuk Wasm.
Latar belakang: "membuat blog dalam 15 menit" di command line
Dengan asumsi bahwa Anda telah menginstal Ruby dan Ruby on Rails di komputer, Anda akan memulai dengan membuat aplikasi Ruby on Rails baru dan membuat scaffolding beberapa fungsi (seperti dalam video asli "blog dalam 15 menit"):
$ rails new --css=tailwind web_dev_blog
create .ruby-version
...
$ cd web_dev_blog
$ bin/rails generate scaffold Post title:string date:date body:text
create db/migrate/20241217183624_create_posts.rb
create app/models/post.rb
...
$ bin/rails db:migrate
== 20241217183624 CreatePosts: migrating ====================
-- create_table(:posts)
-> 0.0017s
== 20241217183624 CreatePosts: migrated (0.0018s) ===========
Tanpa menyentuh codebase, Anda kini dapat menjalankan aplikasi dan melihat cara kerjanya:
$ bin/dev
=> Booting Puma
=> Rails 8.0.1 application starting in development
...
* Listening on http://127.0.0.1:3000
Sekarang, Anda dapat membuka blog Anda di http://localhost:3000/posts dan mulai menulis postingan.
Anda memiliki aplikasi blog yang sangat sederhana, tetapi fungsional, yang dibuat dalam hitungan menit. Ini adalah aplikasi yang dikontrol server dan full stack: Anda memiliki database (SQLite) untuk menyimpan data, server web untuk menangani permintaan HTTP (Puma), dan program Ruby untuk menyimpan logika bisnis, menyediakan UI, dan memproses interaksi pengguna. Terakhir, ada lapisan tipis JavaScript (Turbo) untuk menyederhanakan pengalaman penjelajahan.
Demo Rails resmi berlanjut ke arah deployment aplikasi ini ke server bare metal, sehingga siap untuk produksi. Perjalanan Anda akan berlanjut ke arah yang berlawanan: alih-alih menempatkan aplikasi di tempat yang jauh, Anda akan "men-deploy"-nya secara lokal.
Tingkat berikutnya: "membuat blog dalam 15 menit" di Wasm
Sejak penambahan WebAssembly, browser tidak hanya dapat menjalankan kode JavaScript, tetapi juga kode apa pun yang dapat dikompilasi menjadi Wasm. Dan Ruby bukan pengecualian. Tentu saja, Rails lebih dari sekadar Ruby, tetapi sebelum membahas perbedaannya, mari kita lanjutkan demo dan wasmify (kata kerja yang diciptakan oleh library wasmify-rails) aplikasi Rails.
Anda hanya perlu menjalankan beberapa perintah untuk mengompilasi aplikasi blog menjadi modul Wasm dan menjalankannya di browser.
Pertama, Anda menginstal library wasmify-rails menggunakan Bundler (npm
Ruby)
dan menjalankan generatornya menggunakan Rails CLI:
$ bundle add wasmify-rails
$ bin/rails wasmify:install
create config/wasmify.yml
create config/environments/wasm.rb
...
info ✅ The application is prepared for Wasm-ificaiton!
Perintah wasmify:rails
mengonfigurasi lingkungan eksekusi "wasm" khusus (selain lingkungan "pengembangan", "pengujian", dan "produksi"
default) dan menginstal dependensi yang diperlukan. Untuk aplikasi Rails
greenfield, ini cukup untuk membuatnya siap Wasm.
Selanjutnya, build modul Wasm inti yang berisi runtime Ruby, library standar, dan semua dependensi aplikasi:
$ bin/rails wasmify:build
==> RubyWasm::BuildSource(3.3) -- Building
...
==> RubyWasm::CrossRubyProduct(ruby-3.3-wasm32-unknown-wasip1-full-4aaed4fbda7afe0bdf4e22167afd101e) -- done in 47.37s
INFO: Packaging gem: rake-13.2.1
...
INFO: Packaging gem: wasmify-rails-0.2.0
INFO: Packaging setup.rb: bundle/setup.rb
INFO: Size: 73.77 MB
Langkah ini dapat memerlukan waktu beberapa saat: Anda harus mem-build Ruby dari sumber untuk menautkan ekstensi native (ditulis dalam C) dari library pihak ketiga dengan benar. Kekurangan (sementara) ini akan dibahas nanti dalam postingan.
Modul Wasm yang dikompilasi hanyalah fondasi untuk aplikasi Anda. Anda juga harus memaketkan kode aplikasi itu sendiri dan semua aset (misalnya, gambar, CSS, JavaScript). Sebelum melakukan pengepakan, buat aplikasi peluncur dasar yang dapat digunakan untuk menjalankan Rails yang di-wasmify di browser. Untuk hal itu, ada juga perintah generator:
$ bin/rails wasmify:pwa
create pwa
create pwa/boot.html
create pwa/boot.js
...
prepend config/wasmify.yml
Perintah sebelumnya menghasilkan aplikasi PWA minimal yang dibuat dengan Vite yang dapat digunakan secara lokal untuk menguji modul Rails Wasm yang dikompilasi atau di-deploy secara statis untuk mendistribusikan aplikasi.
Sekarang, dengan peluncur, Anda hanya perlu memaketkan seluruh aplikasi ke dalam satu biner Wasm:
$ bin/rails wasmify:pack
...
Packed the application to pwa/app.wasm
Size: 76.2 MB
Selesai. Jalankan aplikasi peluncur dan lihat aplikasi blogging Rails Anda berjalan sepenuhnya dalam browser:
$ cd pwa/
$ yarn dev
VITE v4.5.5 ready in 290 ms
➜ Local: http://localhost:5173/
Buka http://localhost:5173, tunggu beberapa saat hingga tombol "Launch" aktif, lalu klik tombol tersebut. Nikmati penggunaan aplikasi Rails yang berjalan secara lokal di browser Anda.
Bukankah rasanya seperti keajaiban menjalankan aplikasi sisi server monolitik tidak hanya di mesin Anda, tetapi dalam sandbox browser? Bagi saya (meskipun saya adalah "dukun"), hal ini masih terlihat seperti fantasi. Namun, tidak ada keajaiban, hanya kemajuan teknologi.
Demo
Anda dapat melihat demo yang disematkan dalam artikel atau meluncurkan demo di jendela mandiri. Lihat kode sumber di GitHub.
Di balik layar Rails di Wasm
Untuk lebih memahami tantangan (dan solusi) dalam memaketkan aplikasi sisi server ke dalam modul Wasm, bagian lain dari postingan ini menjelaskan komponen yang merupakan bagian dari arsitektur ini.
Aplikasi web bergantung pada banyak hal selain bahasa pemrograman yang digunakan untuk menulis kode aplikasi. Setiap komponen juga harus dibawa ke _lingkungan deployment lokal_—browser. Yang menarik dari demo "blog dalam 15 menit" adalah hal ini dapat dilakukan tanpa menulis ulang kode aplikasi. Kode yang sama digunakan untuk menjalankan aplikasi dalam mode klasik, sisi server, dan di browser.
Framework, seperti Ruby on Rails, memberi Anda antarmuka, abstraksi untuk berkomunikasi dengan komponen infrastruktur. Bagian berikut membahas cara menggunakan arsitektur framework untuk memenuhi kebutuhan penayangan lokal yang agak esoteris.
Dasar: ruby.wasm
Ruby secara resmi siap Wasm pada tahun 2022 (sejak versi 3.2.0) yang berarti kode sumber C dapat dikompilasi ke Wasm dan membawa VM Ruby ke mana pun Anda inginkan. Project ruby.wasm mengirimkan modul yang telah dikompilasi sebelumnya dan binding JavaScript untuk menjalankan Ruby di browser (atau runtime JavaScript lainnya). Project ruby:wasm juga dilengkapi dengan alat build yang memungkinkan Anda mem-build versi Ruby kustom dengan dependensi tambahan—hal ini sangat penting untuk project yang mengandalkan library dengan ekstensi C. Ya, Anda juga dapat mengompilasi ekstensi native ke dalam Wasm. (Belum semua ekstensi, tetapi sebagian besar ekstensi).
Saat ini, Ruby sepenuhnya mendukung Antarmuka Sistem WebAssembly, WASI 0.1. WASI 0.2 yang menyertakan Model Komponen sudah dalam status alfa dan beberapa langkah lagi dari penyelesaian.Setelah WASI 0.2 didukung, hal ini akan menghilangkan kebutuhan saat ini untuk mengompilasi ulang seluruh bahasa setiap kali Anda perlu menambahkan dependensi native baru: dependensi tersebut dapat dikomponenkan.
Sebagai efek samping, Model Komponen juga akan membantu mengurangi ukuran paket. Anda dapat mempelajari lebih lanjut pengembangan dan progres ruby.wasm dari diskusi Yang dapat Anda lakukan dengan Ruby di WebAssembly.
Jadi, bagian Ruby dari persamaan Wasm telah dipecahkan. Namun, Rails sebagai framework web memerlukan semua komponen yang ditampilkan dalam diagram sebelumnya. Lanjutkan membaca untuk mempelajari cara memasukkan komponen lain ke dalam browser dan menautkannya di Rails.
Menghubungkan ke database yang berjalan di browser
SQLite3 dilengkapi dengan distribusi Wasm resmi dan wrapper JavaScript yang sesuai, sehingga siap disematkan di browser. PostgreSQL untuk Wasm tersedia melalui project PGlite. Oleh karena itu, Anda hanya perlu mengetahui cara terhubung ke database dalam browser dari aplikasi Rails on Wasm.
Komponen, atau sub-framework, Rails yang bertanggung jawab atas pemodelan data dan interaksi database disebut Active Record (ya, dinamai berdasarkan pola desain ORM). Active Record memisahkan implementasi database yang menggunakan SQL sebenarnya dari kode aplikasi melalui adaptor database. Secara default, Rails memberi Anda adaptor MySQL, PostgreSQL, dan SQLite3. Namun, semuanya mengasumsikan terhubung ke database sebenarnya yang tersedia melalui jaringan. Untuk mengatasi hal ini, Anda dapat menulis adaptor Anda sendiri untuk terhubung ke database dalam browser yang lokal.
Berikut adalah cara adaptor SQLite3 Wasm dan PGlite diterapkan sebagai bagian dari project Wasmify Rails yang dibuat:
- Class adaptor mewarisi dari adaptor bawaan yang sesuai (misalnya,
class PGliteAdapter < PostgreSQLAdapter
), sehingga Anda dapat menggunakan kembali logika penguraian hasil dan persiapan kueri yang sebenarnya. - Sebagai ganti koneksi database tingkat rendah, Anda menggunakan objek antarmuka eksternal yang berada di runtime JavaScript—jembatan antara modul Rails Wasm dan database.
Misalnya, berikut adalah implementasi jembatan untuk SQLite3 Wasm:
export function registerSQLiteWasmInterface(worker, db, opts = {}) {
const name = opts.name || "sqliteForRails";
worker[name] = {
exec: function (sql) {
let cols = [];
let rows = db.exec(sql, { columnNames: cols, returnValue: "resultRows" });
return {
cols,
rows,
};
},
changes: function () {
return db.changes();
},
};
}
Dari perspektif aplikasi, peralihan dari database sebenarnya ke database dalam browser hanyalah masalah konfigurasi:
# config/database.yml
development:
adapter: sqlite3
production:
adapter: sqlite3
wasm:
adapter: sqlite3_wasm
js_interface: "sqliteForRails"
Menggunakan database lokal tidak memerlukan banyak upaya. Namun, jika sinkronisasi data dengan beberapa sumber tepercaya sentral diperlukan, Anda mungkin akan menghadapi tantangan tingkat yang lebih tinggi. Pertanyaan ini berada di luar cakupan postingan ini (petunjuk: lihat demo Rails di PGlite dan ElectricSQL).
Pekerja layanan sebagai server web
Komponen penting lainnya dari aplikasi web adalah server web. Pengguna berinteraksi dengan aplikasi web menggunakan permintaan HTTP. Oleh karena itu, Anda memerlukan cara untuk merutekan permintaan HTTP yang dipicu oleh navigasi atau pengiriman formulir ke modul Wasm. Untungnya, browser memiliki jawaban untuk itu—pekerja layanan.
Pekerja layanan adalah jenis Web Worker khusus yang bertindak sebagai proxy antara aplikasi JavaScript dan jaringan. Modul ini dapat memintas permintaan dan memanipulasinya, misalnya: menayangkan data yang di-cache, mengalihkan ke URL lain, atau… ke modul Wasm. Berikut adalah sketsa layanan yang berfungsi menayangkan permintaan menggunakan aplikasi Rails yang berjalan di Wasm:
// The vm variable holds a reference to the Wasm module with a
// Ruby VM initialized
let vm;
// The db variable holds a reference to the in-browser
// database interface
let db;
const initVM = async (progress, opts = {}) => {
if (vm) return vm;
if (!db) {
await initDB(progress);
}
vm = await initRailsVM("/app.wasm");
return vm;
};
const rackHandler = new RackHandler(initVM});
self.addEventListener("fetch", (event) => {
// ...
return event.respondWith(
rackHandler.handle(event.request)
);
});
"Pengambilan" dipicu setiap kali permintaan dibuat oleh browser. Anda dapat mendapatkan informasi permintaan (URL, header HTTP, isi) dan membuat objek permintaan Anda sendiri.
Rails, seperti sebagian besar aplikasi web Ruby, mengandalkan antarmuka Rack untuk menangani permintaan HTTP. Antarmuka Rack menjelaskan format objek permintaan dan respons serta antarmuka pengendali HTTP (aplikasi) yang mendasarinya. Anda dapat menyatakan properti ini sebagai berikut:
request = {
"REQUEST_METHOD" => "GET",
"SCRIPT_NAME" => "",
"SERVER_NAME" => "localhost",
"SERVER_PORT" => "3000",
"PATH_INFO" => "/posts"
}
handler = proc do |env|
[
200,
{"Content-Type" => "text/html"},
["<!doctype html><html><body>Hello Web!</body></html>"]
]
end
handler.call(request) #=> [200, {...}, [...]]
Jika Anda merasa format permintaan tersebut sudah tidak asing, berarti Anda mungkin pernah menggunakan CGI pada zaman dulu.
Objek JavaScript RackHandler
bertanggung jawab untuk mengonversi permintaan dan
respons antara realm JavaScript dan Ruby. Mengingat bahwa Rack digunakan oleh sebagian besar aplikasi web Ruby, penerapannya menjadi universal, bukan khusus Rails.
Namun,
implementasi sebenarnya
terlalu panjang untuk diposting di sini.
Pekerja layanan adalah salah satu titik integral utama aplikasi web dalam browser. Ini bukan hanya proxy HTTP, tetapi juga lapisan penyimpanan dalam cache dan pengalih jaringan (yaitu, Anda dapat mem-build aplikasi utamakan lokal atau mampu offline). Ini juga merupakan komponen yang dapat membantu Anda menayangkan file yang diupload pengguna.
Mempertahankan upload file di browser
Salah satu fitur tambahan pertama yang akan diterapkan di aplikasi blog baru Anda kemungkinan adalah dukungan untuk upload file, atau lebih khusus, melampirkan gambar ke postingan. Untuk melakukannya, Anda memerlukan cara untuk menyimpan dan menayangkan file.
Di Rails, bagian framework yang bertanggung jawab untuk menangani upload file disebut Active Storage. Active Storage memberi developer abstraksi dan antarmuka untuk menangani file tanpa memikirkan mekanisme penyimpanan tingkat rendah. Di mana pun Anda menyimpan file, di hard drive atau di cloud, kode aplikasi tetap tidak mengetahuinya.
Serupa dengan Active Record, untuk mendukung mekanisme penyimpanan kustom, Anda hanya perlu menerapkan adaptor layanan penyimpanan yang sesuai. Di mana menyimpan file di browser?
Opsi tradisionalnya adalah menggunakan database. Ya, Anda dapat menyimpan file sebagai blob di database, tanpa memerlukan komponen infrastruktur tambahan. Dan sudah ada plugin siap pakai untuk itu di Rails, Active Storage Database. Namun, menayangkan file yang disimpan dalam database melalui aplikasi Rails yang berjalan dalam WebAssembly tidak ideal karena melibatkan putaran serialisasi (de-) yang tidak gratis.
Solusi yang lebih baik dan lebih dioptimalkan untuk browser adalah menggunakan File System API dan memproses upload file serta file yang diupload server langsung dari pekerja layanan. Kandidat yang sempurna untuk infrastruktur tersebut adalah OPFS (origin private file system), API browser terbaru yang pasti akan memainkan peran penting untuk aplikasi dalam browser di masa mendatang.
Yang dapat dicapai Rails dan Wasm bersama-sama
Kami yakin Anda telah mengajukan pertanyaan ini saat mulai membaca artikel: mengapa menjalankan framework sisi server di browser? Ide framework atau library yang bersifat sisi server (atau sisi klien) hanyalah label. Kode yang baik dan, terutama, abstraksi yang baik berfungsi di mana saja. Label tidak boleh menghentikan Anda untuk menjelajahi kemungkinan baru dan mendorong batas-batas framework (misalnya, Ruby on Rails) serta batas-batas runtime (WebAssembly). Keduanya dapat memperoleh manfaat dari kasus penggunaan yang tidak konvensional tersebut.
Ada juga banyak kasus penggunaan yang konvensional, atau praktis.
Pertama, menghadirkan framework ke browser membuka peluang prototyping dan pembelajaran yang sangat besar. Bayangkan Anda dapat bermain dengan library, plugin, dan pola langsung di browser dan bersama orang lain. Stackblitz memungkinkan hal ini untuk framework JavaScript. Contoh lainnya adalah WordPress Playground yang memungkinkan pengguna bermain dengan tema WordPress tanpa keluar dari halaman web. Wasm dapat memungkinkan hal serupa untuk Ruby dan ekosistemnya.
Ada kasus khusus coding dalam browser yang sangat berguna bagi developer open source—melakukan prioritas dan proses debug masalah. Sekali lagi, StackBlitz membuat hal ini menjadi hal yang penting untuk project JavaScript: Anda membuat skrip reproduksi minimal, mengarahkan link ke Issue GitHub, dan menghemat waktu pengelola untuk mereproduksi skenario Anda. Dan, sebenarnya, hal ini sudah mulai terjadi di Ruby berkat project RunRuby.dev (berikut adalah contoh masalah yang diselesaikan dengan reproduksi dalam browser).
Kasus penggunaan lainnya adalah aplikasi yang dapat digunakan secara offline (atau yang mendukung offline). Aplikasi yang kompatibel dengan offline yang biasanya berfungsi menggunakan jaringan, tetapi saat tidak ada koneksi, aplikasi tersebut tetap dapat digunakan. Misalnya, program email yang memungkinkan Anda menelusuri kotak masuk saat offline. Atau, aplikasi koleksi musik dengan kemampuan "Simpan di perangkat", sehingga musik favorit Anda terus diputar meskipun tidak ada koneksi jaringan. Kedua contoh tersebut bergantung pada data yang disimpan secara lokal, bukan hanya menggunakan cache seperti PWA klasik.
Terakhir, membuat aplikasi lokal (atau desktop) dengan Rails juga masuk akal, karena produktivitas yang diberikan framework kepada Anda tidak bergantung pada runtime. Framework berfitur lengkap sangat cocok untuk mem-build aplikasi yang sarat data pribadi dan logika. Selain itu, menggunakan Wasm sebagai format distribusi portabel juga merupakan opsi yang layak.
Ini hanyalah awal dari perjalanan Rails on Wasm ini. Anda dapat mempelajari lebih lanjut tantangan dan solusi dalam ebook Ruby on Rails on WebAssembly (yang, ngomong-ngomong, adalah aplikasi Rails yang dapat digunakan secara offline).