Upgrade to Pro — share decks privately, control downloads, hide ads and more …

[WasmCon 2024] From client to server: Ruby on R...

[WasmCon 2024] From client to server: Ruby on Rails on WebAssembly

Ruby on Rails is a famous “batteries included” framework for the rapid development of web applications. Its full-stack promise comes in a server-oriented, or HTML-over-the-Wire flavor: a server oversees everything from database interactions to your application UI/UX. And whenever there is a server involved, the network and its unpredictability come into play. No matter how much you enjoy developing with Rails, it would be hard to achieve the same level of user experience as with client-side and especially local-first frameworks. And here comes Wasm.

With the help of WebAssembly, we can do a radical shift—bring the Rails application right into your browser, and make it local-first!

In my talk, I want to discuss the challenges of making a classic web framework Wasm compatible, the techniques we can use to run server-first applications in the browsers and what are the use cases.

Vladimir Dementyev

November 12, 2024
Tweet

More Decks by Vladimir Dementyev

Other Decks in Programming

Transcript

  1. palkan_tula palkan Ruby VM Gems Native extensions System tools Database

    Rails Components 14 Web Server Storage Queue API
  2. palkan_tula palkan Ruby VM Gems Native extensions System tools Database

    Rails Components 14 Web Server Storage Queue API ??? !
  3. palkan_tula palkan Ruby VM Gems Native extensions System tools Web

    Server Database Queue Storage API Rails Components 17
  4. palkan_tula palkan Ruby VM Gems Native extensions System tools Web

    Server Database Queue Storage bundler active_storage rack active_record active_job API net/http Rails Components 17
  5. palkan_tula palkan rails new rails g scaffold ... Demo: ReviewCon

    22 Build a new full stack app Scaffold models, controllers, views
  6. palkan_tula palkan rails new rails g scaffold ... bolt.new Demo:

    ReviewCon 23 Ask AI to polish UI # Build a new full stack app Scaffold models, controllers, views
  7. palkan_tula palkan wasmify:install wasmify:pwa wasmify:pack Demo: ReviewCon 25 Rails on

    Wasm toolkit Generate a boot app (JS) Compile the whole app into app.wasm
  8. palkan_tula palkan wasmify:install wasmify:pwa wasmify:pack on_wasm? Demo: ReviewCon 26 Compile

    the whole app into app.wasm Rails on Wasm toolkit Generate a boot app (JS) Make application Wasm-aware
  9. palkan_tula palkan Running an entire Rails application within your browser

    is reality! 27 Tell me more! ! $ reviewcon2024.netlify.app
  10. palkan_tula palkan Ruby VM Gems Native extensions System tools Web

    Server Database Queue Storage bundler active_storage rack active_record active_job API net/http Rails Components 28
  11. palkan_tula palkan sqlite.wasm Database 29 Database active_record config/database.yml database.yml default:

    &default adapter: sqlite3 production: # ... wasm: adapter: sqlite3_wasm development: <<: *default test: <<: *default database: storage/test.sqlite3 1 2 3 4 . 20 21 22 23 24 25 . 28 29 30 31
  12. active_record/connection_adapters/sqlite3_wasm_adapter.rb sqlite3_wasm_adapter.rb module ActiveRecord module ConnectionAdapters class SQLite3WasmAdapter < SQLite3Adapter

    class ExternalInterface # ... def exec(...) JS.global[js_interface].exec(...) end end end end # ... end 1 2 3 4 . 20 21 22 23 24 25 . 229 230 Most functionality is inherited Query execution is delegated to a JS object
  13. src/active_record.js active_record.js export function registerSQLiteWasmInterface(worker, db, opts = {}) {

    const name = opts.name || "sqlite4rails"; worker[name] = { exec: function (sql) { let cols = []; let rows = db.exec(sql, { columnNames: cols, returnValue: "resultRows" }); return { cols, rows, }; }, changes: function () { return db.changes(); }, }; } 1 2 3 4 5 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
  14. palkan_tula palkan sqlite.wasm pglite Database 32 Database active_record config/database.yml database.yml

    default: &default adapter: sqlite3 production: # ... wasm: adapter: pglite development: <<: *default test: <<: *default database: storage/test.sqlite3 1 2 3 4 . 20 21 22 23 24 25 . 28 29 30 31
  15. palkan_tula palkan Ruby VM Gems Native extensions System tools Web

    Server Database Queue Storage bundler active_storage rack active_record active_job API net/http Rails Components 33
  16. palkan_tula palkan Service Worker Serve HTTP 34 pwa/rails.sw.js rails.sw.js //

    ... 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) ); }); . 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 Web Server rack Write your Rack-compatible server in JS
  17. src/rack.js rack.js export class RackHandler { async process(request) { let

    vm = await this.vmSetup(); const railsURL = request.url; const railsHeaders = {}; for (const [key, value] of request.headers.entries()) { railsHeaders[`HTTP_${key.toUpperCase().replaceAll("-", "_")}`] = value; } try { const command = ` request = Rack::MockRequest.env_for( "${railsURL}", JSON.parse(%q[${JSON.stringify(railsHeaders)}]).merge( method: :${request.method} ) ) response = Rack::Response[*Rails.application.call(request)] status, headers, body = *response.finish {status:, headers:, body:} `; let res = vm.eval(command).toJS(); let { status, headers, body } = res; const resp = new Response(body, { headers, status, }); return resp; } catch (e) { this.logger.error(e); return new Response(`Application Error: ${e.message}`, { status: 500, }); } } } 1 2 3 4 5 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45
  18. src/rack.js rack.js export class RackHandler { async process(request) { let

    vm = await this.vmSetup(); const railsURL = request.url; const railsHeaders = {}; for (const [key, value] of request.headers.entries()) { railsHeaders[`HTTP_${key.toUpperCase().replaceAll("-", "_")}`] = value; } try { const command = ` request = Rack::MockRequest.env_for( "${railsURL}", JSON.parse(%q[${JSON.stringify(railsHeaders)}]).merge( method: :${request.method} ) ) response = Rack::Response[*Rails.application.call(request)] status, headers, body = *response.finish {status:, headers:, body:} `; let res = vm.eval(command).toJS(); let { status, headers, body } = res; const resp = new Response(body, { headers, status, }); return resp; } catch (e) { this.logger.error(e); return new Response(`Application Error: ${e.message}`, { status: 500, }); } } } 1 2 3 4 5 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 Boring code that translates requests/ responses from JS to Ruby and back !
  19. palkan_tula palkan HTTP: challenges Serving files Handling multipart form data

    / uploads Managing cookies 36 Web Server rack Base64 and custom Rack middleware Cookie Store API FormData and Data URIs
  20. palkan_tula palkan HTTP: the future wasi:http/proxy (WASI 0.2) 37 Web

    Server rack Implement incoming_handler right in your framework
  21. palkan_tula palkan Ruby VM Gems Native extensions System tools Web

    Server Database Queue Storage bundler active_storage rack active_record active_job API net/http Rails Components 38
  22. palkan_tula palkan Other Storage (uploads, assets) Background threads Outgoing HTTP

    Image processing 39 Storage active_storage Queue active_job API net/http File System API, OPFS Broadcast Channel API (multiple VMs) fetch() now, wasi/http future wasm-vips, magic-wasm
  23. palkan_tula palkan Guides Recipes Ideas Wasmbook % 40 https://writebook-on-wasm.fly.dev How

    to get started with Wasm How to solve particular problems Why and when you may need it
  24. palkan_tula palkan Rails on Wasm: Why? Joy of problem solving

    Pushing boundaries Practical applications 42 Of both Rails and Wasm Programming for happiness You can probably make some money out of it
  25. palkan_tula palkan TLR=Try-Learn-Reproduce Offline-aware apps email clients, task boards Data-paranoid

    apps truly own your data Local-first desktop apps music players, notes 43 Rails on Wasm: Why? Tutorials, online playgrounds, bug reproductions Better offline experience Better data control Bring web frameworks productivity to desktop dev