Skip to content

Latest commit

 

History

History
1224 lines (812 loc) · 42.9 KB

File metadata and controls

1224 lines (812 loc) · 42.9 KB

Tips for Contributors

🏗️ Monorepo structure

This repository is a monorepo containing both the MIT-licensed core (the react_on_rails gem and the react-on-rails npm package) and the Pro packages (the react_on_rails_pro gem and the react-on-rails-pro and react-on-rails-pro-node-renderer npm packages). License boundaries are directory-based — see LICENSE.md for the authoritative list of which directories use which license.

When contributing:

  • License compliance is critical — never add Pro code to MIT-licensed directories.
  • Follow the existing directory layout for the package you are changing.

Prerequisites

Note for users: End users of react_on_rails can continue using their preferred package manager (npm, yarn, pnpm, or bun). The generators automatically detect your package manager. The pnpm commands below are for contributors working on the react_on_rails codebase itself.

  • Git hooks setup (automatic during normal setup):

Git hooks are installed automatically when you run the standard setup commands. They will run automatic linting on all changed files (staged + unstaged + untracked) - making commits fast while preventing CI failures.

  • After cloning the repo, run bin/setup from the root directory to install all dependencies.

  • After updating code via Git, to prepare all examples:

bundle && pnpm install && rake shakapacker_examples:gen_all && rake node_package && rake

See Dev Initial Setup below for initial setup details, and Running tests for more details on running tests.

IDE/IDE SETUP

It's critical to configure your IDE/editor to ignore certain directories. Otherwise, your IDE might slow to a crawl!

  • /coverage
  • /tmp
  • /gen-examples
  • /packages/react-on-rails/lib
  • /node_modules
  • /react_on_rails/spec/dummy/app/assets/webpack
  • /react_on_rails/spec/dummy/log
  • /react_on_rails/spec/dummy/node_modules
  • /react_on_rails/spec/dummy/client/node_modules
  • /react_on_rails/spec/dummy/tmp
  • /react_on_rails/spec/react_on_rails/dummy-for-generators

Example apps

The react_on_rails/spec/dummy app is an example of the various setup techniques you can use with the gem.

There are also two such apps for React on Rails Pro: one using the Node renderer and one using ExecJS.

When you add a new feature, consider adding an example demonstrating it to the example apps.

Testing your changes in an external application

You may also want to test your React on Rails changes with your own application. There are three main ways to do it: using local dependencies, Git dependencies, or tarballs.

Local version

Ruby

To make your Rails app use a local version of our gems, use

gem "react_on_rails", path: "<React on Rails root>"

and/or

gem "react_on_rails_pro", path: "<React on Rails root>/react_on_rails_pro"

Note that you will need to run bundle install after making this change, but also that you will need to restart your Rails application if you make any changes to the gem.

JS

For testing with external applications (your own Rails apps), you have several options:

Option 1: pnpm pack (Recommended)

This creates a tarball that mimics the published package:

cd <React on Rails root>
pnpm install
pnpm run build
cd packages/react-on-rails
pnpm pack  # Creates react-on-rails-<version>.tgz

Then in your external app:

pnpm add <path-to-react_on_rails>/packages/react-on-rails/react-on-rails-*.tgz

This is the most reliable approach as it exactly mimics what pnpm install react-on-rails would install.

Option 2: yalc (for rapid iteration)

If you need to iterate quickly, yalc provides a local publish/link workflow:

# Install yalc globally
pnpm add -g yalc

# In React on Rails root
cd <React on Rails root>
pnpm install
pnpm run build

# Publish to local yalc store
cd packages/react-on-rails
yalc publish

# In your external app
cd <your project root>
yalc add react-on-rails
pnpm install

After making changes, run yalc push from the package directory to update all linked apps.

Note: The internal dummy apps (react_on_rails/spec/dummy, etc.) use pnpm workspaces with workspace:* protocol. They no longer require yalc.

Example: Testing NPM changes with the dummy app

The dummy apps are part of the pnpm workspace, so changes are automatically linked.

  1. Build the package: pnpm run build (from root)
  2. Add console.log('Hello!') to clientStartup.ts
  3. Rebuild: pnpm run build
  4. Start the dummy server: cd react_on_rails/spec/dummy && foreman start
  5. Navigate to http://localhost:3000/ - you'll see the message in the browser console

Git dependencies

If you push your local changes to Git, you can use them as dependencies as follows:

Ruby

Adjust depending on the repo you pushed to and commit/branch you want to use, see Bundler documentation:

gem 'react_on_rails',
  git: 'https://github.com/shakacode/react_on_rails',
  branch: 'main'
gem 'react_on_rails_pro',
  git: 'https://github.com/shakacode/react_on_rails',
  glob: 'react_on_rails_pro/react_on_rails_pro.gemspec',
  branch: 'main'

JS

Unfortunately, not all package managers allow depending on a single subfolder of a Git repo. The examples below are for the main branch of react-on-rails package.

PNPM (recommended, v9+)

See this issue.

pnpm add "github:shakacode/react_on_rails#main&path:packages/react-on-rails"

Yarn Berry

See Yarn Git protocol documentation.

yarn add "git@github.com:shakacode/react_on_rails.git#workspace=react-on-rails&head=main"

NPM

Explicitly doesn't want to support it.

Tarball

This method works only for JS packages, not for Ruby gems.

Run pnpm pack in the package you modified, copy the generated file into your app or upload it somewhere, and run

npm install <tarball path/URL>

or the equivalent command for your package manager.

Development Setup for Gem and Node Package Contributors

Dev Initial Setup

Quick Setup (Recommended)

After checking out the repo and ensuring you have Ruby and Node version managers set up (such as rvm and nvm, or rbenv and nodenv, etc.) with the correct versions active, run:

# First, verify your versions match the project requirements
ruby -v  # Should show 4.0.x (latest), 3.3.x (minimum), or version in .ruby-version
node -v  # Should show 22.x or version in .node-version

# Then run the setup script
bin/setup

# Or skip Pro setup (for contributors without Pro access)
bin/setup --skip-pro

This single command installs all dependencies across the monorepo:

  • Root pnpm and bundle dependencies
  • Builds the node package
  • Sets up react_on_rails/spec/dummy
  • Sets up react_on_rails_pro (if present)
  • Sets up react_on_rails_pro/spec/dummy (if present)
  • Sets up react_on_rails_pro/spec/execjs-compatible-dummy (if present)

Manual Setup (Alternative)

If you prefer to set up manually or need more control:

  1. Install root dependencies:
bundle install
pnpm install
  1. Build the node package:
rake node_package
  1. Set up the dummy app:
cd react_on_rails/spec/dummy
bundle install
pnpm install

You can also run bin/console for an interactive prompt that will allow you to experiment.

Lockfile Platforms

When committing any Gemfile.lock, ensure it includes the Linux platform used by CI. If you generated or refreshed a lockfile on macOS or another non-Linux platform and x86_64-linux is missing from the PLATFORMS section, the lint workflow will fail. Run this in the directory that owns that lockfile before committing:

bundle lock --add-platform x86_64-linux

Local Node Package

Note, the example and dummy apps will use your local packages/react-on-rails folder as the react-on-rails node package. This will also be done automatically for you via the rake examples:gen_all rake task.

Side note: It's critical to use the alias section of the Webpack config to avoid a double inclusion error. This has already been done for you in the example and dummy apps, but for reference:

  resolve: {
    alias: {
      react: path.resolve('./node_modules/react'),
      'react-dom': path.resolve('./node_modules/react-dom'),
    },
  },

Install NPM dependencies and build the NPM package for react-on-rails

cd react_on_rails/
pnpm install
pnpm run build

Or run this, which builds the package, then the Webpack files for react_on_rails/spec/dummy, and runs tests in react_on_rails/spec/dummy.

# Optionally change default capybara driver
export DRIVER=selenium_firefox
cd react_on_rails/
pnpm run dummy:spec

To convert the development environment over to Shakapacker v6 instead of the default Shakapacker v8:

# Optionally change default capybara driver
export DRIVER=selenium_firefox
cd react_on_rails/
script/convert
pnpm run dummy:spec

Running tests

JS tests

cd react_on_rails/
pnpm run test

react_on_rails/spec/dummy tests

cd react_on_rails/spec/dummy
rspec

Linting, type checking and JS tests together

cd react_on_rails/
pnpm run check

Development Commands

Code Formatting

To format JavaScript/TypeScript files with Prettier:

pnpm start format

To check formatting without fixing:

pnpm start format.listDifferent

Linting

Run all linters (ESLint and RuboCop):

rake lint

Run only RuboCop:

rake lint:rubocop

For v17, new Ruby code should use long-form hash syntax instead of shorthand; shorthand cleanup is tracked for v18 in #3501.

Run only ESLint:

pnpm run lint

Bundle Size Checks

React on Rails monitors bundle sizes in CI to prevent unexpected size increases. The CI compares your PR's bundle sizes against the base branch and fails if any package increases by more than 0.5KB.

Running Locally

Check current bundle sizes:

pnpm run size

Get JSON output for programmatic use:

pnpm run size:json

Compare your branch against the base branch:

bin/compare-bundle-sizes

This script automatically:

  1. Stashes any uncommitted changes
  2. Checks out and builds the base branch (default: main)
  3. Checks out and builds your current branch
  4. Compares the size and total execution time (loading + running) and shows a detailed report

Options:

bin/compare-bundle-sizes main        # Compare against 'main'
bin/compare-bundle-sizes --hierarchical  # Group results by package

Bypassing the Check

If your PR intentionally increases bundle size (e.g., adding a new feature), you can skip the bundle size check:

# Run from your PR branch
bin/skip-bundle-size-check
git add .bundle-size-skip-branch
git commit -m "Skip bundle size check for intentional size increase"
git push

This sets your branch to skip the size check. The skip only applies to the specific branch name written to .bundle-size-skip-branch.

Important: Only skip the check when the size increase is justified. Document why the increase is acceptable in your PR description.

What Gets Measured

The CI measures sizes for:

  • react-on-rails: Raw, gzip, and brotli compressed sizes
  • react-on-rails-pro: Raw, gzip, and brotli compressed sizes
  • react-on-rails-pro-node-renderer: Raw, gzip, and brotli compressed sizes
  • Webpack bundled imports: Client-side bundle sizes when importing via webpack

Starting the Dummy App

To run the dummy app, it's CRITICAL to not just run rails s. You have to run foreman start with one of the Procfiles. If you don't do this, then webpack will not generate a new bundle, and you will be seriously confused when you change JavaScript and the app does not change. If you change the Webpack configs, then you need to restart Foreman. If you change the JS code for react-on-rails, you need to run pnpm run build in the project root.

RSpec Testing

Run rake for testing the gem and react_on_rails/spec/dummy. Otherwise, the rspec command only works for testing within the sample apps, like react_on_rails/spec/dummy.

If you run rspec at the top level, you'll see this message: require': cannot load such file -- rails_helper (LoadError)

If you run tests with COVERAGE=true, you can view the SimpleCov report at coverage/index.html.

The Turbolinks 5 gem is always bundled in the test app. Setting DISABLE_TURBOLINKS=TRUE in the environment suppresses the require_asset call in react_on_rails/spec/dummy/app/assets/javascripts/application_non_webpack.js.erb, so Turbolinks does not load at runtime — but the gem itself stays in the bundle to keep the gemset consistent with Gemfile.lock.

Run rake -T or rake -D to see testing options.

rake all_but_examples is typically best for developers, except if any generators changed.

See below for verifying changes to the generators.

Updating the Shakapacker Version for Testing

When a new version of Shakapacker is released, the pinned version used across the test suite needs to be updated. The version is specified in several places:

Source files (update manually):

  1. react_on_rails/Gemfile.development_dependencies — gem version pin
  2. react_on_rails/spec/dummy/package.json — npm version pin
  3. react_on_rails_pro/Gemfile.development_dependencies — gem version pin (Pro)
  4. react_on_rails_pro/spec/dummy/package.json — npm version pin (Pro)
  5. react_on_rails_pro/spec/execjs-compatible-dummy/Gemfile — gem version pin (Pro)
  6. react_on_rails_pro/spec/execjs-compatible-dummy/package.json — npm version pin (Pro)

Lock files (regenerated automatically):

After updating the source files above, regenerate lock files by running bundle install and pnpm install in the relevant directories:

  • react_on_rails/ and react_on_rails/spec/dummy/ (OSS)
  • react_on_rails_pro/ and react_on_rails_pro/spec/dummy/ and react_on_rails_pro/spec/execjs-compatible-dummy/ (Pro)
  • react_on_rails/Gemfile.lock and root pnpm-lock.yaml

Example apps (handled automatically):

The CI-generated example apps (under gen-examples/) automatically resolve the shakapacker version via the gem dependency. The pin_shakapacker_npm_version helper in react_on_rails/rakelib/shakapacker_examples.rake ensures the npm version matches the gem.

Updating the pnpm Fallback Version for Scaffolded CI

The react_on_rails:install generator scaffolds a GitHub Actions workflow that uses pnpm/action-setup@v4. When the target app declares packageManager: pnpm@… in its package.json, the scaffold reads the pin from there. When it does not (e.g., existing Shakapacker apps that only have pnpm-lock.yaml), the scaffold has to supply an explicit version: to the action — otherwise the workflow fails before installing dependencies.

That fallback comes from CI_PNPM_FALLBACK_VERSION in react_on_rails/lib/generators/react_on_rails/install_generator.rb.

When to bump:

  • Whenever Renovate updates the repo's own root package.json packageManager pin to a newer pnpm version. The spec keeps the fallback pin tied to a version-specific pnpm release note (in react_on_rails/spec/react_on_rails/generators/install_generator_spec.rb) fails when CI_PNPM_FALLBACK_VERSION drifts from the packageManager version in package.json; treat that failure as the signal to update the fallback version and release-note URL in the same PR.
  • Review pnpm release PRs from Renovate, at minimum on every pnpm major release and ideally on each minor as well. A new major may change the lockfile format, in which case projects scaffolded without packageManager will fail with the older pinned pnpm. Renovate may open separate PRs for package.json and CI_PNPM_FALLBACK_VERSION; merge the constant-bump PR first or batch both updates into one PR to keep CI green.

What to update together (single commit):

  1. The CI_PNPM_FALLBACK_VERSION constant value in install_generator.rb
  2. The https://github.com/pnpm/pnpm/releases/tag/v<version> URL in the comment above the constant
  3. The root package.json packageManager field when bumping manually; for Renovate PRs, verify the existing packageManager bump matches the fallback version. The spec catches drift between these values.
  4. Re-run pnpm install in the repo root so the root pnpm-lock.yaml matches

Verification:

bundle exec rspec react_on_rails/spec/react_on_rails/generators/install_generator_spec.rb \
  -e "keeps the fallback pin tied to a version-specific pnpm release note"

Users who want exact reproducibility in their generated CI can commit a packageManager: pnpm@<version> field to their own package.json; the generator then omits the fallback version: entirely.

CI Testing and Optimization

React on Rails uses a small required PR gate plus opt-in hosted validation. Contributors should validate locally before pushing, then request optimized hosted CI only when the branch is ready for remote confirmation.

CI Behavior

  • Required PR gate: Always starts for PRs and keeps the merge signal cheap.
  • Optimized hosted CI: Runs path-selected hosted suites after +ci-run-hosted, ready-for-hosted-ci, same-repository non-Dependabot release-target PRs, merge queue, main, release/* pushes, or manual dispatch. Dependabot PRs need trusted current-head +ci-run-hosted or +ci-force-full dispatch proof before hosted jobs are enabled.
  • Force-full hosted CI: Runs every hosted suite only after +ci-force-full, force-full-hosted-ci, or a manual force_full_hosted: true dispatch.
  • Docs-only changes: Expensive hosted suites skip when only documentation/metadata paths change (*.md, *.mdx, *.markdown, *.rst, *.txt, docs/, internal/, .github/ISSUE_TEMPLATE/, or .lychee.toml)

Local CI Tools

bin/ci-local - Smart Local CI Runner

Analyzes your changes and runs appropriate tests locally before pushing. By default, it auto-detects the current PR base branch and runs optimized changed-files CI:

# Auto-detect what to test based on changed files
bin/ci-local

# Same optimized mode, made explicit
bin/ci-local --changed

# Run broad local CI where practical
bin/ci-local --all

# Quick check - only fast tests, skip slow integration tests
bin/ci-local --fast

Benefits:

  • Catches CI failures before pushing
  • Skips irrelevant tests (e.g., Ruby tests when only JS changed)
  • Provides clear summary of what passed/failed

script/ci-changes-detector - Change Analysis

Analyzes git changes and recommends which CI jobs to run:

# Check what changed since main
script/ci-changes-detector origin/main

# JSON output for scripting (requires jq)
CI_JSON_OUTPUT=1 script/ci-changes-detector origin/main

Output example:

=== CI Changes Analysis ===
Changed file categories:
  • Ruby source code
  • JavaScript/TypeScript code

Recommended CI jobs:
  ✓ Lint (Ruby + JS)
  ✓ RSpec gem tests
  ✓ JS unit tests

/run-ci - Claude Code Skill

If using Claude Code, run the /run-ci skill for interactive CI execution that:

  1. Analyzes your changes
  2. Shows recommended CI jobs
  3. Asks which tests to run
  4. Executes and reports results

CI Best Practices

DO:

  • Run bin/ci-local before pushing to catch issues early
  • Use bin/ci-local --fast during rapid iteration
  • Use optimized hosted CI for final remote confirmation, and reserve force-full hosted CI for explicit broad-matrix decisions
  • Separate docs-only changes into dedicated commits/PRs when possible

DON'T:

  • Push without running local tests first
  • Mix code and docs changes if you want docs to skip CI
  • Expect the required PR gate alone to catch minimum Ruby/Node version issues (use bin/ci-local --all or force-full hosted CI for that)

Understanding CI Optimizations

The optimized detector intelligently skips unnecessary hosted work:

Change Type Optimized Hosted Behavior
Docs only (.md, docs/) Skips expensive hosted suites
Ruby code only Runs Ruby/lint coverage, skips JS-only suites
JS code only Runs JS/lint coverage, skips Ruby-only suites
Workflow changes Runs CI-infrastructure validation, no benchmarks unless explicitly labeled

For more details, see internal/contributor-info/ci-optimization.md.

CI Control Commands

React on Rails provides PR comment commands to control CI behavior:

Post one CI command per comment. If a comment contains multiple +ci-* commands, the command workflow handles only the first one.

Maintainers and contributors with a local authenticated gh session can also run bin/request-hosted-ci from a PR branch or add ready-for-hosted-ci directly. That user-token label event starts optimized hosted workflows. Agents and maintainers who want an auditable PR-visible decision should prefer the +ci-* comment commands.

+ci-run-hosted - Enable Optimized Hosted CI

Adds the hosted CI readiness label and dispatches hosted workflows for the current head SHA:

+ci-run-hosted

What it does:

  • Dispatches hosted workflows for the current head SHA
  • Creates and adds the ready-for-hosted-ci label to the PR
  • Persists across future commits - subsequent pushes run optimized hosted CI
  • Keeps script/ci-changes-detector in charge of which suites are applicable

When to use:

  • The PR has passed local validation and is ready for remote confirmation
  • Reviewers need hosted results before merge-readiness
  • You want an auditable current-head hosted CI request

+ci-force-full - Force Full Hosted CI

Bypasses optimized suite selection and dispatches every hosted suite:

+ci-force-full

What it does:

  • Dispatches hosted workflows with force_full_hosted: true
  • Creates and adds ready-for-hosted-ci and force-full-hosted-ci
  • Persists across future commits until +ci-stop-full removes the force-full override

When to use:

  • A maintainer explicitly wants the broad hosted matrix
  • The PR changes CI detection, runtime floors, package manager behavior, release/build/publishing logic, or similarly cross-cutting infrastructure
  • You need to prove optimized selection itself is not hiding a regression path

+ci-stop-hosted - Disable Hosted CI Mode

Removes hosted CI labels and returns to required-gate-only behavior:

+ci-stop-hosted

What it does:

  • Removes ready-for-hosted-ci and force-full-hosted-ci from the PR
  • Future commits use only the required gate until hosted CI is requested again
  • Does not stop currently running workflows

When to use:

  • The PR is back in active iteration and hosted CI should not rerun on every push

+ci-stop-full - Disable Only Force-Full Hosted CI

Removes the force-full override while leaving optimized hosted CI enabled if present:

+ci-stop-full

What it does:

  • Removes force-full-hosted-ci from the PR
  • Leaves ready-for-hosted-ci in place if it was present
  • Does not stop currently running workflows

When to use:

  • The broad matrix is no longer needed, but optimized hosted CI should continue

+ci-skip-hosted [reason] - Record a Hosted CI Waiver

Records that a maintainer intentionally waived hosted CI for the current PR head SHA:

+ci-skip-hosted docs-only change; markdown checks are enough

The reason is optional. If omitted, the bot records not provided.

What it does:

  • Posts an audit comment with the current head SHA
  • Does not cancel or block any workflow run
  • Removes hosted CI labels if present
  • Does not skip the required fast gate
  • Does not apply to later pushes, because the waiver is bound to the SHA in the comment

Use this when you intentionally choose not to run hosted CI before merge, especially when acting as an administrator.

+ci-status and +ci-help

Use +ci-status to summarize the current head SHA, whether the PR matches the docs-only metadata paths, whether hosted CI labels are present, and whether the current SHA has a waiver comment.

Use +ci-help to list the available CI commands.

Note: ready-for-hosted-ci records that a PR ran optimized hosted CI. force-full-hosted-ci records the separate decision to bypass optimized selection.

Legacy slash commands (/run-skipped-ci, /run-skipped-tests, and /stop-run-skipped-ci) were removed. Use +ci-* commands because GitHub's comment editor uses / for built-in slash command autocomplete.

Important Notes

  • Force-pushes: +ci-run-hosted and +ci-force-full dispatch workflows for the current head SHA before adding labels. If you force-push after commenting, the initial workflow run may test the old commit, but subsequent pushes follow the persistent labels.
  • Workflow-token labels: A label added by a workflow's GITHUB_TOKEN does not start new pull_request workflow runs by itself. That is why the comment commands dispatch workflows for the current head SHA before adding labels.
  • Fork PRs: Comment-command hosted CI does not dispatch same-repository workflows or add persistent labels for fork heads. A maintainer should push a trusted base-repository branch when release-target, Pro, or secret-backed CI is required.
  • Waivers: The +ci-skip-hosted command records the exact SHA in the PR conversation. If you push another commit, use +ci-skip-hosted again if you still intend to waive hosted CI.
  • Branch operations: Avoid deleting or force-pushing branches while workflows are running, as this may cause failures.

Benchmarking

React on Rails includes a performance benchmark workflow that measures RPS (requests per second) and latency for both Core and Pro versions.

When Benchmarks Run

  • Automatically on main: Benchmarks run on every push to main

  • On PRs with labels: Add the benchmark label to your PR to run benchmarks

  • Manual trigger: Use gh workflow run to run benchmarks with custom parameters (see https://github.com/cli/cli#installation if you don't have gh):

    # Run with default parameters
    gh workflow run benchmark.yml
    
    # Run with custom parameters
    gh workflow run benchmark.yml \
      -f rate=100 \
      -f duration=60s \
      -f connections=20 \
      -f app_version=core_only

Regression Detection

When benchmarks run, the github-action-benchmark action compares results against historical data. If performance regresses by more than 50%, the workflow will:

  1. Fail the CI check with fail-on-alert: true
  2. Post a comment on the PR explaining the regression
  3. Tag reviewers for attention

This helps catch performance regressions before they reach production.

Running Benchmarks Locally

Prerequisites: Install k6 and Vegeta.

You can also run the server in a separate terminal instead of backgrounding it.

Core benchmarks:

cd react_on_rails/spec/dummy
bin/prod-assets  # Build production assets
bin/prod &       # Start production server on port 3001
SERVER_PID=$!
cd ../..
ruby benchmarks/bench.rb
kill $SERVER_PID

Pro benchmarks:

cd react_on_rails_pro/spec/dummy
bin/prod-assets
bin/prod &       # Starts Rails server and node renderer
SERVER_PID=$!
cd ../..
PRO=true ruby benchmarks/bench.rb         # Rails benchmarks
ruby benchmarks/bench-node-renderer.rb    # Node renderer benchmarks
kill $SERVER_PID

Configuration: Both scripts support environment variables for customization (rate, duration, connections, etc.). See the script headers in benchmarks/bench.rb and benchmarks/bench-node-renderer.rb for available options. For debugging, you may want lower DURATION and/or specific ROUTES:

DURATION=5s ROUTES=/ ruby benchmarks/bench.rb

Install Generator

In your Rails app add this gem with a path to your fork.

gem 'react_on_rails', path: '../relative/path/to/react_on_rails'

Then run bundle.

The main installer can be run with ./bin/rails generate react_on_rails:install

Then add the npm package using one of these methods:

Option 1: pnpm pack (recommended)

# In React on Rails root
cd packages/react-on-rails
pnpm pack

# In your test app
pnpm add <path-to-react_on_rails>/packages/react-on-rails/react-on-rails-*.tgz

Option 2: yalc (for rapid iteration)

# Install yalc globally if needed
pnpm add -g yalc

# In React on Rails root
cd packages/react-on-rails
yalc publish

# In your test app
yalc add react-on-rails

Testing the Generator

The generators are covered by generator tests using Rails's generator testing helpers, but it never hurts to do a sanity check and explore the API. See generator-testing.md for a script on how to run the generator on a fresh project.

rake run_rspec:shakapacker_examples_basic is a great way to run tests on one generator. Once that works, you should run rake run_rspec:shakapacker_examples. Be aware that this will create a huge number of files under a /gen-examples directory. You should be sure to exclude this directory from your IDE and delete it once your testing is done.

Manual Generator Testing Workflow

For comprehensive testing of generator changes, use this manual testing workflow with dedicated test applications:

1. Set up test application with clean baseline:

# Create a test Rails app
mkdir -p {project_dir}/test-app
cd {project_dir}/test-app
rails new . --skip-javascript

# Set up for testing the generator
echo 'gem "react_on_rails", path: "../react_on_rails"' >> Gemfile
yalc add react-on-rails

# Create a clean baseline tag for testing
git init && git add . && git commit -m "Initial commit"
git tag generator_testing_base
bundle install

# Clean reset to baseline state
git clean -fd && git reset --hard && git clean -fd

2. Test generator commits systematically:

When testing specific generator improvements or fixes, test both Shakapacker scenarios:

Scenario A: No Shakapacker installed (fresh Rails app)

# Reset to clean baseline before each test
git clean -fd && git reset --hard generator_testing_base && git clean -fd

# Ensure no Shakapacker in Gemfile (remove if present)
# Edit Gemfile to update gem path: gem 'react_on_rails', path: '../path/to/main/repo'
bundle install

# Run generator - should install Shakapacker automatically
./bin/rails generate react_on_rails:install

# Verify Shakapacker was added to Gemfile and installed correctly

Scenario B: Shakapacker already installed

# Reset to clean baseline
git clean -fd && git reset --hard generator_testing_base && git clean -fd

# Add Shakapacker to Gemfile
bundle add shakapacker --strict

# Run Shakapacker installer first
./bin/rails shakapacker:install

# Edit Gemfile to update gem path: gem 'react_on_rails', path: '../path/to/main/repo'
bundle install

# Run generator - should detect existing Shakapacker
./bin/rails generate react_on_rails:install

# Verify generator adapts to existing Shakapacker setup

3. Document testing results:

For each commit tested, document:

  • Generator execution success/failure for both scenarios
  • Shakapacker installation/detection behavior
  • Component rendering in browser
  • Console output and warnings
  • File generation differences between scenarios
  • Specific issues found

This systematic approach ensures generator changes work correctly whether Shakapacker is pre-installed or needs to be installed by the generator.

Testing Generator with Yalc for React Component Functionality

When testing the install generator with new Rails apps, you need to use yalc for the JavaScript package to ensure React components work correctly. The Ruby gem path reference is insufficient for client-side rendering.

Problem: Using only the gem path (gem "react_on_rails", path: "../path") in a new Rails app results in React components not mounting on the client side, even though server-side rendering works fine.

Solution: Use both gem path and yalc for complete testing:

# In test app's Gemfile
gem 'react_on_rails', path: '../relative/path/to/react_on_rails'
# After running the install generator AND after making any changes to the React on Rails source code
cd /path/to/react_on_rails
npm run build
npx yalc publish
# CRITICAL: Push changes to all linked apps
npx yalc push

cd /path/to/test_app
npm install

# Restart development server
bin/dev

⚠️ CRITICAL DEBUGGING NOTE: Always run yalc push after making changes to React on Rails source code. Without this step, your test app won't receive the updated package, leading to confusing behavior where changes appear to have no effect.

Alternative to Yalc: npm pack (More Reliable) For a more reliable alternative that exactly mimics real package installation:

# In react_on_rails directory
npm run build
npm pack  # Creates react-on-rails-15.0.0.tgz

# In test app directory
npm install ../path/to/react_on_rails/react-on-rails-15.0.0.tgz

This approach:

  • ✅ Exactly mimics real package installation
  • ✅ No symlink issues across different filesystems
  • ✅ More reliable for CI/CD testing
  • ⚠️ Requires manual step after each change (can be scripted)

Why this is needed:

  • The gem provides Rails integration and server-side rendering
  • Yalc provides the complete JavaScript client library needed for component mounting
  • Without yalc, you'll see empty divs where React components should render

Verification:

  • Visit the hello_world page in browser
  • Check browser console for "RENDERED HelloWorld to dom node" success message
  • Confirm React component is interactive (input field updates name display)

Development Mode Console Output:

  • bin/dev (HMR): Shows HMR warnings and resource preload warnings (expected)
  • bin/dev static: Shows only resource preload warnings (cleaner output)
  • bin/dev prod: Cleanest output with minimal warnings (production-like environment)

Note: Resource preload warnings in development modes are normal and can be ignored. They occur because Shakapacker generates preload tags but scripts load asynchronously. Production mode eliminates most of these warnings.

Generator Testing Troubleshooting

Common Issues and Solutions:

  1. React components not rendering (empty divs)

    • Cause: Missing yalc setup for JavaScript package
    • Solution: Follow yalc setup steps above after running generator
  2. Generator fails with Shakapacker errors

    • Cause: Conflicting Shakapacker versions or incomplete installation
    • Solution: Clean reset and ensure consistent Shakapacker version across tests
  3. Babel configuration conflicts during yalc development

    • Cause: Both babel.config.js and package.json "babel" section defining presets
    • Solution: Remove "babel" section from package.json, keep only babel.config.js
  4. "Package.json not found" errors

    • Cause: Generator trying to access non-existent package.json files
    • Solution: Test with commits that fix this specific issue (e.g., bc69dcd0)
  5. Port conflicts during testing

    • Cause: Multiple development servers running
    • Solution: Run bin/dev kill before starting new test servers

Testing Best Practices:

  • Always use the double clean command: git clean -fd && git reset --hard && git clean -fd
  • Test both Shakapacker scenarios for comprehensive coverage
  • Document exact error messages and steps to reproduce
  • Verify React component interactivity, not just rendering
  • Test all development modes: bin/dev, bin/dev static, bin/dev prod

Pre-Commit Requirements

AUTOMATED: If you've set up Lefthook (see Prerequisites), linting runs automatically on changed files before each commit.

MANUAL OPTION: If you need to run linting manually:

# Navigate to the main react_on_rails directory
cd react_on_rails/

# Run Prettier for JavaScript/TypeScript formatting
pnpm start format

# Run ESLint for JavaScript/TypeScript linting
pnpm run lint

# Run RuboCop for Ruby linting and formatting
rake lint:rubocop

# Or run all linters together
rake lint

Git hooks automatically run:

  • Format JavaScript/TypeScript files with Prettier (on changed files only)
  • Check and fix linting issues with ESLint
  • Check and fix Ruby style issues with RuboCop (on all changed files)
  • Ensure trailing newlines on all files

Setup: Automatic during normal development setup

🤖 Best Practices for AI Coding Agents

CRITICAL WORKFLOW to prevent CI failures:

1. After Making Code Changes

# Auto-fix all linting violations after code changes
rake autofix

2. Common AI Agent Mistakes

DON'T:

  • Commit code that hasn't been linted locally
  • Ignore formatting rules when creating new files
  • Add manual formatting that conflicts with Prettier/RuboCop

DO:

  • Run rake lint after any code changes
  • Use rake autofix to automatically fix all linting violations
  • Create new files that follow existing patterns
  • Test locally before committing

4. Template File Best Practices

When creating new template files (.jsx, .rb, etc.):

  1. Copy existing template structure and patterns
  2. Run pnpm run eslint . --fix immediately after creation
  3. Verify with rake lint before committing

5. RuboCop Complexity Issues

For methods with high ABC complexity (usually formatting/display methods):

# rubocop:disable Metrics/AbcSize
def complex_formatting_method
  # ... method with lots of string interpolation/formatting
end
# rubocop:enable Metrics/AbcSize

Remember: Failing CI wastes time and resources. Always lint locally first!

Linting

All linting is performed from the docker container for CI. You will need docker and docker-compose installed locally to lint code changes via the lint container. You can lint locally by running pnpm run lint

Once you have docker and docker-compose running locally, run docker-compose build lint. This will build the reactonrails_lint docker image and docker-compose lint container. The initial build is slow, but after the install, startup is very quick.

Linting Commands

Run rake lint.

Alternately with Docker:

Run rake -D docker to see all docker linting commands for rake. rake docker:lint will run all linters. For individual rake linting commands please refer to rake -D docker for the list.

You can run specific linting for directories or files by using docker-compose run lint rubocop (file path or directory), etc.

docker-compose run lint bash sets you up to run from the container command line.

Updating Rubocop

2 files require updating to update the Rubocop version:

  1. react_on_rails.gemspec
  2. react_on_rails/spec/dummy/Gemfile

Docker CI - Test and Linting

Docker CI and Tests containers have a xvfd server automatically started for headless browser testing with selenium and Firefox.

Run docker-compose build ci to build the CI container. Run docker-compose run ci to start all rspec tests and linting. docker-compose run --entrypoint=/bin/bash will override the default CI action and place you inside the CI container in a bash session. This is what is run on Travis-CI.

Run docker-compose build tests to build the tests container. Run docker-compose run tests to start all RSpec tests.

Advice for Project Maintainers and Contributors

What do project maintainers do? What sort of work is involved? sstephenson wrote in the turbolinks repo:

Why this is not still fully merged?

📦 Demo Naming and README Standards

To keep our React on Rails demos clear, discoverable, and SEO-friendly, all demo repos follow a standardized naming and documentation structure.


✅ Repository Naming Convention

Use the format:

react_on_rails-demo-v[REACT_ON_RAILS_VERSION]-[key-topics]

Examples:

  • react_on_rails-demo-v15-ssr-auto-registration-bundle-splitting
  • react_on_rails-demo-v15-react-server-components
  • react_on_rails-demo-v15-typescript-setup
  • react_on_rails-demo-v15-cypress-setup

Why this format?

  • Clear versioning and purpose
  • Easy to discover in GitHub, search engines, and documentation
  • Consistent prefix for grouping demos together

📝 README Title Format

# React on Rails Demo: [Topics] with v[VERSION] and Rails [VERSION]

Example:

# React on Rails Demo: SSR, Auto-Registration & Bundle Splitting with v15 and Rails 8

📄 README Description Template

A fully working demo of React on Rails v[VERSION] on Rails [VERSION], showcasing [topics].

✅ Includes:
- [Topic 1]
- [Topic 2]
- [Topic 3]

📂 Repo name: `react_on_rails-demo-v[VERSION]-[topics]`

📚 Part of the [React on Rails Demo Series](https://github.com/shakacode?tab=repositories&q=react_on_rails-demo)

Example:

A fully working demo of React on Rails v15 on Rails 8, showcasing server-side rendering, file-system-based auto-registration, and intelligent bundle splitting.

✅ Includes:
- Server-Side Rendering (SSR)
- Auto-discovered components based on file structure
- Lightweight vs. heavy component splitting
- Fix for “package.json not found” install bug

📂 Repo name: `react_on_rails-demo-v15-ssr-auto-registration-bundle-splitting`

📚 Part of the [React on Rails Demo Series](https://github.com/shakacode?tab=repositories&q=react_on_rails-demo)