# A Ruby template language inspired by JSX [![Build Status](https://travis-ci.org/patbenatar/rbexy.svg?branch=master)](https://travis-ci.org/patbenatar/rbexy) Love JSX and component-based frontends, but sick of paying the costs of SPA development? Rbexy brings the elegance of JSX—operating on HTML elements and custom components with an interchangeable syntax—to the world of Rails server-rendered apps. Combine this with CSS Modules in your Webpacker PostCSS pipeline and you'll have a first-class frontend development experience while maintaining the development efficiency of Rails. _But what about Javascript and client-side behavior?_ You probably don't need as much of it as you think you do. See how far you can get with layering RailsUJS, vanilla JS, Turbolinks, and/or StimulusJS onto your server-rendered components. I think you'll be pleasantly surprised with the modern UX you're able to build while writing and maintaining less code. ## Example Use your custom Ruby class components from `.rbx` templates just like you would React components in JSX: ```jsx

Hello {@name}

Welcome to rbexy, marrying the nice parts of React templating with the development efficiency of Rails server-rendered apps.

``` after defining them in Ruby: ```ruby class HeroComponent < Rbexy::Component # or use ViewComponent, or another component lib def setup(size:) @size = size end end class ButtonComponent < Rbexy::Component def setup(to:) @to = to end end ``` with their accompying template files (also can be `.rbx`!), scoped scss files, JS and other assets (not shown). ## Getting Started (with Rails) Add it to your Gemfile and `bundle install`: ```ruby gem "rbexy" ``` _From 1.0 onward, we only support Rails 6. If you're using Rails 5, use the 0.x releases._ In `config/application.rb`: ```ruby require "rbexy/rails/engine" ``` _Not using Rails? See "Usage outside of Rails" below._ Create your first component at `app/components/hello_world_component.rb`: ```ruby class HelloWorldComponent < Rbexy::Component def setup(name:) @name = name end end ``` With a template `app/components/hello_world_component.rbx`: ```jsx

Hello {@name}

{content}
``` Add a controller, action, route, and `rbx` view like `app/views/hello_worlds/index.rbx`: ```jsx

Welcome to the world of component-based frontend development in Rails!

``` _Or you can render Rbexy components from ERB with `<%= HelloWorldComponent.new(self, name: "Nick").render %>`_ Fire up `rails s`, navigate to your route, and you should see Rbexy in action! ## Template Syntax ### Text You can put arbitrary strings anywhere. At the root: ```jsx Hello world ``` Inside tags: ```jsx

Hello world

``` As attributes: ```jsx
``` ### Comments Start a line with `#` to leave a comment: ```jsx # Comments can be at the root
# Or within tags # spanning multiple lines

Hello world

``` ### Expressions You can put ruby code anywhere that you would put text, just wrap it in `{ ... }` At the root: ```jsx {"hello world".upcase} ``` Inside a sentence: ```jsx Hello {"world".upcase} ``` Inside tags: ```jsx

{"hello world".upcase}

``` As attributes: ```jsx

Hello world

``` #### Tags within expressions To conditionalize your template: ```jsx
{some_boolean &&

Welcome

} {another_boolean ?

Option One

:

Option Two

}
``` Loops: ```jsx ``` As an attribute: ```jsx Hello World}> Content here... ``` Pass a lambda to a prop, that when called returns a tag: ```jsx {

Hello World

}}> Content here...
``` ### Tags You can put standard HTML tags anywhere. At the root: ```jsx

Hello world

``` As children: ```jsx

Hello world

``` As siblings with other tags: ```jsx

Hello world

Welcome to rbexy

``` As siblings with text and expressions: ```jsx

Hello world

{an_expression} Some arbitrary text ``` Self-closing tags: ```jsx ``` #### Attributes Text and expressions can be provided as attributes: ```jsx
``` Value-less attributes are allowed: ```jsx ``` You can splat a hash into attributes: ```jsx
``` ## Custom components You can use custom components alongside standard HTML tags: ```jsx

To the world of custom components

``` ### `Rbexy::Component` We ship with a component superclass that integrates nicely with Rails' ActionView and the controller rendering context. You can use it to easily implement custom components in your Rails app: ```ruby # app/components/page_header_component.rb class PageHeaderComponent < Rbexy::Component def setup(title:) @title = title end end ``` By default, we'll look for a template file in the same directory as the class and with a matching filename: ```jsx // app/components/page_header_component.rbx

{@title}

``` You can call this component from another `.rbx` template file (``)—either one rendered by another component class or a Rails view file like `app/views/products/index.rbx`. Or you can call it from ERB (or any other template language) like `PageHeaderComponent.new(self, title: "Hello").render`. Your components and their templates run in the same context as traditional Rails views, so you have access to all of the view helpers you're used to as well as any custom helpers you've defined in `app/helpers/`. #### Template-less components If you'd prefer to render your components entirely from Ruby, e.g. using Rails `tag` helpers, you can do so with `#call`: ```ruby class PageHeaderComponent < Rbexy::Component def setup(title:) @title = title end def call tag.h1 @title end end ``` #### Context `Rbexy::Component` implements a similar notion to React's Context API, allowing you to pass data through the component tree without having to pass props down manually. Given a template: ```jsx
``` The form component can use Rails `form_for` and then pass the `form` builder object down to any field components using context: ```ruby class FormComponent < Rbexy::Component def setup(form_object:) @form_object = form_object end def call form_for @form_object do |form| create_context(:form, form) content end end end class TextFieldComponent < Rbexy::Component def setup(field:) @field = field @form = use_context(:form) end def call @form.text_field @field end end ``` ### `ViewComponent` Using Github's view_component library? Rbexy ships with a provider that'll resolve your RBX tags like `", MyRuntime.new(MyComponentProvider.new) ) ``` Or implement your own runtime, so long as it conforms to the API: * `#rbexy_tag` that returns a tag builder conforming to the API of `ActionView::Helpers::TagHelpers::TagBuilder` * `#evaluate(code)` that evals the given string of ruby code ## Development ``` docker-compose build docker-compose run rbexy rspec ``` Or auto-run tests with guard if you prefer: ``` docker-compose run rbexy guard ``` ## Contributing Bug reports and pull requests are welcome on GitHub at https://github.com/[USERNAME]/rbexy. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [code of conduct](https://github.com/[USERNAME]/rbexy/blob/master/CODE_OF_CONDUCT.md). ## License The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT). ## Code of Conduct Everyone interacting in the Rbexy project's codebases, issue trackers, chat rooms and mailing lists is expected to follow the [code of conduct](https://github.com/[USERNAME]/rbexy/blob/master/CODE_OF_CONDUCT.md).