Coming from Svelte
If you know Svelte, Ruby2JS provides the same reactive simplicity with Ruby syntax for your component logic.
Table of Contents
- What You Know → What You Write
- Quick Start
- Component Patterns
- SvelteKit Integration
- Why Ruby2JS for Svelte?
- Template Syntax
- Lifecycle Hook Mapping
- Key Differences
- Caching (ISR)
- Next Steps
What You Know → What You Write
| Svelte | Ruby2JS |
|---|---|
let count = 0 |
@count = 0 |
{count} |
{count} |
{#each items as item} |
{#each items as item} |
{#if condition} |
{#if condition} |
on:click={handler} |
on:click={handler} |
onMount(() => {}) |
def on_mount |
$page.params.id |
@@id |
Quick Start
@count = 0
def increment
@count += 1
end
__END__
<div>
<p>Count: {count}</p>
<button on:click={increment}>+1</button>
</div>
Component Patterns
Data Fetching with Lifecycle
@post = nil
@loading = true
def on_mount
id = @@id
fetch("/api/posts/#{id}")
.then(->(r) { r.json })
.then(->(data) {
@post = data
@loading = false
})
end
def delete_post
@post.destroy
goto('/posts')
end
__END__
{#if loading}
<p>Loading...</p>
{:else}
<article>
<h1>{post.title}</h1>
{@html post.body}
<button on:click={deletePost}>Delete</button>
</article>
{/if}
Forms and Binding
@name = ""
@email = ""
@agreed = false
def submit
return unless @agreed
data = { name: @name, email: @email }
fetch('/api/signup', method: 'POST', body: JSON.stringify(data))
end
__END__
<form on:submit|preventDefault={submit}>
<input bind:value={name} placeholder="Name">
<input bind:value={email} type="email" placeholder="Email">
<label>
<input type="checkbox" bind:checked={agreed}>
I agree to the terms
</label>
<button type="submit" disabled={!agreed}>Sign Up</button>
</form>
Each Blocks with Index and Key
@items = [
{ id: 1, name: "Apple" },
{ id: 2, name: "Banana" },
{ id: 3, name: "Cherry" }
]
def remove(id)
@items = @items.reject { |i| i[:id] == id }
end
__END__
<ul>
{#each items as item, index (item.id)}
<li>
{index + 1}. {item.name}
<button on:click={() => remove(item.id)}>Remove</button>
</li>
{/each}
</ul>
SvelteKit Integration
Ruby2JS automatically handles SvelteKit imports:
# Navigation
def go_home
goto('/')
end
def go_back
goto(-1)
end
# Access page params with @@ sigil
def on_mount
id = @@id # Becomes $page.params.id
end
Generated code:
import { goto } from '$app/navigation'
import { page } from '$app/stores'
function goHome() {
goto('/')
}
onMount(() => {
const id = $page.params.id
})
Why Ruby2JS for Svelte?
Svelte already has clean syntax—so why add Ruby? The value isn’t in syntax transformation, it’s in what Ruby brings to the table:
Full-Stack Ruby
Use the same language for your SvelteKit frontend and your backend. If you know Rails, you already know the patterns:
# Backend model (Rails)
class Post < ApplicationRecord
validates :title, presence: true
has_many :comments
end
# Frontend component (Ruby2JS → Svelte)
@posts = Post.published.order(created_at: :desc)
Built-in ORM
Direct database access in your components—no API endpoints, no fetch calls:
# Instead of this:
def on_mount
fetch('/api/posts')
.then(->(r) { r.json })
.then(->(data) { @posts = data })
end
# Write this:
@posts = Post.where(published: true).limit(10)
@categories = Category.with_post_counts
Rails Ecosystem
ActiveRecord validations, associations, and query interface—all available in your Svelte components:
@post = Post.find(@@id)
@comments = @post.comments.includes(:author)
@related = Post.where(category: @post.category).limit(3)
Syntax Benefits
The syntax improvements are modest but add up:
# Svelte lifecycle hooks need imports
import { onMount, onDestroy } from 'svelte'
onMount(() => { /* setup */ })
# Ruby2JS - just define methods
def on_mount
# setup
end
Template Syntax
The template stays Svelte—you’re just writing the script in Ruby:
Conditionals
{#if loading}
<p>Loading...</p>
{:else if error}
<p>Error: {error.message}</p>
{:else}
<p>Ready!</p>
{/if}
Loops
{#each items as item (item.id)}
<Item {item} />
{/each}
{#each items as item, index}
<p>{index}: {item}</p>
{/each}
Await Blocks
{#await fetchData}
<p>Loading...</p>
{:then data}
<p>{data.message}</p>
{:catch error}
<p>Error: {error}</p>
{/await}
Lifecycle Hook Mapping
| Svelte | Ruby2JS Method |
|---|---|
onMount |
def on_mount |
onDestroy |
def on_destroy |
beforeUpdate |
def before_update |
afterUpdate |
def after_update |
Key Differences
Snake Case Conversion
Ruby’s snake_case becomes JavaScript’s camelCase:
@user_name = "Sam" # → let userName
def handle_click; end # → function handleClick()
# In template, snake_case is converted:
{user_name} # Converted to {userName}
on:click={handle_click} # Converted to on:click={handleClick}
File Extension
Use .svelte.rb for Svelte components:
app/pages/
index.svelte.rb → index.svelte
about.svelte.rb → about.svelte
posts/
[id].svelte.rb → [id].svelte
Reactive Declarations
Svelte’s $: reactive declarations are handled through methods:
# Instead of: $: doubled = count * 2
@count = 0
def doubled
@count * 2
end
Or with explicit reactive blocks in the template:
{@const doubled = count * 2}
<p>Doubled: {doubled}</p>
Caching (ISR)
For pages that benefit from caching, add a pragma comment:
# Pragma: revalidate 60
@posts = Post.published
__END__
<ul>
{#each posts as post (post.id)}
<li>{post.title}</li>
{/each}
</ul>
See Vercel Deployment or Cloudflare Deployment for details.
Next Steps
- Svelte Template Compiler - Full Svelte support documentation
- File-Based Routing - SvelteKit-style routing
- User’s Guide - General Ruby2JS patterns
🧪 Feedback requested — Share your experience