refactor: add RenderRequest, JsCodeBuilder, and RenderingStrategy abstractions#3094
refactor: add RenderRequest, JsCodeBuilder, and RenderingStrategy abstractions#3094
Conversation
…tractions (#2905) Introduce three new abstractions to replace runtime react_on_rails_pro? checks in the rendering pipeline (Step 1+2 of issue #2905): - RenderRequest: structured data object encapsulating everything needed for a server render, replacing ad-hoc parameter passing - JsCodeBuilder: structured builder with overridable section methods, replacing heredoc-based JS code generation - RenderingStrategy: strategy interface with ExecJsStrategy (core) and NodeStrategy (Pro) implementations, replacing conditional dispatch All new code is additive — the main rendering path in helper.rb still uses the existing ServerRenderingPool and ServerRenderingJsCode classes. The switchover to the new strategy pattern will happen in a follow-up PR. Configuration wiring: - ReactOnRails.rendering_strategy and ReactOnRails.js_code_builder accessors added, set by engine initializers at boot time - Core engine sets ExecJsStrategy as default - Pro engine overrides with NodeStrategy and Pro JsCodeBuilder Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
|
Note Reviews pausedIt looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the Use the following commands to manage reviews:
Use the checkboxes below for quick actions:
WalkthroughAdds three new abstractions—RenderRequest, RenderingStrategy (with ExecJsStrategy), and JsCodeBuilder—plus Pro-specific subclasses and engine wiring; exposes module-level accessors for strategy and builder. Changes are additive; core rendering path is not yet swapped. Changes
Sequence Diagram(s)sequenceDiagram
participant Helper as Helper (caller)
participant RenderReq as RenderRequest
participant Builder as JsCodeBuilder
participant Strategy as RenderingStrategy
participant Pool as ServerRenderingPool
Helper->>+RenderReq: build render_request(...)
RenderReq-->>-Helper: return RenderRequest
Helper->>+Strategy: reset_if_bundle_changed()
Strategy-->>-Helper: (maybe resets pool)
Helper->>+Strategy: execute(render_request)
Strategy->>+RenderReq: render_request.to_js
RenderReq-->>-Strategy: JS string
Strategy->>+Pool: exec_server_render_js(js, render_options)
Pool-->>-Strategy: rendered result
Strategy-->>-Helper: result
Estimated code review effort🎯 4 (Complex) | ⏱️ ~45 minutes Possibly related PRs
Poem
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
Review: Strategy Pattern Refactoring (Steps 1+2)Overall this is a clean, well-scoped additive PR. The three abstractions map clearly to their purpose, the existing rendering path is untouched, and the 23 new specs are well-structured. A few things worth addressing before Step 3 wires this into production: Bugs / correctness
Design / maintainability
Minor observations
🤖 Generated with Claude Code |
Greptile SummaryThis PR introduces three new additive abstractions — Confidence Score: 5/5Safe to merge — all changes are additive, existing rendering path is untouched, and new code has 23 passing specs. All findings are P2 style/consistency suggestions (lazy-init inconsistency between the two accessors, module-level methods placed in configuration.rb, misleading method name in Pro builder). No P0/P1 defects were identified. The new code faithfully ports the existing JS generation logic and the engine initializer ordering is correct. react_on_rails/lib/react_on_rails/configuration.rb — the rendering_strategy getter lacks a lazy default that js_code_builder has, which will need addressing before Step 3 wires the strategy into the live path.
|
| Filename | Overview |
|---|---|
| react_on_rails/lib/react_on_rails/configuration.rb | Adds rendering_strategy and js_code_builder module-level accessors; js_code_builder has a lazy default but rendering_strategy does not, creating an inconsistency that may cause nil errors when the strategy is wired in. |
| react_on_rails/lib/react_on_rails/render_request.rb | New data object encapsulating SSR parameters; clean keyword-arg constructor, correct unicode escaping in props_string, and server bundle validation guard. |
| react_on_rails/lib/react_on_rails/js_code_builder.rb | New base JS builder with overridable section methods; correctly generates an IIFE with railsContext, store inits, props, and the render call. |
| react_on_rails_pro/lib/react_on_rails_pro/js_code_builder.rb | Faithful port of ServerRenderingJsCode with RSC support, pre-hooks, and parameterized IIFE; resolve_render_function_name returns a JS expression not just a name, making the method name misleading. |
| react_on_rails/lib/react_on_rails/rendering_strategy/exec_js_strategy.rb | Thin wrapper around RubyEmbeddedJavaScript; correctly delegates execute/reset/reset_if_bundle_changed to existing pool class. |
| react_on_rails_pro/lib/react_on_rails_pro/rendering_strategy/node_strategy.rb | Thin wrapper around ProRendering; correctly delegates all three strategy methods and properly includes the RenderingStrategy interface module. |
| react_on_rails/lib/react_on_rails/engine.rb | Adds after_initialize block using |
| react_on_rails_pro/lib/react_on_rails_pro/engine.rb | Adds after_initialize block that overrides core's default rendering strategy with NodeStrategy and Pro JsCodeBuilder; ordering relative to core engine is correct since Pro loads after core. |
Reviews (1): Last reviewed commit: "refactor: add RenderRequest, JsCodeBuild..." | Re-trigger Greptile
There was a problem hiding this comment.
🧹 Nitpick comments (2)
react_on_rails_pro/lib/react_on_rails_pro/js_code_builder.rb (1)
58-62: Clarify the regex replacement intent with a comment.The regex
/\\(\\s*\\)\\s*$/that replaces trailing empty parentheses with component name and props is non-obvious. A brief inline comment explaining this transformation would help future maintainers understand the RSC request rewriting mechanism.📝 Suggested comment
if (typeof generateRSCPayload !== 'function') { globalThis.generateRSCPayload = function generateRSCPayload(componentName, props, railsContext) { const { renderingRequest, rscBundleHash } = railsContext.serverSideRSCPayloadParameters; const propsString = JSON.stringify(props); + // Replace trailing empty () in the IIFE with the RSC component name and props const newRenderingRequest = renderingRequest.replace( /\\(\\s*\\)\\s*$/, function() { return `(${JSON.stringify(componentName)}, ${propsString})`; } );🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@react_on_rails_pro/lib/react_on_rails_pro/js_code_builder.rb` around lines 58 - 62, Add an inline comment above the regex replacement in the renderingRequest -> newRenderingRequest transformation to explain that the pattern /\\(\\s*\\)\\s*$/ matches and replaces trailing empty call parentheses (e.g. "foo()") so the code can inject the RSC component name and serialized props; reference the variables involved (renderingRequest, newRenderingRequest, componentName, propsString, rscBundleHash, runOnOtherBundle) and briefly state that this rewrites an empty-call RSC request into a call with the component and props for runOnOtherBundle.react_on_rails/lib/react_on_rails/render_request.rb (1)
41-43: Consider unicode escaping forrails_context_jsonas well.
props_stringescapes\u2028/\u2029for JavaScript safety, butrails_context_jsondoes not. Ifrails_contextcan contain user-provided data with these characters, the same escaping should apply to avoid JavaScript parsing issues when embedded in a<script>tag or evaluated via ExecJS.♻️ Proposed fix for consistency
def rails_context_json - rails_context.to_json + rails_context.to_json.gsub("\u2028", '\u2028').gsub("\u2029", '\u2029') endAlternatively, extract a shared helper method for JSON-with-unicode-escaping to DRY up both methods.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@react_on_rails/lib/react_on_rails/render_request.rb` around lines 41 - 43, rails_context_json currently returns rails_context.to_json without escaping U+2028/U+2029, which can break JS when embedding in <script> or Eval; update rails_context_json to apply the same unicode-escape logic used by props_string (escape \u2028 and \u2029 after to_json) or extract a shared helper (e.g., json_with_js_safe_unicode) and use it from both props_string and rails_context_json so both outputs consistently replace those characters with their \u escapes.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Nitpick comments:
In `@react_on_rails_pro/lib/react_on_rails_pro/js_code_builder.rb`:
- Around line 58-62: Add an inline comment above the regex replacement in the
renderingRequest -> newRenderingRequest transformation to explain that the
pattern /\\(\\s*\\)\\s*$/ matches and replaces trailing empty call parentheses
(e.g. "foo()") so the code can inject the RSC component name and serialized
props; reference the variables involved (renderingRequest, newRenderingRequest,
componentName, propsString, rscBundleHash, runOnOtherBundle) and briefly state
that this rewrites an empty-call RSC request into a call with the component and
props for runOnOtherBundle.
In `@react_on_rails/lib/react_on_rails/render_request.rb`:
- Around line 41-43: rails_context_json currently returns rails_context.to_json
without escaping U+2028/U+2029, which can break JS when embedding in <script> or
Eval; update rails_context_json to apply the same unicode-escape logic used by
props_string (escape \u2028 and \u2029 after to_json) or extract a shared helper
(e.g., json_with_js_safe_unicode) and use it from both props_string and
rails_context_json so both outputs consistently replace those characters with
their \u escapes.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: b665d2e2-fc5b-44ca-8d4d-a3f0e802138a
📒 Files selected for processing (14)
react_on_rails/lib/react_on_rails.rbreact_on_rails/lib/react_on_rails/configuration.rbreact_on_rails/lib/react_on_rails/engine.rbreact_on_rails/lib/react_on_rails/js_code_builder.rbreact_on_rails/lib/react_on_rails/render_request.rbreact_on_rails/lib/react_on_rails/rendering_strategy.rbreact_on_rails/lib/react_on_rails/rendering_strategy/exec_js_strategy.rbreact_on_rails/spec/react_on_rails/js_code_builder_spec.rbreact_on_rails/spec/react_on_rails/render_request_spec.rbreact_on_rails/spec/react_on_rails/rendering_strategy/exec_js_strategy_spec.rbreact_on_rails_pro/lib/react_on_rails_pro.rbreact_on_rails_pro/lib/react_on_rails_pro/engine.rbreact_on_rails_pro/lib/react_on_rails_pro/js_code_builder.rbreact_on_rails_pro/lib/react_on_rails_pro/rendering_strategy/node_strategy.rb
… pattern Give `rendering_strategy` the same `||=` lazy-initialization that `js_code_builder` already uses, so any code path that calls `ReactOnRails.rendering_strategy` before the engine initializer fires gets a usable `ExecJsStrategy` instance instead of nil. Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
Review Comment SummaryAddressed (1):
Declined with rationale (4):
Duplicates (1):
|
There was a problem hiding this comment.
🧹 Nitpick comments (1)
react_on_rails/lib/react_on_rails/configuration.rb (1)
36-38: Consider fail-fast contract checks in setters.Both setters currently accept any object. A small interface guard would catch misconfiguration earlier.
Suggested hardening
def self.rendering_strategy=(strategy) + if strategy && !%i[execute reset reset_if_bundle_changed].all? { |method_name| strategy.respond_to?(method_name) } + raise ArgumentError, "rendering_strategy must respond to :execute, :reset, and :reset_if_bundle_changed" + end `@rendering_strategy` = strategy end def self.js_code_builder=(builder) + if builder && !builder.respond_to?(:build) + raise ArgumentError, "js_code_builder must respond to :build" + end `@js_code_builder` = builder endAlso applies to: 47-49
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@react_on_rails/lib/react_on_rails/configuration.rb` around lines 36 - 38, The setter rendering_strategy= currently accepts any object; add a fail-fast contract check that validates the provided strategy implements the required interface (for example, respond_to?(:render) or respond_to?(:call) depending on your expected API) and raise ArgumentError with a clear message if it does not; apply the same pattern to the other setter methods in this file so misconfiguration is caught early.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Nitpick comments:
In `@react_on_rails/lib/react_on_rails/configuration.rb`:
- Around line 36-38: The setter rendering_strategy= currently accepts any
object; add a fail-fast contract check that validates the provided strategy
implements the required interface (for example, respond_to?(:render) or
respond_to?(:call) depending on your expected API) and raise ArgumentError with
a clear message if it does not; apply the same pattern to the other setter
methods in this file so misconfiguration is caught early.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: 7a3b7f62-ec48-4bbe-8071-8746069fda87
📒 Files selected for processing (1)
react_on_rails/lib/react_on_rails/configuration.rb
Review: Step 1+2 Refactoring (RenderRequest, JsCodeBuilder, RenderingStrategy)The overall structure is clean and the strategy pattern split makes sense. The code faithfully mirrors the existing Pro/core JS generation logic. A few things worth addressing before or during Step 3: 🔴 Step 3 footgun:
|
Remove the rendering_strategy engine initializer that was dead code — the getter already provides the same ||= lazy default, so the initializer's ||= never fired. Add an ArgumentError guard in rails_context_json to catch accidental double-encoding when rails_context is passed as a String. Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
Review comments addressed in c19a744Fixed (2):
Declined with rationale (4):
All 10 review threads resolved. |
Review: RenderRequest, JsCodeBuilder, and RenderingStrategy abstractionsGood architectural direction — separating JS-code generation from execution, and replacing scattered A few issues worth addressing before the switchover in Step 3: Substantive issues1. Missing unicode escaping in 2. 3. Module-level accessors belong in Minor issues4. 5. Test isolation for 6. Overall this is a clean foundation for the Step 3 switchover. Issues 2 and 1 are the most important to address before wiring into the live rendering path. |
There was a problem hiding this comment.
Actionable comments posted: 2
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@react_on_rails_pro/lib/react_on_rails_pro/js_code_builder.rb`:
- Around line 42-45: The long JavaScript error string inside the
generateRSCPayload function exceeds the Ruby 120-char limit; split the message
into shorter concatenated JS strings inside the heredoc so each Ruby source line
stays <=120 chars, e.g., break the single Error('...') argument into two or more
adjacent string segments joined with + (preserving the exact message and
punctuation) within the same heredoc block where globalThis.generateRSCPayload
is defined.
- Around line 40-64: The current code uses a typeof guard so the first request's
version of globalThis.generateRSCPayload persists across reused renderer
contexts, causing request-order dependent behavior; always overwrite the global
handler per request instead of only setting it when typeof generateRSCPayload
!== 'function' — i.e., remove the typeof checks and unconditionally assign
globalThis.generateRSCPayload in both branches (the throwing stub when
render_request.rsc_payload_streaming? and the dispatcher that uses
railsContext.serverSideRSCPayloadParameters, renderingRequest, rscBundleHash and
runOnOtherBundle in the other branch) so each request installs the correct
implementation.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: 0337e380-f8ad-4c19-9984-60e60ebb9d99
📒 Files selected for processing (11)
react_on_rails/Steepfilereact_on_rails/lib/react_on_rails/configuration.rbreact_on_rails/lib/react_on_rails/render_request.rbreact_on_rails/lib/react_on_rails/rendering_strategy.rbreact_on_rails/sig/react_on_rails.rbsreact_on_rails/sig/react_on_rails/js_code_builder.rbsreact_on_rails/sig/react_on_rails/render_request.rbsreact_on_rails/sig/react_on_rails/rendering_strategy.rbsreact_on_rails/sig/react_on_rails/rendering_strategy/exec_js_strategy.rbsreact_on_rails/spec/react_on_rails/render_request_spec.rbreact_on_rails_pro/lib/react_on_rails_pro/js_code_builder.rb
✅ Files skipped from review due to trivial changes (3)
- react_on_rails/sig/react_on_rails/rendering_strategy.rbs
- react_on_rails/lib/react_on_rails/rendering_strategy.rb
- react_on_rails/spec/react_on_rails/render_request_spec.rb
🚧 Files skipped from review as they are similar to previous changes (2)
- react_on_rails/lib/react_on_rails/configuration.rb
- react_on_rails/lib/react_on_rails/render_request.rb
Review: RenderRequest, JsCodeBuilder, and RenderingStrategy abstractionsOverall this is a clean, well-structured refactor. The additive approach is the right call — keeping the existing path intact while introducing the new abstractions side-by-side makes the transition safe. A few issues worth addressing before Step 3 wires these in: Issues1. RBS type mismatch:
2. When 3. Minor thread-safety note on The 4. Implicit In Minor
What's good
|
…igure reset - Fix RBS type for store_initializations to String? (attr_reader and initialize) — the spec passes nil and JsCodeBuilder#build_sections calls .compact to filter it. - Remove @rendering_strategy/@js_code_builder reset from configure — Pro only sets these once during after_initialize, so resetting them on reconfigure silently drops Pro behavior. Since the strategy path isn't wired in yet, the reset was premature; the proper reconfigure mechanism should be designed when Step 3 lands. Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
Review comments addressed in 6a44410Fixed (2):
Declined with rationale (3):
All 7 review threads resolved. |
|
To use Codex here, create an environment for this repo. |
|
Tip For best results, initiate chat on the files or code changes.
Both of my declined items ( ✏️ Learnings added
🧠 Learnings used |
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes and found 1 potential issue.
Bugbot Autofix is ON, but it could not run because on-demand usage is turned off. To enable Bugbot Autofix, turn on on-demand usage and set a spend limit in the Cursor dashboard.
Reviewed by Cursor Bugbot for commit 6a44410. Configure here.
Review: RenderRequest, JsCodeBuilder, and RenderingStrategy abstractionsThe architecture is sound and the additive approach (zero impact on the live rendering path until Step 3) is the right way to land a refactor of this scope. The unit tests are thorough and the RBS signatures are a nice touch. Issues1. Module-level state in the wrong file (see inline on 2. Hidden cross-method invariant in 3. Duplicate validation in 4. Observations
Overall this is clean, well-tested code. The inline comments above are the main items I'd want addressed before Step 3 lands. |
The base and Pro JsCodeBuilder have cross-method coupling where props_section, render_call_section, and wrap_in_iife share JS variable names. Document this invariant so future subclass authors know to keep all three methods aligned when overriding any one of them. Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
Review comments addressed in ea1e3eaFixed (1):
Declined with rationale (4):
|
Review: refactor — RenderRequest, JsCodeBuilder, RenderingStrategy abstractionsOverall the design is clean and the additive approach is the right call for this size of refactor. The three-abstraction split (data object / builder / strategy) mirrors the existing layering well, and the JS variable contract documentation in the builder is especially appreciated. A few things worth addressing before Step 3 wires this into the live path: 1. Pro-specific methods on core
|
There was a problem hiding this comment.
🧹 Nitpick comments (1)
react_on_rails_pro/lib/react_on_rails_pro/js_code_builder.rb (1)
74-80: Document the IIFE-call-shape coupling too.The contract here is broader than variable names: Line 58 rewrites the trailing
()fromwrap_in_iife. A later wrapper refactor can break payload dispatch without touching the RSC code path.♻️ Suggested doc tweak
# These three methods are coupled by JS variable names: # - wrap_in_iife introduces `componentName` and `props` as IIFE default parameters + # - rsc_payload_function_section rewrites the trailing empty IIFE invocation, + # so wrap_in_iife must continue ending in a bare `()` # - props_section declares `usedProps` (falls back to render_request.props_string # when `props` IIFE param is undefined, i.e. normal invocation) # - render_call_section references both `componentName` and `usedProps`Also applies to: 58-60, 101-102
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@react_on_rails_pro/lib/react_on_rails_pro/js_code_builder.rb` around lines 74 - 80, The comment block documents JS variable coupling but omits the IIFE call-shape coupling — note that wrap_in_iife rewrites the trailing () and other wrappers must preserve that exact call shape; update the doc in react_on_rails_pro/lib/react_on_rails_pro/js_code_builder.rb near the existing comment (mentioning build_sections, wrap_in_iife, props_section, render_call_section) to explicitly state that wrap_in_iife removes or rewrites the trailing invocation token, that props_section and render_call_section expect that modified call shape (usedProps fallback behavior), and that any future wrapper refactors must preserve this IIFE-call-shape to avoid breaking payload dispatch (also add a short reference to the related spots around wrap_in_iife and the lines noted).
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Nitpick comments:
In `@react_on_rails_pro/lib/react_on_rails_pro/js_code_builder.rb`:
- Around line 74-80: The comment block documents JS variable coupling but omits
the IIFE call-shape coupling — note that wrap_in_iife rewrites the trailing ()
and other wrappers must preserve that exact call shape; update the doc in
react_on_rails_pro/lib/react_on_rails_pro/js_code_builder.rb near the existing
comment (mentioning build_sections, wrap_in_iife, props_section,
render_call_section) to explicitly state that wrap_in_iife removes or rewrites
the trailing invocation token, that props_section and render_call_section expect
that modified call shape (usedProps fallback behavior), and that any future
wrapper refactors must preserve this IIFE-call-shape to avoid breaking payload
dispatch (also add a short reference to the related spots around wrap_in_iife
and the lines noted).
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: 5e81bf62-1b37-42d1-bdc9-6f2e71e8cae4
📒 Files selected for processing (4)
react_on_rails/lib/react_on_rails/configuration.rbreact_on_rails/lib/react_on_rails/js_code_builder.rbreact_on_rails/sig/react_on_rails/render_request.rbsreact_on_rails_pro/lib/react_on_rails_pro/js_code_builder.rb
🚧 Files skipped from review as they are similar to previous changes (2)
- react_on_rails/lib/react_on_rails/js_code_builder.rb
- react_on_rails/sig/react_on_rails/render_request.rbs
Review comments addressed — final roundAll 4 remaining review threads from the latest review have been replied to and resolved. No code changes were needed. Declined with rationale (4):
All review threads on this PR are now resolved. |
…ages * origin/main: Fix initial page startup race for late-loading client bundles (#3151) chore: apply prettier formatting to tracked docs files (#3153) docs: comprehensive RSC API documentation and registration consolidation (#3140) Split rspec-package-tests into parallel generator/unit shards (#3134) fix: add concurrency groups to long-running CI workflows (#3133) refactor: add RenderRequest, JsCodeBuilder, and RenderingStrategy abstractions (#3094) fix: address deferred review items from PR #2849 (#3093) Add complimentary OSS license policy for React on Rails Pro (#3123) fix: centralize CI docs-only detection and add CLI flag validation (#3091) refactor: replace stub-throw + Object.assign with capability-based composition (#3096) Enhance address-review with parallel fixes, self-review, and Greptile verification (#3121) fix: Doctor no longer fails custom projects for missing bin/dev (#3117) fix: cap webpack <5.106.0 to prevent ExecJS SSR breakage (#3095) Add Rspack + RSC compatibility tests and documentation (#1828) (#3120) Add error scenarios hub and test pages (#2497) docs: document polyfill requirements for web-targeted server bundles (#3092) docs: RSC integration pitfalls from tutorial app (#3087) docs: fix render function/helper API documentation (#3088) Doctor: accept TS/TSX server bundle suffixes (#3111) feat: add CI guard requiring sidebar updates when adding docs (#3089)
…tractions (#3094) ## Summary Implements Steps 1+2 of #2905 — introduces three new abstractions to replace runtime `react_on_rails_pro?` checks in the rendering pipeline: - **`RenderRequest`** — structured data object encapsulating everything needed for a server render (component name, props, rails context, store initializations, render options), replacing ad-hoc parameter passing of `js_code` strings and `render_options` through the delegation chain - **`JsCodeBuilder`** — structured builder with overridable section methods for JS code generation, replacing the two divergent heredoc generators (core and Pro). Pro inherits and overrides only the sections it needs (RSC manifest, pre-hooks, dynamic render function name) - **`RenderingStrategy`** — strategy interface with `ExecJsStrategy` (core) and `NodeStrategy` (Pro) concrete implementations, replacing the conditional dispatch in `ServerRenderingPool` and `ServerRenderingJsCode` All new code is **additive** — the main rendering path in `helper.rb` still uses the existing `ServerRenderingPool` and `ServerRenderingJsCode` classes. The switchover will happen in a follow-up PR (Step 3 of the issue). ### Configuration wiring - `ReactOnRails.rendering_strategy` and `ReactOnRails.js_code_builder` module-level accessors, set by engine initializers at boot time - Core engine sets `ExecJsStrategy` as default - Pro engine overrides with `NodeStrategy` and Pro `JsCodeBuilder` ### New files | File | Purpose | |------|---------| | `react_on_rails/lib/react_on_rails/render_request.rb` | RenderRequest data object | | `react_on_rails/lib/react_on_rails/js_code_builder.rb` | Base JS code builder | | `react_on_rails/lib/react_on_rails/rendering_strategy.rb` | Strategy interface module | | `react_on_rails/lib/react_on_rails/rendering_strategy/exec_js_strategy.rb` | ExecJS strategy | | `react_on_rails_pro/lib/react_on_rails_pro/js_code_builder.rb` | Pro JS code builder (RSC, pre-hooks) | | `react_on_rails_pro/lib/react_on_rails_pro/rendering_strategy/node_strategy.rb` | Pro strategy (wraps ProRendering) | Closes #2905 (partially — Steps 1+2 of 4) ## Test plan - [x] New unit tests for `RenderRequest`, `JsCodeBuilder`, and `ExecJsStrategy` (23 specs, all passing) - [x] RuboCop passes on all 14 changed files - [x] Pre-commit and pre-push hooks pass - [ ] CI passes (existing tests unaffected since changes are additive) 🤖 Generated with [Claude Code](https://claude.com/claude-code) <!-- CURSOR_SUMMARY --> --- > [!NOTE] > **Low Risk** > Mostly additive abstractions with unit tests; existing SSR rendering path is unchanged, so behavior impact is low aside from new boot-time configurable globals. > > **Overview** > Introduces new SSR abstractions: `RenderRequest` to encapsulate render inputs, `JsCodeBuilder` to generate SSR JavaScript via overridable sections, and a `RenderingStrategy` interface with a core `ExecJsStrategy` implementation. > > Adds boot-time configuration hooks (`ReactOnRails.rendering_strategy` and `ReactOnRails.js_code_builder`) and a Pro engine initializer that overrides these with Pro’s `NodeStrategy` and extended `JsCodeBuilder` (adds RSC/streaming/pre-hook sections). Updates Steep/RBS signatures and adds unit specs covering the new objects and strategies. > > <sup>Reviewed by [Cursor Bugbot](https://cursor.com/bugbot) for commit ea1e3ea. Bugbot is set up for automated code reviews on this repo. Configure [here](https://www.cursor.com/dashboard/bugbot).</sup> <!-- /CURSOR_SUMMARY --> <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **New Features** * Configurable rendering strategy and pluggable JS code builder for server-side rendering. * Pro: Node-based rendering option with React Server Components (RSC) support and streaming payload handling. * Safer server-render payloads: props and Rails context are JSON-serialized and sanitized for embedding. * **Tests** * Added tests covering JS generation, render request behavior, and rendering strategies. <!-- end of auto-generated comment: release notes by coderabbit.ai --> --------- Co-authored-by: Claude Opus 4.6 (1M context) <[email protected]>

Summary
Implements Steps 1+2 of #2905 — introduces three new abstractions to replace runtime
react_on_rails_pro?checks in the rendering pipeline:RenderRequest— structured data object encapsulating everything needed for a server render (component name, props, rails context, store initializations, render options), replacing ad-hoc parameter passing ofjs_codestrings andrender_optionsthrough the delegation chainJsCodeBuilder— structured builder with overridable section methods for JS code generation, replacing the two divergent heredoc generators (core and Pro). Pro inherits and overrides only the sections it needs (RSC manifest, pre-hooks, dynamic render function name)RenderingStrategy— strategy interface withExecJsStrategy(core) andNodeStrategy(Pro) concrete implementations, replacing the conditional dispatch inServerRenderingPoolandServerRenderingJsCodeAll new code is additive — the main rendering path in
helper.rbstill uses the existingServerRenderingPoolandServerRenderingJsCodeclasses. The switchover will happen in a follow-up PR (Step 3 of the issue).Configuration wiring
ReactOnRails.rendering_strategyandReactOnRails.js_code_buildermodule-level accessors, set by engine initializers at boot timeExecJsStrategyas defaultNodeStrategyand ProJsCodeBuilderNew files
react_on_rails/lib/react_on_rails/render_request.rbreact_on_rails/lib/react_on_rails/js_code_builder.rbreact_on_rails/lib/react_on_rails/rendering_strategy.rbreact_on_rails/lib/react_on_rails/rendering_strategy/exec_js_strategy.rbreact_on_rails_pro/lib/react_on_rails_pro/js_code_builder.rbreact_on_rails_pro/lib/react_on_rails_pro/rendering_strategy/node_strategy.rbCloses #2905 (partially — Steps 1+2 of 4)
Test plan
RenderRequest,JsCodeBuilder, andExecJsStrategy(23 specs, all passing)🤖 Generated with Claude Code
Note
Low Risk
Mostly additive abstractions with unit tests; existing SSR rendering path is unchanged, so behavior impact is low aside from new boot-time configurable globals.
Overview
Introduces new SSR abstractions:
RenderRequestto encapsulate render inputs,JsCodeBuilderto generate SSR JavaScript via overridable sections, and aRenderingStrategyinterface with a coreExecJsStrategyimplementation.Adds boot-time configuration hooks (
ReactOnRails.rendering_strategyandReactOnRails.js_code_builder) and a Pro engine initializer that overrides these with Pro’sNodeStrategyand extendedJsCodeBuilder(adds RSC/streaming/pre-hook sections). Updates Steep/RBS signatures and adds unit specs covering the new objects and strategies.Reviewed by Cursor Bugbot for commit ea1e3ea. Bugbot is set up for automated code reviews on this repo. Configure here.
Summary by CodeRabbit