In case you haven’t heard about it, I’ve been working on a front-end framework in Ruby called Clearwater. These are a few of its main features:
- Component architecture
- Routing
- Virtual DOM
Components
Components are your bread and butter. They’ll make up most of your app. Here is an example of a Clearwater component:
class Layout
include Clearwater::Component
def render
div({ class_name: 'foo' }, [
h1({ id: 'hello-world' }, 'Hello, world!'),
])
end
end
Notice how it’s a plain Ruby class that includes the Clearwater::Component
mixin and responds to render
. The return value from render
needs to be one of the following:
String
Numeric
nil
VirtualDOM::Node
- Another
Clearwater::Component
(or, really, something that responds torender
and returns any of these) - An array containing any of the above
Also notice the DOM DSL, how we call a div
method and an h1
method. Those are defined by the Clearwater::Component
mixin. They just generate VirtualDOM::Node
objects of the appropriate kind. There are similar methods for each of the HTML5 element types.
Element DSL
The element DSL methods are simple.
div(attributes, content)
The attributes
is either a Hash
of attributes or nil
. The important thing to remember is that you must use the JS DOM API properties, not necessarily HTML-style attributes. For example, the DOM property representing the HTML class
attribute is actually className
. Because Ruby is traditionally snake_cased
rather than camelCased
, you can use snake-cased attribute names with Clearwater components, like div({ class_name: 'foo' })
.
The content
value is any valid return value for render
.
Routing
Routes are simple:
router = Clearwater::Router.new do
route 'blog' => Blog.new do
route ':article_id' => ArticleReader.new
end
end
In the above router, the path /blog
maps to the Blog
component. The path /blog/123
will map to both the Blog
and the ArticleReader
components and will set params[:article_id] = '123'
within your routed components.
Application
Your application wraps your router and your primary component:
app = Clearwater::Application.new(
component: Layout.new,
router: router
)
Then, once your document is loaded, you send the app the call
message:
$document.ready { app.call }
Any time you need to rerender your app, you simply call app.render
. Clearwater will coalesce multiple render calls that come in faster than once per animation frame (about 16.67ms) into a single render, so you can call it as much as you need to without worrying that you’re rendering too frequently.
You also don’t need to throttle render calls while the app isn’t visible to save on CPU cycles. Clearwater will withhold render calls while the app is hidden and automatically rerender when the app becomes visible again.
Example app
This is a fully functional Clearwater application:
require 'opal'
require 'clearwater'
class Layout
include Clearwater::Component
def render
h1(nil, "Hello, world!")
end
end
app = Clearwater::Application.new(
component: Layout.new
)
$document.ready { app.call }
I’ll be working on some screencasts to show off how simple it is to develop Clearwater apps. I hope it’s as fun for you as it has been for me!