Ruwi(Ruby Wasm Ui)
A modern web frontend framework for Ruby using ruby.wasm. Write reactive web applications using familiar Ruby syntax and patterns.
⚠️ Warning: This library is currently under development and subject to frequent breaking changes. Please use with caution and expect API changes in future versions.
Features
- 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
Quick Start
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 = Ruwi.define_component(
# Initialize component state
state: ->(props) {
{ count: props[:count] || 0 }
},
# Render the counter component
template: ->() {
Ruwi::Template::Parser.parse_and_eval(" <div>\n <div>{state[:count]}</div>\n <!-- Both ButtonComponent and button-component are valid -->\n <ButtonComponent\n label=\"Increment\"\n on=\"{ click_button: -> { increment } }\">\n </ButtonComponent>\n <button-component\n label=\"Decrement\"\n on=\"{ click_button: -> { decrement } }\"\n />\n </div>\n HTML\n },\n\n # Component methods\n methods: {\n increment: ->() {\n update_state(count: state[:count] + 1)\n },\n decrement: ->() {\n update_state(count: state[:count] - 1)\n }\n }\n)\n\n# Button component - reusable button with click handler\nButtonComponent = Ruwi.define_component(\n template: ->() {\n Ruwi::Template::Parser.parse_and_eval(<<~HTML, binding)\n <button on=\"{ click: ->() { emit('click_button') } }\">\n {props[:label]}\n </button>\n HTML\n }\n)\n\n# Create and mount the app\napp = Ruwi::App.create(CounterComponent, count: 5)\napp_element = JS.global[:document].getElementById(\"app\")\napp.mount(app_element)\n", binding)
Using as a Gem
You can also use ruwi as a Ruby gem with rbwasm to build your application as a WASM file.
Setup
- Add
ruwito yourGemfile:
# frozen_string_literal: true
source "https://rubygems.org"
gem "ruwi"
- Install dependencies:
bundle install
Building Your Application
Set up your project (first time only):
bundle exec ruwi setup
This command will:
- Configure excluded gems for WASM build
- Build Ruby WASM file
- Update
.gitignore - Create initial
src/index.htmlandsrc/index.rbfiles
The setup command will configure your project structure as follows:
my-app/
Develop Your Application
- Development server: Start a development server with file watching and auto-build:
bash bundle exec ruwi dev
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 "ruwi"
require_relative './src/index.rb'
`);
</script>
</head>
<body>
<div id="app"></div>
</body>
</html>
Your src/index.rb file can use ruwi just like in the Quick Start example:
CounterComponent = Ruwi.define_component(
state: ->(props) {
{ count: props[:count] || 0 }
},
template: ->() {
Ruwi::Template::Parser.parse_and_eval(" <div>\n <div>{state[:count]}</div>\n <button on=\"{ click: -> { increment } }\">Increment</button>\n </div>\n HTML\n },\n methods: {\n increment: ->() {\n update_state(count: state[:count] + 1)\n }\n }\n)\n\napp = Ruwi::App.create(CounterComponent, count: 0)\napp_element = JS.global[:document].getElementById(\"app\")\napp.mount(app_element)\n", binding)
See the examples directory for a complete working example.
Additional Commands:
- Rebuild Ruby WASM: Rebuild the Ruby WASM file when you add new gems:
bash bundle exec ruwi rebuild
Deployment
Pack your application files for deployment:
bundle exec ruwi pack
This command packs your Ruby files from the ./src directory into the WASM file and outputs to the dist directory for deployment.
Documentation
Development
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:dev
License
MIT