{"id":13878427,"url":"https://github.com/patbenatar/rbexy","last_synced_at":"2025-12-26T18:35:58.229Z","repository":{"id":42563595,"uuid":"298320092","full_name":"patbenatar/rbexy","owner":"patbenatar","description":"A Ruby template language and component framework inspired by JSX and React","archived":false,"fork":false,"pushed_at":"2024-03-25T20:17:17.000Z","size":274,"stargazers_count":33,"open_issues_count":11,"forks_count":5,"subscribers_count":3,"default_branch":"master","last_synced_at":"2024-04-14T09:59:22.540Z","etag":null,"topics":["rails","ruby"],"latest_commit_sha":null,"homepage":"","language":"Ruby","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/patbenatar.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE.txt","code_of_conduct":"CODE_OF_CONDUCT.md","threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2020-09-24T15:29:47.000Z","updated_at":"2024-08-06T08:47:03.408Z","dependencies_parsed_at":"2024-08-06T08:46:59.659Z","dependency_job_id":"6daa06c1-215d-4b36-8d7c-6d9bfeda974c","html_url":"https://github.com/patbenatar/rbexy","commit_stats":{"total_commits":120,"total_committers":4,"mean_commits":30.0,"dds":"0.10833333333333328","last_synced_commit":"3f1a56a03af7adf3f4abb2ac87a58266c37dcef4"},"previous_names":[],"tags_count":29,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/patbenatar%2Frbexy","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/patbenatar%2Frbexy/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/patbenatar%2Frbexy/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/patbenatar%2Frbexy/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/patbenatar","download_url":"https://codeload.github.com/patbenatar/rbexy/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":226138849,"owners_count":17579496,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["rails","ruby"],"created_at":"2024-08-06T08:01:49.365Z","updated_at":"2025-12-26T18:35:58.183Z","avatar_url":"https://github.com/patbenatar.png","language":"Ruby","funding_links":[],"categories":["Ruby"],"sub_categories":[],"readme":"# A Ruby template language inspired by JSX\n\n[![Build Status](https://github.com/patbenatar/rbexy/actions/workflows/main.yml/badge.svg?branch=master)](https://github.com/patbenatar/rbexy/actions?query=branch%3Amaster)\n\n* [Getting Started](#getting-started-with-rails)\n* [Template Syntax](#template-syntax)\n* [Components](#components)\n  * [`Rbexy::Component`](#rbexycomponent)\n  * [Usage with any component library](#usage-with-any-component-library)\n* [Fragment caching in Rails](#fragment-caching-in-rails)\n* [Advanced](#advanced)\n  * [Component resolution](#component-resolution)\n  * [AST Transforms](#ast-transforms)\n  * [Usage outside of Rails](#usage-outside-of-rails)\n\n## Manifesto\n\nLove 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.\n\nCombine 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.\n\n_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.\n\n## Example\n\nUse your custom Ruby class components from `.rbx` templates just like you would React components in JSX:\n\n```jsx\n\u003cbody\u003e\n  \u003cHero size=\"fullscreen\" {**splat_some_attributes}\u003e\n    \u003ch1\u003eHello {@name}\u003c/h1\u003e\n    \u003cp\u003eWelcome to rbexy, marrying the nice parts of React templating with the development efficiency of Rails server-rendered apps.\u003c/p\u003e\n    \u003cButton to={about_path}\u003eLearn more\u003c/Button\u003e\n  \u003c/Hero\u003e\n\u003c/body\u003e\n```\n\nafter defining them in Ruby:\n\n```ruby\nclass HeroComponent \u003c Rbexy::Component # or use ViewComponent, or another component lib\n  def setup(size:)\n    @size = size\n  end\nend\n\nclass ButtonComponent \u003c Rbexy::Component\n  def setup(to:)\n    @to = to\n  end\nend\n```\n\nwith their accompying template files (also can be `.rbx`!), scoped scss files, JS and other assets (not shown).\n\n## Getting Started (with Rails)\n\nAdd it to your Gemfile and `bundle install`:\n\n```ruby\ngem \"rbexy\"\n```\n\n_From 1.0 onward, we only support Rails 6. If you're using Rails 5, use the 0.x releases._\n\n_Not using Rails? See \"Usage outside of Rails\" below._\n\nCreate your first component at `app/components/hello_world_component.rb`:\n\n```ruby\nclass HelloWorldComponent \u003c Rbexy::Component\n  def setup(name:)\n    @name = name\n  end\nend\n```\n\nWith a template `app/components/hello_world_component.rbx`:\n\n```jsx\n\u003cdiv\u003e\n  \u003ch1\u003eHello {@name}\u003c/h1\u003e\n  {content}\n\u003c/div\u003e\n```\n\nAdd a controller, action, route, and `rbx` view like `app/views/hello_worlds/index.rbx`:\n\n```jsx\n\u003cHelloWorld name=\"Nick\"\u003e\n  \u003cp\u003eWelcome to the world of component-based frontend development in Rails!\u003c/p\u003e\n\u003c/HelloWorld\u003e\n```\n\nFire up `rails s`, navigate to your route, and you should see Rbexy in action!\n\n## Template Syntax\n\nYou can use Ruby code within brackets:\n\n```jsx\n\u003cp class={@dynamic_class}\u003e\n  Hello {\"world\".upcase}\n\u003c/p\u003e\n```\n\nYou can splat a hash into attributes:\n\n```jsx\n\u003cdiv {**{class: \"myClass\"}} {**@more_attrs}\u003e\u003c/div\u003e\n```\n\nYou can use HTML or component tags within expressions. e.g. to conditionalize a template:\n\n```jsx\n\u003cdiv\u003e\n  {some_boolean \u0026\u0026 \u003ch1\u003eWelcome\u003c/h1\u003e}\n  {another_boolean ? \u003cp\u003eOption One\u003c/p\u003e : \u003cp\u003eOption Two\u003c/p\u003e}\n\u003c/div\u003e\n```\n\nOr in loops:\n\n```jsx\n\u003cul\u003e\n  {[1, 2, 3].map { |n| \u003cli\u003e{n}\u003c/li\u003e }}\n\u003c/ul\u003e\n```\n\nBlocks:\n\n```jsx\n{link_to \"/\" do\n  \u003cspan\u003eClick me\u003c/span\u003e\nend}\n```\n\nPass a tag to a component as an attribute:\n\n```jsx\n\u003cHero title={\u003ch1\u003eHello World\u003c/h1\u003e}\u003e\n  Content here...\n\u003c/Hero\u003e\n```\n\nOr pass a lambda as an attribute, that when called returns a tag:\n\n```jsx\n\u003cHero title={-\u003e { \u003ch1\u003eHello World\u003c/h1\u003e }}\u003e\n  Content here...\n\u003c/Hero\u003e\n```\n\n_Note that when using tags inside blocks, the block must evaluate to a single root element. Rbexy behaves similar to JSX in this way. E.g.:_\n\n```\n# Do\n-\u003e { \u003cspan\u003e\u003ci\u003eHello\u003c/i\u003e World\u003c/span\u003e }\n\n# Don't\n-\u003e { \u003ci\u003eHello\u003c/i\u003e World }\n```\n\nStart a line with `#` to leave a comment:\n\n```jsx\n# Private note to self that won't be rendered in the final HTML\n```\n\n## Components\n\nYou can use Ruby classes as components alongside standard HTML tags:\n\n```jsx\n\u003cdiv\u003e\n  \u003cPageHeader title=\"Welcome\" /\u003e\n  \u003cPageBody\u003e\n    \u003cp\u003eTo the world of custom components\u003c/p\u003e\n  \u003c/PageBody\u003e\n\u003c/div\u003e\n```\n\nBy default, Rbexy will resolve `PageHeader` to a Ruby class called `PageHeaderComponent` and render it with the view context, attributes, and its children: `PageHeaderComponent.new(self, title: \"Welcome\").render_in(self, \u0026block)`. This behavior is customizable, see \"Component resolution\" below.\n\n### `Rbexy::Component`\n\nWe 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:\n\n```ruby\n# app/components/page_header_component.rb\nclass PageHeaderComponent \u003c Rbexy::Component\n  def setup(title:)\n    @title = title\n  end\nend\n```\n\nBy default, we'll look for a template file in the same directory as the class and with a matching filename:\n\n```jsx\n// app/components/page_header_component.rbx\n\u003ch1\u003e{@title}\u003c/h1\u003e\n```\n\nYour 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/` or via `helper_method` in your controller.\n\n#### Template-less components\n\nIf you'd prefer to render your components entirely from Ruby, you can do so by implementing `#call`:\n\n```ruby\nclass PageHeaderComponent \u003c Rbexy::Component\n  def setup(title:)\n    @title = title\n  end\n\n  def call\n    tag.h1 @title\n  end\nend\n```\n\n#### Context\n\n`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.\n\nGiven a template:\n\n```jsx\n\u003cForm\u003e\n  \u003cTextField field={:title} /\u003e\n\u003c/Form\u003e\n```\n\nThe form component can use Rails `form_for` and then pass the `form` builder object down to any field components using context:\n\n```ruby\nclass FormComponent \u003c Rbexy::Component\n  def setup(form_object:)\n    @form_object = form_object\n  end\n\n  def call\n    form_for @form_object do |form|\n      create_context(:form, form)\n      content\n    end\n  end\nend\n\nclass TextFieldComponent \u003c Rbexy::Component\n  def setup(field:)\n    @field = field\n    @form = use_context(:form)\n  end\n\n  def call\n    @form.text_field @field\n  end\nend\n```\n\n#### Usage with ERB\n\nWe recommend using `Rbexy::Component` with the rbx template language, but if you prefer ERB... a component's template can be `.html.erb` and you  can render a component from ERB like so:\n\nRails 6.1:\n\n```erb\n\u003c%= render PageHeaderComponent.new(self, title: \"Welcome\") do %\u003e\n  \u003cp\u003eChildren...\u003c/p\u003e\n\u003c% end \u003e\n```\n\nRails 6.0 or earlier:\n\n```erb\n\u003c%= PageHeaderComponent.new(self, title: \"Welcome\").render_in(self) %\u003e\n```\n\n### Usage with any component library\n\nYou can use the rbx template language with other component libraries like Github's view_component. You just need to tell Rbexy how to render the component:\n\n```ruby\n# config/initializers/rbexy.rb\nRbexy.configure do |config|\n  config.component_rendering_templates = {\n    children: \"{capture{%{children}}}\",\n    component: \"::%{component_class}.new(%{view_context},%{kwargs}).render_in%{children_block}\"\n  }\nend\n```\n\n## Fragment caching in Rails\n\n`.rbx` templates integrate with Rails fragment caching, automatically cachebusting when the template or its render dependencies change.\n\nIf you're using `Rbexy::Component`, you can further benefit from component cachebusting where the fragment cache will be busted if any dependent component's template _or_ class definition changes.\n\nAnd you can use `\u003cRbexy.Cache\u003e`, a convenient wrapper for the Rails fragment cache:\n\n```rbx\n\u003cRbexy.Cache key={...}\u003e\n  \u003cp\u003eFragment here...\u003c/p\u003e\n  \u003cMyButton /\u003e\n\u003c/Rbexy.Cache\u003e\n```\n\n## Advanced\n\n### Component resolution\n\nBy default, Rbexy resolves component tags to Ruby classes named `#{tag}Component`, e.g.:\n\n* `\u003cPageHeader /\u003e` =\u003e `PageHeaderComponent`\n* `\u003cAdmin.Button /\u003e` =\u003e `Admin::ButtonComponent`\n\nYou can customize this behavior by providing a custom resolver:\n\n```ruby\n# config/initializers/rbexy.rb\nRbexy.configure do |config|\n  config.element_resolver = MyResolver.new\nend\n```\n\nWhere `MyResolver` implements the following API:\n\n* `component?(name: string, template: Rbexy::Template) =\u003e Boolean`\n* `component_class(name: string, template: Rbexy::Template) =\u003e T`\n\nSee `lib/rbexy/component_resolver.rb` for an example.\n\n#### Auto-namespacing\n\nWant to namespace your components but sick of typing `Admin.` in front of every component call? Rbexy's default `ComponentResolver` implementation has an option for that:\n\n```ruby\n# config/initializers/rbexy.rb\nRbexy.configure do |config|\n  config.element_resolver.component_namespaces = {\n    Rails.root.join(\"app\", \"views\", \"admin\") =\u003e %w[Admin],\n    Rails.root.join(\"app\", \"components\", \"admin\") =\u003e %w[Admin]\n  }\nend\n```\n\nNow any calls to `\u003cButton\u003e` made from `.rbx` views within `app/views/admin/` or from component templates within `app/components/admin/` will first check for `Admin::ButtonComponent` before `ButtonComponent`.\n\n### AST Transforms\n\nYou can hook into Rbexy's compilation process to mutate the abstract syntax tree. This is both useful and dangerous, so use with caution.\n\nAn example use case is automatically scoping CSS class names if you're using something like CSS Modules. Here's an oversimplified example of this:\n\n```ruby\n# config/initializers/rbexy.rb\nRbexy.configure do |config|\n  config.transforms.register(Rbexy::Nodes::HTMLAttr) do |node, context|\n    if node.name == \"class\"\n      class_list = node.value.split(\" \")\n      node.value.content = scope_names(class_list, scope: context.template.identifier)\n    end\n  end\nend\n```\n\n### Usage outside of Rails\n\nRbexy compiles your template into ruby code, which you can then execute in any context you like. Subclass `Rbexy::Runtime` to add methods and instance variables that you'd like to make available to your template.\n\n```ruby\nclass MyRuntime \u003c Rbexy::Runtime\n  def initialize\n    super\n    @an_ivar = \"Ivar value\"\n  end\n\n  def a_method\n    \"Method value\"\n  end\nend\n\nRbexy.evaluate(\"\u003cp class={a_method}\u003e{@an_ivar}\u003c/p\u003e\", MyRuntime.new)\n```\n\n## Development\n\n```\ndocker-compose build\ndocker-compose run rbexy bin/test\n```\n\nOr auto-run tests with guard if you prefer:\n\n```\ndocker-compose run rbexy guard\n```\n\nIf you want to run against the supported versions of Rails, use\nAppraisal:\n\n```\ndocker-compose run rbexy bundle exec appraisal bin/test\n```\n\nWhen updating dependency versions in gemspec, you also need to regenerate the appraisal gemspecs with:\n\n```\ndocker-compose run rbexy bundle exec appraisal install\n```\n\n## Debugging TemplatePath methods being called\nWhen a new version of Rails is released, we need to check what methods are being\ncalled on `Rbexy::Component::TemplatePath` to make sure we always return\na TemplatePath, not a string due to how we handle `TemplatePath`s\ninternally.\n\nTo list all methods being called, enable `RBEXY_TEMPLATE_PATH_DEBUG` and\nrun tests:\n\n```\ndocker-compose run -e RBEXY_TEMPLATE_PATH_DEBUG=1 rbexy bundle exec appraisal bin/test\n```\n\n## Contributing\n\nBug reports and pull requests are welcome on GitHub at https://github.com/patbenatar/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/patbenatar/rbexy/blob/master/CODE_OF_CONDUCT.md).\n\n## License\n\nThe gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).\n\n## Code of Conduct\n\nEveryone 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/patbenatar/rbexy/blob/master/CODE_OF_CONDUCT.md).\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fpatbenatar%2Frbexy","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fpatbenatar%2Frbexy","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fpatbenatar%2Frbexy/lists"}