A modern web frontend framework for Ruby using ruby.wasm. Write reactive web applications using familiar Ruby syntax and patterns.
- Reactive State Management: Simple, predictable state updates with actions
- Virtual DOM: Efficient DOM updates using a virtual DOM implementation
- Event Handling: Intuitive event system with Ruby lambdas
- Component Architecture: Build reusable components with clean separation of concerns
- Lifecycle Hooks: Manage component lifecycle with hooks like
on_mounted - Ruby Syntax: Write frontend applications using Ruby instead of JavaScript
Create an HTML file:
<!DOCTYPE html>
<html>
<head>
<script src="https://unpkg.com/[email protected]"></script>
<script defer type="text/ruby" src="app.rb"></script>
</head>
<body>
<div id="app"></div>
</body>
</html>Create app.rb:
require "js"
# Define a Counter component
CounterComponent = RubyWasmUi.define_component(
# Initialize component state
state: ->(props) {
{ count: props[:count] || 0 }
},
# Render the counter component
template: ->() {
RubyWasmUi::Template::Parser.parse_and_eval(<<~HTML, binding)
<div>
<div>{state[:count]}</div>
<!-- Both ButtonComponent and button-component are valid -->
<ButtonComponent
label="Increment"
on="{ click_button: -> { increment } }">
</ButtonComponent>
<button-component
label="Decrement"
on="{ click_button: -> { decrement } }"
/>
</div>
HTML
},
# Component methods
methods: {
increment: ->() {
update_state(count: state[:count] + 1)
},
decrement: ->() {
update_state(count: state[:count] - 1)
}
}
)
# Button component - reusable button with click handler
ButtonComponent = RubyWasmUi.define_component(
template: ->() {
RubyWasmUi::Template::Parser.parse_and_eval(<<~HTML, binding)
<button on="{ click: ->() { emit('click_button') } }">
{props[:label]}
</button>
HTML
}
)
# Create and mount the app
app = RubyWasmUi::App.create(CounterComponent, count: 5)
app_element = JS.global[:document].getElementById("app")
app.mount(app_element)You can also use ruby_wasm_ui as a Ruby gem with rbwasm to build your application as a WASM file.
- Add
ruby_wasm_uito yourGemfile:
# frozen_string_literal: true
source "https://rubygems.org"
gem "ruby_wasm_ui"- Install dependencies:
bundle install- Build Ruby WASM:
bundle exec rbwasm build --ruby-version 3.4 -o ruby.wasm- Pack your application files:
bundle exec rbwasm pack ruby.wasm --dir ./src::./src -o src.wasmThis command packs your Ruby files from the ./src directory into the WASM file.
Create an HTML file in the src directory that loads the WASM file:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<title>My App</title>
<script type="module">
import { DefaultRubyVM } from "https://cdn.jsdelivr.net/npm/@ruby/[email protected]/dist/browser/+esm";
const response = await fetch("../src.wasm");
const module = await WebAssembly.compileStreaming(response);
const { vm } = await DefaultRubyVM(module);
vm.evalAsync(`
require "ruby_wasm_ui"
require_relative './src/app.rb'
`);
</script>
</head>
<body>
<div id="app"></div>
</body>
</html>my-app/
├── Gemfile
├── src.wasm
└── src/
├── app.rb
└── index.html
Your src/app.rb file can use ruby_wasm_ui just like in the Quick Start example:
CounterComponent = RubyWasmUi.define_component(
state: ->(props) {
{ count: props[:count] || 0 }
},
template: ->() {
RubyWasmUi::Template::Parser.parse_and_eval(<<~HTML, binding)
<div>
<div>{state[:count]}</div>
<button on="{ click: -> { increment } }">Increment</button>
</div>
HTML
},
methods: {
increment: ->() {
update_state(count: state[:count] + 1)
}
}
)
app = RubyWasmUi::App.create(CounterComponent, count: 0)
app_element = JS.global[:document].getElementById("app")
app.mount(app_element)See the examples directory for a complete working example.
This project is currently under active development. To run the examples locally:
# Run production examples
npm run serve:examples
# Run development examples
npm run serve:examples:devMIT