npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

tombolo

v0.10.0

Published

Mount React components in Rails views with optional server-side rendering

Readme

Tombolo

Gem Version npm CI MIT License

Tombolo is a lightweight alternative to react-rails for mounting React islands in your Rails views. It provides a react_component view helper, client-side mounting via a component map, and optional server-side rendering through ExecJS.

Built for React 18+, Tombolo targets modern Rails apps using jsbundling-rails (or any JS bundler) and propshaft, with no asset pipeline integration — just register your components and write a few lines of JavaScript.

Compatibility

| Dependency | Versions | |------------|----------| | Ruby | >= 3.2 | | Rails | >= 7.0 | | React | 18, 19 |

Installation

Add the gem to your Gemfile:

gem "tombolo"
# gem "execjs"  # required for server-side rendering

Install the npm package:

npm install tombolo
# or
pnpm add tombolo

Optionally, run the install generator to create a config initializer (config/initializers/tombolo.rb) and an SSR entry point (app/javascript/prerender.ts):

bin/rails generate tombolo:install

Usage

Rails helper

Render a React component from any view or partial:

<%= react_component("Greeting", props: { name: "World" }) %>

This renders a <div> with data-react-component and data-react-props attributes that the JavaScript side picks up.

Client-side mounting

Tombolo takes a component map — an object mapping component names to React components. The easiest way to create one is a barrel file (a module that re-exports from other modules):

// app/javascript/components/index.ts
export { Greeting } from "./Greeting";
export { SearchForm } from "./SearchForm";

With Turbo

For apps using Turbo, use Tombolo.mount and Tombolo.unmount directly:

import * as Tombolo from "tombolo";
import * as components from "./components";

document.addEventListener("turbo:load", () => Tombolo.mount(components));
document.addEventListener("turbo:before-cache", () => Tombolo.unmount());

Without Turbo

For apps without Turbo, Tombolo.start mounts components on DOMContentLoaded (or immediately if the DOM is already loaded):

import * as Tombolo from "tombolo";
import * as components from "./components";

Tombolo.start(components);

Scoped mounting

Both Tombolo.mount and Tombolo.unmount accept an optional scope parameter to limit operations to a subtree of the DOM:

Tombolo.mount(components, document.getElementById("sidebar"));

Server-side rendering

SSR is optional and requires the execjs gem.

Create a server entry point that registers your components:

// app/javascript/prerender.ts
import { registerServerRenderer } from "tombolo/server";
import * as components from "./components";

registerServerRenderer(components);

Build it with esbuild (or your bundler of choice) as a CommonJS bundle that ExecJS can evaluate:

esbuild app/javascript/prerender.ts --bundle --platform=neutral --outfile=app/assets/builds/prerender.js

Then use prerender: true in your views:

<%= react_component("Greeting", props: { name: "World" }, prerender: true) %>

When a component is prerendered, the server-rendered HTML is placed inside the div and Tombolo uses hydrateRoot instead of createRoot on the client. This preserves interactivity without a full re-render.

The default server bundle path is app/assets/builds/prerender.js. To customize it, add an initializer:

# config/initializers/tombolo.rb
Tombolo.configure do |config|
  config.server_bundle = "path/to/your/bundle.js"
end

Named render scopes

If your app needs multiple SSR bundles with different sets of components, you can register named render scopes. Each scope gets its own isolated ExecJS runtime:

Tombolo.configuration.server_bundles[:admin] = "app/assets/builds/admin/prerender.js"
Tombolo.configuration.server_bundles[:storefront] = "app/assets/builds/storefront/prerender.js"

Then reference the scope by name:

<%= react_component("Dashboard", prerender: :admin, props: { ... }) %>
<%= react_component("ProductGrid", prerender: :storefront, props: { ... }) %>
<%= react_component("Greeting", prerender: true, props: { ... }) %>  <%# uses :default %>

prerender: true uses the :default bundle. prerender: :name uses a named bundle.

Configuration

Tombolo.configure do |config|
  # Convert snake_case prop keys to camelCase (default: false)
  config.camelize_props = true

  # Path to the default server-side JS bundle for SSR
  # (default: "app/assets/builds/prerender.js")
  config.server_bundle = "app/assets/builds/prerender.js"

  # Named server bundles for isolated SSR render scopes
  # config.server_bundles[:admin] = "path/to/admin/prerender.js"
end

camelize_props can also be overridden per-call:

<%= react_component("Greeting", props: { first_name: "World" }, camelize_props: true) %>

API reference

JavaScript

mount(components, scope?)

Scans scope (default: document) for elements with a data-react-component attribute. For each element, looks up the component by name, parses props from data-react-props, and mounts it. Elements with a data-react-prerender attribute are hydrated with hydrateRoot; all others use createRoot. Already-mounted elements are skipped.

unmount(scope?)

Unmounts all tracked React roots within scope (default: document).

start(components)

Calls mount on DOMContentLoaded, or immediately if the DOM is already loaded.

registerServerRenderer(components)

Imported from tombolo/server.

Assigns a renderComponent(name, propsJson) function to globalThis, making it callable from ExecJS. Used in server entry points for SSR.

Ruby

react_component(name, props: {}, prerender: false, camelize_props: nil)

Renders a <div> with data-react-component and data-react-props attributes. When prerender: true, the component is rendered on the server via ExecJS using the :default bundle. Pass a Symbol (e.g. prerender: :admin) to use a named render scope instead. Pass camelize_props: true to convert snake_case prop keys to camelCase, or set it globally in the configuration.

Tombolo.configure { |config| ... }

See Configuration above.

Migrating from react-rails

Helper signature. react-rails passes props as a positional argument, Tombolo uses a keyword argument:

# react-rails
react_component("Name", { title: "Hello" })

# Tombolo
react_component("Name", props: { title: "Hello" })

No html_options argument. react-rails accepts a third argument for HTML attributes on the wrapper div. Tombolo does not support this — wrap the helper call in your own tag if you need custom attributes.

No asset pipeline integration. Tombolo does not ship a component generator or integrate with Sprockets/Webpacker. You manage your JavaScript build yourself with jsbundling-rails or an equivalent.

SSR setup. Replace any server_rendering.js pack with a prerender.ts entry point that calls registerServerRenderer. See Server-side rendering above.

Contributing

Bug reports and pull requests are welcome on GitHub.

License

MIT