rux
Rux is a JSX-inspired way to write HTML tags in your Ruby code. It can be used to render view components in Rails via the rux-rails gem. This repo however contains only the rux parser itself.
Introduction
A bit of background before we dive into how to use rux.
React and JSX
React mainstreamed the idea of composing websites from a series of components. To make it conceptually easier to transition from HTML templates to Javascript components, React also introduced an HTML-based syntax called JSX that allows developers to embed HTML into their Javascript code.
Rails View Components
For a long time, Rails didn’t really have any support for components, preferring to rely on HTML template languages like ERB and HAML. The fine folks at Github however decided components could work well in Rails and released their view_component framework. There was even some talk about merging view_component into Rails core as ActionView::Component
, but unfortunately it looks like that won’t be happening.
NOTE: I’m going to be focusing on Rails examples here using the view_component gem, but rendering views from a series of components is a framework-agnostic idea.
View Component Example
A view component is just a class. The actual view portion is usually stored in a secondary template file that the component renders in the context of an instance of that class. For example, here’s a very basic view component that displays a person’s name on the page:
# app/components/name_component.rb class NameComponent < ViewComponent::Base def initialize(first_name:, last_name:) @first_name = first_name @last_name = last_name end end
HTML in Your Ruby
Rux does one thing: it lets you write HTML in your Ruby code. Here’s the name component example from earlier rewritten in rux (sorry about the syntax highlighting, Github doesn’t know about rux yet).
call
method instead of creating a separate template file.
Next, we’ll run the ruxc
tool to translate the rux code into Ruby code, eg. ruxc app/components/name_component.rux
. Here’s the result:
class NameComponent < ViewComponent::Base def initialize(first_name:, last_name:) @first_name = first_name @last_name = last_name end def call Rux.tag("span") { Rux.create_buffer.tap { |_rux_buf_,| _rux_buf_ << @first_name _rux_buf_ << " " _rux_buf_ << @last_name }.to_s } end end
As you can see, the span tag was converted to a Rux.tag
call. The instance variables containing the first and last names are concatenated together and rendered inside the span.
Composing Components
Things get even more interesting when it comes to rendering components inside other components. Let’s create a greeting component that makes use of the name component:
end
end” dir=”auto”>
# app/components/greeting_component.rux class GreetingComponent < ViewComponent::Base def call <div> Hey there <NameComponent first-name="Homer" last-name="Simpson" />! div> end end
The ruxc
tool produces:
class GreetingComponent < ViewComponent::Base def call Rux.tag("div") { Rux.create_buffer.tap { |_rux_buf_,| _rux_buf_ << " Hey there " _rux_buf_ << render(NameComponent.new(first_name: "Homer", last_name: "Simpson")) _rux_buf_ << "! " }.to_s } end end
The
tag was translated into an instance of the NameComponent
class and the attributes into its keyword arguments.
NOTE: The render
method is provided by ViewComponent::Base
.
Embedding Ruby
Since rux code is translated into Ruby code, anything goes. You’re free to put any valid Ruby statements inside the curly braces.
For example, let’s say we want to change our greeting component to greet a variable number of people:
Keyword Arguments Only
Any view component that will be rendered by rux must only accept keyword arguments in its constructor. For example:
class MyComponent < ViewComponent::Base # GOOD def initialize(first_name:, last_name:) end # BAD def initialize(first_name, last_name) end # BAD def initialize(first_name, last_name = 'Simpson') end end
In other words, positional arguments are not allowed. This is because there’s no such thing as a positional HTML attribute – all HTML attributes are key/value pairs. So, in order to match up with HTML, rux components are written with keyword arguments.
Note also that the rux parser will replace dashes with underscores in rux tag attributes to adhere to both HTML and Ruby syntax conventions, since HTML attributes use dashes while Ruby keyword arguments use underscores. For example, here’s how to write a rux tag for MyComponent
above:
” dir=”auto”>
<MyComponent first-name="Homer" last-name="Simpson" />
Notice that the rux attribute “first-name” is passed to MyComponent#initialize
as “first_name”.
How it Works
Translating rux code (Ruby + HTML tags) into Ruby code happens in three phases: lexing, parsing, and emitting. The lexer phase is implemented as a wrapper around the lexer from the Parser gem that looks for specific patterns in the token stream. When it finds an opening HTML tag, it hands off lexing to the rux lexer. When the tag ends, the lexer continues emitting Ruby tokens, and so on.
In the parsing phase, the token stream is transformed into an intermediate representation of the code known as an abstract syntax tree, or AST. It’s the parser’s job to work out which tags are children of other tags, associate attributes with tags, etc.
Finally it’s time to generate Ruby code in the e
%=span>%=span>%#span>%=>%=>