Yayınlanma tarihi: 31 Ocak 2025
Tarayıcınızda yalnızca ön uç değil, arka uç da dahil olmak üzere tam işlevli bir blog yayınladığınızı hayal edin. Sunucu veya bulut kullanmazsınız. Yalnızca siz, tarayıcınız ve WebAssembly'i kullanırsınız. WebAssembly, sunucu tarafı çerçevelerin yerel olarak çalıştırılmasına izin vererek klasik web geliştirmenin sınırlarını bulanıklaştırıyor ve heyecan verici yeni olasılıklar sunuyor. Bu yayında Vladimir Dementyev (Evil Martians'da Arka Uç Müdürü), Rails'de Ruby'yi Wasm ve tarayıcıya hazır hale getirmeyle ilgili gelişmeleri paylaşıyor:
- Rails'i 15 dakikada tarayıcıya getirme.
- Rails'in wasm'e dönüştürülmesinin perde arkası.
- Rails ve Wasm'in geleceği.
Ruby on Rails'in ünlü "15 dakikada blog oluşturma" özelliği artık tarayıcınızda
Ruby on Rails, geliştirici üretkenliğine ve hızlı bir şekilde ürün sunmaya odaklanan bir web çerçevesidir. GitHub ve Shopify gibi sektör liderleri tarafından kullanılan teknolojidir. Çerçevenin popülerliği, David Heinemeier Hansson (veya DHH) tarafından yayınlanan "15 dakikada blog oluşturma" videosunun yayınlanmasıyla yıllar önce başladı. 2005'te bu kadar kısa sürede tamamen çalışan bir web uygulaması geliştirmek düşünülemezdi. Sihirli bir deneyimdi.
Bugün, tamamen tarayıcınızda çalışan bir Rails uygulaması oluşturarak bu büyülü hissi yeniden yaşamanızı sağlamak istiyorum. Yolculuğunuz, normal şekilde temel bir Rails uygulaması oluşturarak ve ardından bunu Wasm için paketleyerek başlar.
Arka plan: Komut satırında "15 dakikada blog oluşturma"
Bilgisayarınızda Ruby ve Ruby on Rails yüklü olduğunu varsayarak yeni bir Ruby on Rails uygulaması oluşturarak ve bazı işlevleri iskelet olarak oluşturarak başlarsınız (tıpkı orijinal "15 dakikada blog" videosunda olduğu gibi):
$ 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) ===========
Artık kod tabanına dokunmadan uygulamayı çalıştırabilir ve çalışırken görebilirsiniz:
$ bin/dev
=> Booting Puma
=> Rails 8.0.1 application starting in development
...
* Listening on http://127.0.0.1:3000
Artık http://localhost:3000/posts adresinden blogunuzu açıp içerik yazmaya başlayabilirsiniz.
Birkaç dakika içinde basit ancak işlevsel bir blog uygulaması oluşturdunuz. Bu, tam kapsamlı, sunucu kontrollü bir uygulamadır: Verilerinizi saklamak için bir veritabanınız (SQLite), HTTP isteklerini işlemek için bir web sunucunuz (Puma) ve iş mantığınızı korumak, kullanıcı arayüzü sağlamak ve kullanıcı etkileşimlerini işlemek için bir Ruby programınız vardır. Son olarak, gezinme deneyimini kolaylaştırmak için ince bir JavaScript katmanı (Turbo) bulunur.
Resmi Rails demosu, bu uygulamayı bare metal bir sunucuya dağıtma ve böylece üretime hazır hale getirme yönünde devam eder. Yolculuğunuz ters yönde devam edecek: Uygulamanızı uzak bir yere yerleştirmek yerine yerel olarak "dağıtacaksınız".
Bir sonraki seviye: Wasm'de "15 dakikada blog"
WebAssembly eklendikten sonra tarayıcılar yalnızca JavaScript kodunu değil, Wasm'e derlenebilen tüm kodları çalıştırabilir hale geldi. Ruby de bu konuda istisna değil. Rails'in Ruby'den daha fazlası olduğu kesindir. Ancak farklılıklara değinmeden önce demoya devam edelim ve Rails uygulamasını wasmify (wasmify-rails kitaplığı tarafından oluşturulan bir fiil) yapalım.
Blog uygulamanızı bir Wasm modülüne derlemek ve tarayıcıda çalıştırmak için yalnızca birkaç komut yürütmeniz gerekir.
Öncelikle Bundler'ı (Ruby'nin npm
aracı) kullanarak wasmify-rails kitaplığını yükleyin ve Rails CLI'yi kullanarak kitaplığın derleyicisini çalıştırın:
$ 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!
wasmify:rails
komutu, varsayılan "geliştirme", "test" ve "üretim" ortamlarına ek olarak özel bir "wasm" yürütme ortamı yapılandırır ve gerekli bağımlılıkları yükler. Yeni bir Rails uygulaması için bu, Wasm'e hazır hale getirmek için yeterlidir.
Ardından, Ruby çalışma zamanını, standart kitaplığı ve tüm uygulama bağımlılıkları içeren temel Wasm modülünü oluşturun:
$ 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
Bu adım biraz zaman alabilir: Üçüncü taraf kitaplıklarındaki yerel uzantıları (C ile yazılmış) doğru şekilde bağlamak için Ruby'yi kaynaktan derlemeniz gerekir. Bu (geçici) dezavantaja yayının ilerleyen bölümlerinde değineceğiz.
Derlenen Wasm modülü, uygulamanızın yalnızca temelidir. Ayrıca uygulama kodunun kendisini ve tüm öğeleri (ör. resimler, CSS, JavaScript) paketlemeniz gerekir. Paketlemeden önce, tarayıcıda wasm'e dönüştürülmüş Rails'i çalıştırmak için kullanılabilecek temel bir başlatıcı uygulaması oluşturun. Bunun için bir jeneratör komutu da vardır:
$ bin/rails wasmify:pwa
create pwa
create pwa/boot.html
create pwa/boot.js
...
prepend config/wasmify.yml
Önceki komut, derlenmiş Rails Wasm modülünü test etmek için yerel olarak kullanılabilen veya uygulamayı dağıtmak için statik olarak dağıtılabilen, Vite ile oluşturulmuş minimal bir PWA uygulaması oluşturur.
Artık başlatıcıyla birlikte, uygulamanın tamamını tek bir Wasm ikilisine paketlemeniz yeterlidir:
$ bin/rails wasmify:pack
...
Packed the application to pwa/app.wasm
Size: 76.2 MB
İşte bu kadar. Başlatıcı uygulamasını çalıştırın ve Rails blog uygulamanızın tamamen tarayıcıda çalıştığını görün:
$ cd pwa/
$ yarn dev
VITE v4.5.5 ready in 290 ms
➜ Local: http://localhost:5173/
http://localhost:5173 adresine gidin, "Başlat" düğmesinin etkinleşmesi için biraz bekleyin ve düğmeyi tıklayın. Tarayıcınızda yerel olarak çalışan Rails uygulamasıyla çalışmanın keyfini çıkarın.
Monolitik bir sunucu tarafı uygulamayı yalnızca makinenizde değil, tarayıcı korumalı alanında çalıştırmak sizce de büyü gibi değil mi? Ben de "büyücü" olmama rağmen bu durum hâlâ bir hayal gibi geliyor. Ancak bu konuda sihirli bir formül yoktur. Yalnızca teknolojinin ilerlemesi söz konusudur.
Demo
Makaleye yerleştirilmiş denemeyi deneyimleyebilir veya denemeyi bağımsız bir pencerede başlatabilirsiniz. GitHub'daki kaynak koda göz atın.
Wasm'de Rails'in perde arkası
Sunucu tarafı bir uygulamayı Wasm modülüne paketlemenin zorluklarını (ve çözümlerini) daha iyi anlamak için bu gönderinin geri kalanında bu mimarinin bileşenleri açıklanmaktadır.
Web uygulamaları, uygulama kodunu yazmak için kullanılan programlama dilinden çok daha fazlasına bağlıdır. Her bileşen, _yerel dağıtım ortamınıza_ (tarayıcıya) da getirilmelidir. "15 dakikada blog oluşturma" demosunun heyecan verici yanı, uygulama kodunun yeniden yazılmasına gerek kalmadan bu işlemin yapılabilmesidir. Uygulamayı klasik, sunucu tarafı modda ve tarayıcıda çalıştırmak için aynı kod kullanıldı.
Ruby on Rails gibi bir çerçeve, altyapı bileşenleriyle iletişim kurmak için size bir arayüz, bir soyutlama sağlar. Aşağıdaki bölümde, biraz gizemli yerel yayınlama ihtiyaçlarını karşılamak için çerçeve mimarisini nasıl kullanabileceğiniz açıklanmaktadır.
Temel: ruby.wasm
Ruby, 2022'de resmi olarak Wasm'e hazır hale geldi (3.2.0 sürümü itibarıyla). Bu, C kaynak kodunun Wasm'e derlenebileceği ve istediğiniz yere Ruby sanal makinesi getirebileceği anlamına geliyor. ruby.wasm projesi, Ruby'yi tarayıcıda (veya başka bir JavaScript çalışma zamanında) çalıştırmak için önceden derlenmiş modüller ve JavaScript bağlamaları sağlar. ruby:wasm projesi, ek bağımlılıklara sahip özel bir Ruby sürümü oluşturmanıza olanak tanıyan derleme araçlarını da içerir. Bu, C uzantılı kitaplıklara dayalı projeler için çok önemlidir. Evet, yerel uzantıları Wasm'e de derleyebilirsiniz. (Henüz tüm uzantılar değil, ancak çoğu uzantı).
Ruby şu anda WebAssembly Sistem Arayüzü'nü (WASI 0.1) tam olarak desteklemektedir. Bileşen Modeli'ni içeren WASI 0.2, alfa sürümündedir ve tamamlanmasına birkaç adım kalmıştır.WASI 0.2 desteklendiğinde, yeni yerel bağımlılıklar eklemeniz gerektiğinde tüm dili yeniden derleme ihtiyacı ortadan kalkacaktır: Bağımlılıklar bileşenleştirilebilir.
Bileşen modeli, ek bir avantaj olarak paket boyutunun küçültülmesine de yardımcı olur. ruby.wasm geliştirme ve ilerleme durumu hakkında daha fazla bilgiyi WebAssembly'de Ruby ile neler yapabileceğiniz konulu konuşmadan edinebilirsiniz.
Böylece, Wasm denkleminin Ruby kısmı çözüldü. Ancak web çerçevesi olarak Rails'in önceki şemada gösterilen tüm bileşenlere ihtiyacı vardır. Tarayıcıya başka bileşenler eklemeyi ve bunları Rails'de birbirine bağlamayı öğrenmek için okumaya devam edin.
Tarayıcıda çalışan bir veritabanına bağlanma
SQLite3, resmi bir Wasm dağıtımı ve buna karşılık gelen bir JavaScript sarmalayıcısı ile birlikte gelir. Bu nedenle tarayıcıya yerleştirilmeye hazırdır. Wasm için PostgreSQL, PGlite projesi aracılığıyla kullanılabilir. Bu nedenle, yalnızca Wasm'de Rails uygulamasından tarayıcı içi veritabanına nasıl bağlanacağınızı öğrenmeniz gerekir.
Veri modelleme ve veritabanı etkileşimlerinden sorumlu Rails bileşeni veya alt çerçevesi Active Record olarak adlandırılır (evet, ORM tasarım kalıbından almıştır). Active Record, veritabanı bağdaştırıcılar aracılığıyla gerçek SQL konuşan veritabanı uygulamasını uygulama kodundan soyutlar. Rails, SQLite3, PostgreSQL ve MySQL bağdaştırıcılarına sahiptir. Ancak bunların hepsi, ağ üzerinden kullanılabilen gerçek veritabanlarına bağlanmayı varsayar. Bu sorunun üstesinden gelmek için yerel, tarayıcı içi veritabanlarına bağlanmak üzere kendi adaptörlerinizi yazabilirsiniz.
Wasmify Rails projesi kapsamında uygulanan SQLite3 Wasm ve PGlite bağdaştırıcılar aşağıdaki şekilde oluşturulur:
- Uyumlulaştırma sınıfı, ilgili yerleşik uyumlulaştırmadan (ör.
class PGliteAdapter < PostgreSQLAdapter
) devralındığından gerçek sorgu hazırlama ve sonuçları ayrıştırma mantığını yeniden kullanabilirsiniz. - Düşük düzey veritabanı bağlantısı yerine, JavaScript çalışma zamanında bulunan bir harici arayüz nesnesini (Rails Wasm modülü ile veritabanı arasındaki bir köprü) kullanırsınız.
Örneğin, SQLite3 Wasm için köprü uygulaması aşağıda verilmiştir:
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();
},
};
}
Uygulama açısından, gerçek bir veritabanından tarayıcı içi veritabanına geçiş yalnızca bir yapılandırma meselesidir:
# config/database.yml
development:
adapter: sqlite3
production:
adapter: sqlite3
wasm:
adapter: sqlite3_wasm
js_interface: "sqliteForRails"
Yerel veritabanıyla çalışmak çok fazla çaba gerektirmez. Ancak bazı merkezi doğruluk kaynağıyla veri senkronizasyonu gerekiyorsa daha yüksek düzeyde bir zorlukla karşılaşabilirsiniz. Bu soru bu yayının kapsamı dışındadır (ipucu: PGlite ve ElectricSQL'de Rails demo'suna göz atın).
Web sunucusu olarak hizmet çalışanı
Web uygulamalarının diğer önemli bileşenlerinden biri de web sunucusudur. Kullanıcılar, HTTP isteklerini kullanarak web uygulamalarıyla etkileşim kurar. Bu nedenle, gezinme veya form gönderimleri tarafından tetiklenen HTTP isteklerini Wasm modülünüze yönlendirmenin bir yoluna ihtiyacınız vardır. Neyse ki tarayıcıda buna bir çözüm var: hizmet çalışanları.
Hizmet çalışanı, JavaScript uygulaması ile ağ arasında proxy görevi gören özel bir Web Çalışanı türüdür. İstekleri yakalayıp değiştirebilir. Örneğin: Önbelleğe alınmış veriler sunabilir, diğer URL'lere veya Wasm modüllerine yönlendirebilir. Aşağıda, Wasm'de çalışan bir Rails uygulamasını kullanarak istek sunan bir hizmetin taslağı verilmiştir:
// 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)
);
});
"fetch", tarayıcı tarafından her istek gönderildiğinde tetiklenir. İstek bilgilerini (URL, HTTP başlıkları, gövde) alıp kendi istek nesnenizi oluşturabilirsiniz.
Rails, çoğu Ruby web uygulaması gibi HTTP istekleriyle çalışmak için Rack arayüzünden yararlanır. Rack arayüzü, istek ve yanıt nesnelerinin biçiminin yanı sıra temel HTTP işleyicinin (uygulama) arayüzünü tanımlar. Bu özellikleri aşağıdaki gibi ifade edebilirsiniz:
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, {...}, [...]]
İstek biçimini tanıdık bulduysanız muhtemelen eskiden CGI ile çalıştınız.
RackHandler
JavaScript nesnesi, istek ve yanıtları JavaScript ile Ruby alanları arasında dönüştürmekten sorumludur. Rack'ın çoğu Ruby web uygulaması tarafından kullanıldığı göz önüne alındığında, uygulama Rails'e özgü değil, evrensel olur.
Ancak gerçek uygulama burada yayınlanamayacak kadar uzun.
Hizmet çalışanı, tarayıcı içi web uygulamasının temel bileşenlerinden biridir. Yalnızca bir HTTP proxy'si değil, aynı zamanda bir önbelleğe alma katmanı ve ağ değiştiricisidir (yani yerel öncelikli veya çevrimdışı özellikli bir uygulama oluşturabilirsiniz). Bu bileşen, kullanıcı tarafından yüklenen dosyaları yayınlamanıza da yardımcı olabilir.
Dosya yüklemelerini tarayıcıda tutma
Yeni blog uygulamanıza eklenecek ilk ek özelliklerden biri, dosya yükleme veya daha spesifik olarak, yayınlara resim ekleme desteği olabilir. Bunu yapmak için dosyaları depolayacak ve sunacak bir yönteme ihtiyacınız vardır.
Rails'de, dosya yüklemeleriyle ilgilenmekten sorumlu olan çerçeve bölümüne Active Storage denir. Active Storage, geliştiricilere düşük düzey depolama mekanizmasını düşünmeden dosyalarla çalışmak için soyutlamalar ve arayüzler sunar. Dosyalarınızı sabit diskte veya bulutta saklasanız bile uygulama kodu bundan haberdar olmaz.
Active Record'a benzer şekilde, özel bir depolama mekanizmasını desteklemek için tek yapmanız gereken ilgili depolama hizmeti bağdaştırıcısı uygulamaktır. Tarayıcıda dosyaları nerede saklayabilirim?
Geleneksel seçenek, veritabanı kullanmaktır. Evet, dosyaları veritabanında blob olarak depolayabilirsiniz. Bunun için ek altyapı bileşenleri gerekmez. Rails'de bunun için hazır bir eklenti de var: Active Storage Database. Ancak, bir veritabanında depolanan dosyaları WebAssembly içinde çalışan Rails uygulaması aracılığıyla yayınlamak, ücretsiz olmayan (seri) serileştirme turları içerdiğinden ideal değildir.
Daha iyi ve tarayıcı için daha optimize edilmiş bir çözüm, dosya sistemi API'lerini kullanmak ve dosya yüklemelerini ve sunucu tarafından yüklenen dosyaları doğrudan hizmet işleyicisinden işlemek olacaktır. Bu tür bir altyapı için mükemmel bir aday, gelecekteki tarayıcı içi uygulamalarda kesinlikle önemli bir rol oynayacak olan son derece yeni bir tarayıcı API'si olan OPFS (orijinal özel dosya sistemi) olacaktır.
Rails ve Wasm birlikte neler başarabilir?
Makaleyi okumaya başladığınızda kendinize şu soruyu sorduğunuzdan eminim: Tarayıcıda sunucu tarafı çerçeve çalıştırmanın ne gibi avantajları var? Bir çerçevenin veya kitaplığın sunucu tarafı (veya istemci tarafı) olması fikri yalnızca bir etikettir. İyi kod ve özellikle iyi bir soyutlama her yerde işe yarar. Etiketler, yeni olasılıkları keşfetmenizi ve hem çerçevenin (ör. Ruby on Rails) hem de çalışma ortamının (WebAssembly) sınırlarını zorlamanızı engellememelidir. Her ikisi de bu tür alışılmışın dışında kullanım alanlarından yararlanabilir.
Geleneksel veya pratik kullanım alanları da vardır.
Öncelikle, çerçeveyi tarayıcıya getirmek muazzam öğrenim ve prototipleme fırsatları sunar. Doğrudan tarayıcınızda ve diğer kullanıcılarla birlikte kitaplıklar, eklentiler ve kalıplarla oynayabileceğinizi hayal edin. Stackblitz, bu özelliği JavaScript çerçeveleri için mümkün kıldı. Bir diğer örnek ise web sayfasından ayrılmadan WordPress temalarıyla oynamayı mümkün kılan WordPress Playground'tur. Wasm, Ruby ve ekosistemi için benzer bir şey sağlayabilir.
Özellikle açık kaynak geliştiriciler için yararlı olan tarayıcı içi kodlamayla ilgili özel bir durum vardır: sorunları önceliklendirme ve hata ayıklama. Yine StackBlitz, bu özelliği JavaScript projeleri için de kullanıma sundu: Minimum yeniden oluşturma komut dosyası oluşturur, bağlantıyı bir GitHub sorununa yönlendirir ve bakım ekibinin senaryonuzu yeniden oluşturma süresini kısaltırsınız. Aslında, RunRuby.dev projesi sayesinde Ruby'de bu süreç başladı bile (Tarayıcı içi yeniden oluşturma ile çözülen bir örnek sorun burada verilmiştir).
Çevrimdışı özellikli (veya çevrimdışı farkında) uygulamalar da bu kullanım alanlarından biridir. Genellikle ağ kullanarak çalışan ancak bağlantı olmadığında da kullanılabilen çevrimdışı uygulamalar. Örneğin, çevrimdışıyken gelen kutunuzda arama yapmanıza olanak tanıyan bir e-posta istemcisi. Alternatif olarak, "Cihazda sakla" özelliğine sahip bir müzik kitaplığı uygulaması kullanabilirsiniz. Bu sayede, ağ bağlantısı olmasa bile en sevdiğiniz müzikler çalmaya devam eder. Her iki örnek de klasik PWA'larda olduğu gibi yalnızca önbelleği değil, yerel olarak depolanan verileri kullanır.
Son olarak, Rails ile yerel (veya masaüstü) uygulamalar oluşturmak da mantıklıdır. Bunun nedeni, çerçevenin size sunduğu üretkenliğin çalışma zamanına bağlı olmamasıdır. Tam özellikli çerçeveler, kişisel veriler ve mantık ağırlıklı uygulamalar oluşturmak için idealdir. Taşınabilir bir dağıtım biçimi olarak Wasm'i kullanmak da uygun bir seçenektir.
Bu, Wasm'de Rails yolculuğunun yalnızca başlangıcı. Karşılaşılan zorluklar ve çözümler hakkında daha fazla bilgiyi WebAssembly'de Ruby on Rails e-kitabında bulabilirsiniz (bu e-kitap da çevrimdışı olarak çalışabilen bir Rails uygulamasıdır).