Skip to content

fix: scope RSC client reference discovery#3219

Open
justin808 wants to merge 27 commits into
mainfrom
codex/fix-rsc-client-reference-scope
Open

fix: scope RSC client reference discovery#3219
justin808 wants to merge 27 commits into
mainfrom
codex/fix-rsc-client-reference-scope

Conversation

@justin808
Copy link
Copy Markdown
Member

@justin808 justin808 commented Apr 30, 2026

Summary

  • scope generated RSCWebpackPlugin clientReferences to Shakapacker config.source_path for both client and server bundles
  • update existing-config RSC transforms to inject the same scoped references
  • document why omitted clientReferences can accidentally scan vendored gem templates in CI

Fixes #3201

Test plan

  • bundle exec rspec react_on_rails/spec/react_on_rails/generators/rsc_generator_spec.rb:99 react_on_rails/spec/react_on_rails/generators/rsc_generator_spec.rb:112 react_on_rails/spec/react_on_rails/generators/rsc_generator_spec.rb:639 react_on_rails/spec/react_on_rails/generators/rsc_generator_spec.rb:652 react_on_rails/spec/react_on_rails/generators/rsc_generator_spec.rb:726 react_on_rails/spec/react_on_rails/generators/install_generator_spec.rb:1601 react_on_rails/spec/react_on_rails/generators/install_generator_spec.rb:1817 react_on_rails/spec/react_on_rails/generators/install_generator_spec.rb:1824
  • bundle exec rubocop react_on_rails/lib/generators/react_on_rails/rsc_setup.rb react_on_rails/spec/react_on_rails/generators/rsc_generator_spec.rb react_on_rails/spec/react_on_rails/generators/install_generator_spec.rb

Note

Medium Risk
Medium risk because it changes generator rewrite behavior for existing webpack configs and adds a custom lightweight JS parser that could miss edge cases and alter config files unexpectedly.

Overview
Scopes RSC client-reference discovery to application source. Generated RSC webpack configs now pass clientReferences derived from Shakapacker config.source_path (via rscClientReferences) for both client and server RSCWebpackPlugin instances, avoiding scans of unrelated project files (e.g., vendor/bundle) in CI.

The react_on_rails:rsc setup generator now also upgrades existing webpack configs that already include RSCWebpackPlugin by injecting the scoped rscClientReferences helper (when safe) and rewriting plugin option objects to add clientReferences: rscClientReferences, with expanded verification/warnings for non-CommonJS or hard-to-parse configs. Templates, docs, and generator specs were updated accordingly.

Reviewed by Cursor Bugbot for commit 4122dc9. Bugbot is set up for automated code reviews on this repo. Configure here.

Summary by CodeRabbit

  • Documentation

    • Made RSC webpack guidance prescriptive: always point client references at the app source and warn about CI/compilation risks if omitted.
  • Bug Fixes

    • Generated RSC webpack configs now scope clientReferences to the application source to avoid compiling vendored templates or unrelated files in CI.
  • Tests

    • Expanded specs to validate the new RSC webpack configuration, import-injection idempotency, and plugin-update behavior.

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Apr 30, 2026

Note

Reviews paused

It 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 reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

Walkthrough

Generator and webpack/rspack templates now define a scoped rscClientReferences (using resolve(config.source_path), recursive, JS/TS include) and ensure RSCWebpackPlugin is configured with clientReferences: rscClientReferences; existing configs are updated when possible. Tests, docs, and CHANGELOG were updated accordingly.

Changes

RSC clientReferences scoping

Layer / File(s) Summary
Data Shape / Constant
react_on_rails/lib/generators/react_on_rails/rsc_setup.rb
Adds rscClientReferences JS object: directory: resolve(config.source_path), recursive: true, `include: /.(js
Core Generator Logic
react_on_rails/lib/generators/react_on_rails/rsc_setup.rb
Generator injects clientReferences: rscClientReferences into new RSCWebpackPlugin calls; adds update_existing_rsc_webpack_config and import-detection helpers to insert or update rscClientReferences and rewrite plugin constructor options in-place when needed.
Template Wiring
react_on_rails/lib/generators/react_on_rails/templates/.../config/webpack/clientWebpackConfig.js.tt, .../serverWebpackConfig.js.tt
Templates now declare rscClientReferences and instantiate new RSCWebpackPlugin({ isServer: <bool>, clientReferences: rscClientReferences }) for client and server bundles.
Wiring / Injection Safety
react_on_rails/lib/generators/react_on_rails/rsc_setup.rb
Injection logic conditionally adds const { config } = require('shakapacker') and const { resolve } = require('path') only when needed; detects existing imports or custom anchors and emits a warning if it cannot safely inject scoped references.
Tests / Specs
react_on_rails/spec/react_on_rails/generators/rsc_generator_spec.rb, react_on_rails/spec/react_on_rails/generators/install_generator_spec.rb
Specs updated to assert clientReferences: rscClientReferences, directory: resolve(config.source_path), and correct isServer booleans; added contexts for idempotent import injection, plugin in-place updates, and the custom-import-anchor warning behavior.
Documentation / Changelog
docs/oss/migrating/rsc-preparing-app.md, CHANGELOG.md
Docs and changelog updated to recommend scoping clientReferences to the app source and to highlight CI/compilation failures caused by scanning vendored templates (e.g., vendor/bundle).

Sequence Diagram

sequenceDiagram
  participant Generator
  participant File as "webpack config file"
  participant Shakapacker
  participant RSCPlugin as "RSCWebpackPlugin"

  Generator->>File: open/read file
  alt existing RSCWebpackPlugin found without clientReferences
    Generator->>File: rewrite plugin constructor to add clientReferences
  else no RSCWebpackPlugin
    Generator->>File: insert imports for Shakapacker & path
    Generator->>Shakapacker: use config.source_path
    Generator->>File: insert rscClientReferences + new RSCWebpackPlugin({..., clientReferences})
  end
  File->>RSCPlugin: RSCWebpackPlugin configured with scoped clientReferences
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Poem

I’m a rabbit in the code, I hop and fix the trail,
Scoped paths keep CI happy, no more vendor derail,
I stitch imports and references with a careful, quiet tap,
Builds purr in the morning — no mysterious map,
🐇✨

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title 'fix: scope RSC client reference discovery' directly addresses the main change: scoping RSCWebpackPlugin's clientReferences to prevent scanning vendored dependencies.
Linked Issues check ✅ Passed All objectives from issue #3201 are met: clientReferences are scoped to config.source_path, generated webpack configs include proper configuration, the generator handles in-place transforms safely, and documentation/changelog explain the fix.
Out of Scope Changes check ✅ Passed All changes directly support the primary objective of scoping RSC client references: generator updates, template modifications, documentation, changelog, and comprehensive test coverage align with issue #3201 requirements.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch codex/fix-rsc-client-reference-scope

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@greptile-apps
Copy link
Copy Markdown

greptile-apps Bot commented Apr 30, 2026

Greptile Summary

This PR scopes the RSCWebpackPlugin's clientReferences to config.source_path (from Shakapacker) on both the client and server webpack configs, preventing the plugin from scanning the entire project root — including vendor/bundle gem templates in CI — for 'use client' directives. The fix is applied consistently across both the new-install template files (.js.tt) and the existing-config transform paths in rsc_setup.rb, with updated specs and documentation.

Confidence Score: 5/5

Safe to merge — changes are consistent across all code paths, no logic errors or regressions identified.

All six changed files are internally consistent: both template (.tt) and existing-config transform paths inject the same scoped rscClientReferences object, config is never double-imported (server template already has it; client template adds it only in the RSC block), and specs validate the new content. No P0 or P1 findings.

No files require special attention.

Important Files Changed

Filename Overview
react_on_rails/lib/generators/react_on_rails/rsc_setup.rb Updates both transform paths (update_server_webpack_config_for_rsc and update_client_webpack_config_for_rsc) to inject rscClientReferences scoped to config.source_path; logic is consistent with the template changes.
react_on_rails/lib/generators/react_on_rails/templates/base/base/config/webpack/clientWebpackConfig.js.tt Adds const { config } from shakapacker, const { resolve } from path, and rscClientReferences object in the RSC block; passes rscClientReferences to RSCWebpackPlugin.
react_on_rails/lib/generators/react_on_rails/templates/base/base/config/webpack/serverWebpackConfig.js.tt Adds const { resolve } from path and rscClientReferences in the RSC block; config is already imported at the top via shakapacker so no duplicate import needed.
react_on_rails/spec/react_on_rails/generators/rsc_generator_spec.rb Updates spec assertions to verify clientReferences and directory: resolve(config.source_path) are present in generated configs; prior exact-string assertions replaced with more flexible checks.
react_on_rails/spec/react_on_rails/generators/install_generator_spec.rb Extends existing RSCWebpackPlugin assertions to also verify clientReferences and directory: resolve(config.source_path) for server and client configs on both webpack and rspack paths.
docs/oss/migrating/rsc-preparing-app.md Documentation updated to explain the vendor/bundle CI scanning risk when clientReferences is omitted, and to recommend always pointing it at the application source directory.

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
    A[Generator invoked with RSC] --> B{New install or existing config?}
    B -- New install --> C[Render .js.tt templates]
    B -- Existing config --> D[update_webpack_configs_for_rsc]

    C --> E[clientWebpackConfig.js.tt\nInjects: config from shakapacker\n resolve from path\n RSCWebpackPlugin\n rscClientReferences scoped to config.source_path]
    C --> F[serverWebpackConfig.js.tt\nInjects: resolve from path\n RSCWebpackPlugin\n rscClientReferences scoped to config.source_path]

    D --> G[update_client_webpack_config_for_rsc\ngsub: add RSCWebpackPlugin import +\n shakapacker config + rscClientReferences]
    D --> H[update_server_webpack_config_for_rsc\ngsub: add RSCWebpackPlugin import +\n resolve + rscClientReferences]

    E --> I[RSCWebpackPlugin with clientReferences\ndirectory: resolve config.source_path\nrecursive: true\ninclude: js,ts,jsx,tsx]
    F --> I
    G --> I
    H --> I

    I --> J[Webpack scans only source_path\nSkips vendor/bundle in CI]
Loading

Reviews (1): Last reviewed commit: "Scope RSC client references to app sourc..." | Re-trigger Greptile

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Apr 30, 2026

Caution

Review failed

An error occurred during the review process. Please try again later.

Walkthrough

The PR resolves a webpack compilation issue where RSCWebpackPlugin's default recursive project root scan could inadvertently include vendored gem templates in CI environments. Changes include updating documentation, modifying webpack config generators and templates to explicitly compute and pass clientReferences scoped to the application's source directory, and updating corresponding tests.

Changes

Cohort / File(s) Summary
Documentation
docs/oss/migrating/rsc-preparing-app.md
Updated guidance for RSCWebpackPlugin's clientReferences option to be prescriptive and emphasize the CI/compilation risk when the plugin's default recursive scan inadvertently picks up vendored templates.
Generator Logic
react_on_rails/lib/generators/react_on_rails/rsc_setup.rb
Enhanced transformations for both server and client webpack configs to compute rscClientReferences from source_path and inject it into RSCWebpackPlugin instantiation alongside isServer parameter.
Webpack Config Templates
react_on_rails/lib/generators/react_on_rails/templates/base/base/config/webpack/clientWebpackConfig.js.tt, react_on_rails/lib/generators/react_on_rails/templates/base/base/config/webpack/serverWebpackConfig.js.tt
Updated templates to build rscClientReferences from config.source_path with recursive scan and pass it to RSCWebpackPlugin configuration in both client and server bundles.
Test Specs
react_on_rails/spec/react_on_rails/generators/install_generator_spec.rb, react_on_rails/spec/react_on_rails/generators/rsc_generator_spec.rb
Extended assertions to validate that generated webpack/rspack configs include both clientReferences: rscClientReferences and directory: resolve(config.source_path) in the RSC plugin configuration.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Poem

A rabbit hops through webpack's halls,
Where vendor gems no longer call,
With source_path the scope is tight,
Client references bundled right,
No more CI surprises—what a sight! 🐰✨

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and specifically describes the main change: scoping RSC client reference discovery to prevent unintended scanning of vendored dependencies.
Linked Issues check ✅ Passed The PR fully addresses issue #3201 by scoping clientReferences to config.source_path in generated webpack configs, updating RSC transforms, and documenting the scoping requirement.
Out of Scope Changes check ✅ Passed All changes are directly aligned with issue #3201 objectives: scoping RSCWebpackPlugin clientReferences, updating existing-config transforms, adding documentation, and updating relevant tests.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch codex/fix-rsc-client-reference-scope

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@claude
Copy link
Copy Markdown
Contributor

claude Bot commented Apr 30, 2026

Code Review — PR #3219: Scope RSC client reference discovery

Summary

Correct fix for a real CI reliability issue. Scoping RSCWebpackPlugin's clientReferences to Shakapacker::config.source_path prevents the plugin from accidentally compiling vendored gem templates under vendor/bundle. The template, update-transformer, docs, and changelog are all updated consistently.


Issues

1. verify_rsc_webpack_transforms is not updated (medium severity)

check_rsc_server_config and check_rsc_client_config only verify that RSCWebpackPlugin and rscBundle are present. They do not check for rscClientReferences. This means apps that already have RSCWebpackPlugin set up (without scoping) will pass the self-check silently and never receive the warning that their config is incomplete.

Consider adding to both check methods:

missing << "rscClientReferences in serverWebpackConfig.js" unless content.include?("rscClientReferences")

2. Dropped isServer assertions weaken test coverage (low severity)

The updated specs replace the original isServer: false / isServer: true assertions with only clientReferences assertions. A future change that accidentally omits isServer from the plugin constructor would pass the suite undetected. Both sets of assertions should be kept.

See inline comments on specific lines.

3. Asymmetric test coverage for webpack serverWebpackConfig (low severity)

rsc_generator_spec.rb around line 99 ("adds RSCWebpackPlugin to serverWebpackConfig" for the webpack path) was not updated to include clientReferences assertions, even though the equivalent rspack test (line 726) was updated. The install-generator spec covers this case, but the rsc-generator spec for the default webpack path is now inconsistent.


Minor observations

  • In serverWebpackConfig.js.tt, config is already imported via const { merge, config } = require('shakapacker'); at the top, so resolve(config.source_path) works without a new import. In clientWebpackConfig.js.tt, a separate const { config } = require('shakapacker'); is correctly added because config wasn't imported there before. The asymmetry is intentional and correct.
  • path.resolve() applied to an already-absolute path (which source_path returns) is a no-op. Harmless, but worth noting in comments for future readers wondering why it's there.

Verdict

The fix is correct, well-targeted, and consistent across templates and the update-transformer path. The issues above are minor and shouldn't block merge, but closing the verify_rsc_webpack_transforms gap would make the self-check more useful for users upgrading existing configs.

Comment thread react_on_rails/spec/react_on_rails/generators/rsc_generator_spec.rb
Comment thread react_on_rails/spec/react_on_rails/generators/rsc_generator_spec.rb
Comment thread react_on_rails/spec/react_on_rails/generators/rsc_generator_spec.rb
Comment thread react_on_rails/lib/generators/react_on_rails/rsc_setup.rb Outdated
Comment thread react_on_rails/lib/generators/react_on_rails/rsc_setup.rb Outdated
@claude
Copy link
Copy Markdown
Contributor

claude Bot commented Apr 30, 2026

Code Review

Good, targeted fix. The root cause (unscoped clientReferences defaulting to project-root scan) is correctly addressed across both new-generate and update-existing-config paths, and the documentation update makes the risk concrete rather than abstract.

Summary

Area Assessment
Correctness ✅ Fix is accurate — resolve(config.source_path) is the right scope
Both paths covered ✅ Template files + existing-config transformers both updated
CHANGELOG / docs ✅ Clear and informative
Test coverage ⚠️ Minor gaps (see below)
Code duplication ⚠️ rscClientReferences repeated 4×

Issues

1. Test gap — webpack server config (not in diff, so inline comment not possible)

rsc_generator_spec.rb:99–103 only asserts RSCWebpackPlugin / react-on-rails-rsc/WebpackPlugin for the webpack server config. It does not verify clientReferences: rscClientReferences or directory: resolve(config.source_path), so a regression in serverWebpackConfig.js.tt would go undetected. The analogous rspack test at line 726 does assert both — these two should match.

Suggested addition to the test at line 99:

it "adds RSCWebpackPlugin to serverWebpackConfig" do
  assert_file "config/webpack/serverWebpackConfig.js" do |content|
    expect(content).to include("RSCWebpackPlugin")
    expect(content).to include("react-on-rails-rsc/WebpackPlugin")
    expect(content).to include("clientReferences: rscClientReferences")   # add
    expect(content).to include("directory: resolve(config.source_path)")  # add
  end
end

Similarly rsc_generator_spec.rb:639–643 (rspack server config, standard path) has the same gap versus the "Rspack RSC runtime compatibility" block at line 726.

2. Possible duplicate const { config } declaration (inline comment posted)

update_client_webpack_config_for_rsc unconditionally injects const { config } = require('shakapacker'); after matching commonWebpackConfig. If a user's existing clientWebpackConfig.js already imports config (not uncommon for apps that customise output paths or aliases), webpack will throw SyntaxError: Identifier 'config' has already been declared at config-load time.

A simple guard: check content.include?("require('shakapacker')") before injecting, or extend the regex to match and augment an existing shakapacker import rather than adding a second one.

3. Code duplication (inline comment posted)

The rscClientReferences object literal is copy-pasted in four places:

  • serverWebpackConfig.js.tt
  • clientWebpackConfig.js.tt
  • update_server_webpack_config_for_rsc (transformer)
  • update_client_webpack_config_for_rsc (transformer)

A private helper method on the generator class for the Ruby string would keep the transformer paths in sync and reduce the chance of partial updates in the future. The templates remain separate (ERB, not Ruby) but are easier to audit since they're the authoritative source.


Minor notes

  • The tests at lines 113 and 651 previously asserted new RSCWebpackPlugin({ isServer: false }) as a single-line string. That assertion was dropped when the call was reformatted to multiline. Neither the old nor new tests now verify isServer: false / isServer: true — low risk since those params were already present before this PR, but worth noting if someone later changes the plugin call signature.
  • resolve(config.source_path): if source_path is already absolute, resolve() is a no-op. If relative, it resolves against process.cwd() (the project root at build time). Both cases are correct. No issue here, just documenting the behavior is intentional.

Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: d49943e2f3

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread react_on_rails/lib/generators/react_on_rails/rsc_setup.rb Outdated
Comment thread react_on_rails/lib/generators/react_on_rails/rsc_setup.rb Outdated
Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 08e341ea21

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread react_on_rails/lib/generators/react_on_rails/rsc_setup.rb Outdated
Comment thread react_on_rails/lib/generators/react_on_rails/rsc_setup.rb Outdated
Copy link
Copy Markdown

@cursor cursor Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes and found 1 potential issue.

Autofix Details

Bugbot Autofix prepared a fix for the issue found in the latest run.

  • ✅ Fixed: Server config missing duplicate resolve import guard
    • The server RSC webpack transform now skips injecting the path resolve import when it already exists and has regression coverage for that case.

Create PR

Or push these changes by commenting:

@cursor push f73f903f50
Preview (f73f903f50)
diff --git a/react_on_rails/lib/generators/react_on_rails/rsc_setup.rb b/react_on_rails/lib/generators/react_on_rails/rsc_setup.rb
--- a/react_on_rails/lib/generators/react_on_rails/rsc_setup.rb
+++ b/react_on_rails/lib/generators/react_on_rails/rsc_setup.rb
@@ -385,11 +385,11 @@
         # Add RSCWebpackPlugin import after bundler require
         server_injected_imports = [
           "\\1",
-          "const { resolve } = require('path');",
+          ("const { resolve } = require('path');" unless path_resolve_imported?(content)),
           "const { RSCWebpackPlugin } = require('react-on-rails-rsc/WebpackPlugin');",
           "",
           rsc_client_references_js
-        ].join("\n")
+        ].compact.join("\n")
 
         gsub_file(
           config_path,

diff --git a/react_on_rails/spec/react_on_rails/generators/rsc_generator_spec.rb b/react_on_rails/spec/react_on_rails/generators/rsc_generator_spec.rb
--- a/react_on_rails/spec/react_on_rails/generators/rsc_generator_spec.rb
+++ b/react_on_rails/spec/react_on_rails/generators/rsc_generator_spec.rb
@@ -199,6 +199,38 @@
     end
   end
 
+  context "when the server webpack config already imports path resolve" do
+    before(:all) do
+      prepare_destination
+      simulate_existing_rails_files(package_json: true)
+      simulate_npm_files(package_json: true)
+      simulate_existing_file("config/initializers/react_on_rails_pro.rb", <<~RUBY)
+        ReactOnRailsPro.configure do |config|
+          config.server_renderer = "NodeRenderer"
+        end
+      RUBY
+      simulate_existing_file("Procfile.dev", "rails: bin/rails s\n")
+      simulate_pro_webpack_files
+      simulate_existing_file(
+        "config/webpack/serverWebpackConfig.js",
+        "const { resolve } = require('path');\n#{pro_server_webpack_content}"
+      )
+
+      Dir.chdir(destination_root) do
+        run_generator(["--force"])
+      end
+    end
+
+    it "does not inject a duplicate resolve import" do
+      assert_file "config/webpack/serverWebpackConfig.js" do |content|
+        expect(content.scan("const { resolve } = require('path');").length).to eq(1)
+        expect(content).to include("clientReferences: rscClientReferences")
+        expect(content).to include("directory: resolve(config.source_path)")
+        expect(content).to include("isServer: true")
+      end
+    end
+  end
+
   context "when Pro is installed with a canonical legacy hello_world layout" do
     before(:all) do
       prepare_destination

You can send follow-ups to the cloud agent here.

Comment thread react_on_rails/lib/generators/react_on_rails/rsc_setup.rb Outdated
@claude
Copy link
Copy Markdown
Contributor

claude Bot commented Apr 30, 2026

Code Review: PR #3219 — Scope RSC client reference discovery

Overview

This PR correctly fixes the root cause of issue #3201: RSCWebpackPlugin was previously called without explicit clientReferences, causing it to recursively scan the entire project root and accidentally compile vendored gem templates (e.g., vendor/bundle) in CI. The fix computes clientReferences from Shakapacker's source_path and passes it to every RSCWebpackPlugin call.


What's Good

  • Correct fix — scoping clientReferences to resolve(config.source_path) directly addresses the CI scanning issue.
  • Template updates are consistent — both clientWebpackConfig.js.tt and serverWebpackConfig.js.tt are updated symmetrically.
  • commonjs_named_imported? helper — nice reusable abstraction for detecting CommonJS destructure imports; correctly handles default-value bindings ({ config = default }).
  • Duplicate-import guards — the client config path now conditionally skips injecting shakapacker and path imports when they're already present.
  • Test coverage — new tests for fresh install, already-imported config, and aliased destructuring cases.

Issues & Suggestions

Bug: Server config injection lacks the resolve deduplication guard

update_server_webpack_config_for_rsc always injects const { resolve } = require('path'); unconditionally, while the client config path now guards with unless path_resolve_imported?(content). If a user has a custom serverWebpackConfig.js that already imports resolve from path (but hasn't configured RSCWebpackPlugin yet), the generator will produce a duplicate binding and a runtime SyntaxError: Identifier 'resolve' has already been declared.

# current server path — always injects, no guard:
server_injected_imports = [
  "\\1",
  "const { resolve } = require('path');",
  ...
].join("\n")

# should mirror the client path:
server_injected_imports = [
  "\\1",
  ("const { resolve } = require('path');" unless path_resolve_imported?(content)),
  ...
].compact.join("\n")

A test covering a server config with a pre-existing resolve import would catch this regression.


Aliased config import creates two require('shakapacker') calls

commonjs_named_imported? correctly returns false for const { config: shakapackerConfig } = require('shakapacker'); (the alias form), so the generator injects a second require('shakapacker'):

// before generator runs
const { config: shakapackerConfig } = require('shakapacker');

// after generator runs
const { config: shakapackerConfig } = require('shakapacker');  // original
const { config } = require('shakapacker');                      // injected

Node's module cache deduplicates this at runtime, so it's not a correctness bug. But it produces a surprising double-require visible to users. Worth a brief comment in the aliased-imports test explaining this is intentional.


Multiline destructuring silently fails detection

commonjs_named_imported? processes one line at a time via content.lines.any?. A multiline pattern:

const {
  config,
} = require('shakapacker');

won't be detected, leading to a duplicate import. Unlikely in Shakapacker-generated configs, but worth a brief comment on the method.


Minor: include pattern is missing .mjs/.cjs

include: /\.(js|ts|jsx|tsx)$/,

The doctor command already recognizes .mjs and .cjs entrypoints. RSC "use client" directives in .mjs files would be silently missed. Low priority, but aligning with the doctor command's extension set would be more future-proof.


Verdict

The fix is correct and well-tested for the common cases. The server config deduplication gap is the only item worth addressing before merge — it's a latent SyntaxError for users with a non-standard server config who run the RSC generator. The rest are minor nits.

Comment thread react_on_rails/lib/generators/react_on_rails/rsc_setup.rb Outdated
Comment thread react_on_rails/lib/generators/react_on_rails/rsc_setup.rb
Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 43a87cc3ce

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread react_on_rails/lib/generators/react_on_rails/rsc_setup.rb Outdated
@claude
Copy link
Copy Markdown
Contributor

claude Bot commented May 4, 2026

Code Review

Overview

This PR fixes a real CI bug (#3201) where the RSCWebpackPlugin, when clientReferences was omitted, scanned the entire project tree and accidentally compiled vendored gem templates under vendor/bundle. The fix scopes discovery to Shakapacker's source_path by generating a rscClientReferences object and injecting it into both the generator templates and the existing-config rewrite logic.

The approach is sound: generate the scoped references once at the module level, share them between server and client plugin calls, guard against duplicate imports when rewriting existing configs, and document the risk clearly. The new test matrix is comprehensive.


Issues & Suggestions

Bug risk: path_resolve_imported? fallback regex is too broad

def path_resolve_imported?(content)
  commonjs_named_imported?(content, "path", "resolve") ||
    content.match?(/^\s*const\s+resolve\s*=/)   # ← too broad
end

The second arm matches any const resolve = … declaration—including user-defined functions or callbacks named resolve (e.g., from a Promise, a custom helper). If a user's config happens to have const resolve = promiseResolve, the method returns true, the generator skips injecting require('path'), and resolve(config.source_path) silently calls the wrong function at build time.

The first arm (commonjs_named_imported?) already handles the standard forms:

  • const { resolve } = require('path');
  • const {\n resolve,\n} = require('path');

The only case the second arm adds is something like const resolve = require('path').resolve, which is unusual in webpack configs. Consider narrowing the fallback:

def path_resolve_imported?(content)
  commonjs_named_imported?(content, "path", "resolve") ||
    content.match?(/^\s*const\s+resolve\s*=\s*require\(['"]path['"]\)/)
end

Minor: aliased shakapacker imports produce a redundant require call

When an existing config has const { config: shakapackerConfig } = require('shakapacker');, shakapacker_config_imported? returns false (the binding config: alias is not recognized as the config binding), so the generator injects a second require('shakapacker') call:

const { config: shakapackerConfig } = require('shakapacker'); // pre-existing
// ...injected by generator:
const { config } = require('shakapacker');

This is harmless at runtime due to Node module caching and is explicitly tested—but it may surprise users who audit their generated configs. A comment in the spec or a TODO noting the known duplicate would help future readers understand the intent.

Minor: commonjs_named_imported? splits on , but does not strip inline comments

If a destructure has inline comments on a binding line, e.g.:

const {
  merge, // used for deep config merge
  config,
} = require('shakapacker');

After split(","), the token " // used for deep config merge\n config" does not strip to "config", so config would not be detected. This is an edge case unlikely to appear in generated webpack configs, but it could bite users who add comments inside their destructures before running the RSC generator.


Positive notes

  • The duplicate-import guards (shakapacker_config_imported?, path_resolve_imported?) and their test matrix (single-line, multi-line, aliased, pre-existing) are thorough and clearly written.
  • Using Regexp.escape in commonjs_named_imported? is correct and prevents any package name from being interpreted as a regex.
  • The <<~'JS'.chomp heredoc correctly prevents Ruby string interpolation in the generated JavaScript snippet.
  • Template and rewrite logic stay in sync—the same rscClientReferences shape is used in both paths, so fresh installs and existing-config upgrades produce identical output.
  • CHANGELOG and docs updates are accurate and explain the why clearly.

Comment thread react_on_rails/lib/generators/react_on_rails/rsc_setup.rb Outdated
Comment thread react_on_rails/lib/generators/react_on_rails/rsc_setup.rb
Comment thread react_on_rails/spec/react_on_rails/generators/rsc_generator_spec.rb
@claude
Copy link
Copy Markdown
Contributor

claude Bot commented May 4, 2026

Code Review

Overview

This PR scopes RSC webpack plugin's client-reference scanning to Shakapacker.config.source_path instead of the entire project root, preventing CI failures where vendored gem templates under vendor/bundle get accidentally picked up and compiled by webpack. The fix is applied to both the generator templates (fresh installs) and the existing-config transform methods.

The core approach — computing rscClientReferences from Shakapacker's source_path and passing it as clientReferences to RSCWebpackPlugin — is correct and well-targeted.


Issues Found

Existing apps with old generator output are not auto-upgraded

Both update_server_webpack_config_for_rsc and update_client_webpack_config_for_rsc bail out early when RSCWebpackPlugin is already present:

return if content.include?("RSCWebpackPlugin")

This means users who previously ran the generator (which produced new RSCWebpackPlugin({ isServer: true }) without clientReferences) won't get the scoped fix automatically — their CI will keep failing. The PR fixes new installs and re-runs from scratch, but not incremental upgrades. This should be documented clearly in the changelog and/or a migration note, since those users are the ones hitting issue #3201.

Aliased Shakapacker import causes a duplicate require('shakapacker')

When an existing client webpack config has const { config: shakapackerConfig } = require('shakapacker');, shakapacker_config_imported? correctly returns false (since config is not bound under that name), so the generator injects a fresh const { config } = require('shakapacker');. The result is two separate require('shakapacker') lines in the same file:

const { config: shakapackerConfig } = require('shakapacker'); // original
const { config } = require('shakapacker');                    // injected

The test covers this case and treats it as acceptable. Node's module cache means there's no runtime cost, but it's a confusing code smell. A comment in the test explaining why this is intentional would help future maintainers.

/m flag in commonjs_named_imported? is a no-op

The regex uses /m but contains no . — in Ruby, /m only changes whether . matches \n. Character classes like [^}]* and \s already match newlines regardless. The flag is harmless but may mislead future readers.

path_resolve_imported? misses const path = require('path')

If an existing file imports the entire path module as const path = require('path') and calls path.resolve(...), neither branch of path_resolve_imported? detects it. The generator will add a redundant const { resolve } = require('path');. The two bindings don't conflict, so this is not a bug — but it is a minor false negative worth noting.


Positive Aspects

  • Duplication-prevention logic is well-tested: four distinct contexts cover single-line named import, multi-line destructured import, aliased import, and already-imported-resolve.
  • The rsc_client_references_js helper de-duplicates the JS snippet and keeps templates and transform methods in sync.
  • Doc and changelog updates are accurate and clearly explain the risk.

Verdict

The fix is correct and addresses the root cause. The main gap is that existing apps that already have RSCWebpackPlugin configured won't be auto-upgraded and need to manually add clientReferences. This should be called out explicitly in the changelog entry or a migration note, since those users are the ones experiencing CI failures from issue #3201.

Comment thread react_on_rails/lib/generators/react_on_rails/rsc_setup.rb Outdated
Comment thread react_on_rails/lib/generators/react_on_rails/rsc_setup.rb Outdated
Comment thread react_on_rails/lib/generators/react_on_rails/rsc_setup.rb
Comment thread react_on_rails/lib/generators/react_on_rails/rsc_setup.rb
Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 184460aa25

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread react_on_rails/lib/generators/react_on_rails/rsc_setup.rb Outdated
Copy link
Copy Markdown

@cursor cursor Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes and found 1 potential issue.

Autofix Details

Bugbot Autofix prepared a fix for the issue found in the latest run.

  • ✅ Fixed: Partial gsub can produce undefined variable reference
    • Guarded the plugin rewrite on successful client-reference setup and added verification/tests so customized configs warn instead of receiving undefined references.

Create PR

Or push these changes by commenting:

@cursor push 39dc680411
Preview (39dc680411)
diff --git a/react_on_rails/lib/generators/react_on_rails/rsc_setup.rb b/react_on_rails/lib/generators/react_on_rails/rsc_setup.rb
--- a/react_on_rails/lib/generators/react_on_rails/rsc_setup.rb
+++ b/react_on_rails/lib/generators/react_on_rails/rsc_setup.rb
@@ -489,6 +489,7 @@
         missing = []
         missing << "RSCWebpackPlugin in serverWebpackConfig.js" unless content.include?("RSCWebpackPlugin")
         missing << "rscBundle parameter in serverWebpackConfig.js" unless content.include?("rscBundle")
+        missing.concat(check_rsc_client_references(content, "serverWebpackConfig.js"))
         missing
       end
 
@@ -497,7 +498,10 @@
         return [] unless File.exist?(path)
 
         content = File.read(path)
-        content.include?("RSCWebpackPlugin") ? [] : ["RSCWebpackPlugin in clientWebpackConfig.js"]
+        missing = []
+        missing << "RSCWebpackPlugin in clientWebpackConfig.js" unless content.include?("RSCWebpackPlugin")
+        missing.concat(check_rsc_client_references(content, "clientWebpackConfig.js"))
+        missing
       end
 
       def check_rsc_scob_config
@@ -521,7 +525,7 @@
       def update_existing_rsc_webpack_config(config_path, content, is_server:)
         return if content.include?("clientReferences: rscClientReferences")
 
-        add_rsc_client_references_setup(config_path, content, is_server: is_server)
+        return unless add_rsc_client_references_setup(config_path, content, is_server: is_server)
 
         gsub_file(
           config_path,
@@ -549,9 +553,25 @@
                            %r{(const commonWebpackConfig = require\('\./commonWebpackConfig'\);)}
                          end
 
+        return false unless content.match?(import_pattern)
+
         gsub_file(config_path, import_pattern, injected_imports)
+        true
       end
 
+      def check_rsc_client_references(content, filename)
+        return [] unless content.include?("RSCWebpackPlugin")
+
+        missing = []
+        unless content.include?("clientReferences: rscClientReferences")
+          missing << "clientReferences option in #{filename}"
+        end
+        if content.include?("clientReferences: rscClientReferences") && !content.include?("const rscClientReferences =")
+          missing << "rscClientReferences definition in #{filename}"
+        end
+        missing
+      end
+
       def shakapacker_config_imported?(content)
         commonjs_named_imported?(content, "shakapacker", "config")
       end

diff --git a/react_on_rails/spec/react_on_rails/generators/rsc_generator_spec.rb b/react_on_rails/spec/react_on_rails/generators/rsc_generator_spec.rb
--- a/react_on_rails/spec/react_on_rails/generators/rsc_generator_spec.rb
+++ b/react_on_rails/spec/react_on_rails/generators/rsc_generator_spec.rb
@@ -303,6 +303,68 @@
     end
   end
 
+  context "when existing RSC webpack configs have customized import anchors" do
+    before(:all) do
+      prepare_destination
+      simulate_existing_rails_files(package_json: true)
+      simulate_npm_files(package_json: true)
+      simulate_existing_file("config/initializers/react_on_rails_pro.rb", <<~RUBY)
+        ReactOnRailsPro.configure do |config|
+          config.server_renderer = "NodeRenderer"
+        end
+      RUBY
+      simulate_existing_file("Procfile.dev", "rails: bin/rails s\n")
+      simulate_pro_webpack_files
+      simulate_existing_file("config/webpack/serverWebpackConfig.js", <<~JS)
+        const { RSCWebpackPlugin } = require('react-on-rails-rsc/WebpackPlugin');
+        const webpack = require('webpack');
+
+        const configureServer = (rscBundle = false) => {
+          const serverWebpackConfig = { plugins: [] };
+
+          if (!rscBundle) {
+            serverWebpackConfig.plugins.push(new RSCWebpackPlugin({ isServer: true }));
+          }
+
+          return serverWebpackConfig;
+        };
+
+        module.exports = { default: configureServer };
+      JS
+      simulate_existing_file("config/webpack/clientWebpackConfig.js", <<~JS)
+        const { RSCWebpackPlugin } = require('react-on-rails-rsc/WebpackPlugin');
+
+        const configureClient = () => {
+          const clientConfig = { plugins: [] };
+          clientConfig.plugins.push(new RSCWebpackPlugin({ isServer: false }));
+
+          return clientConfig;
+        };
+
+        module.exports = configureClient;
+      JS
+
+      Dir.chdir(destination_root) do
+        run_generator(["--force"])
+      end
+    end
+
+    it "does not add undefined rscClientReferences references" do
+      assert_file "config/webpack/serverWebpackConfig.js" do |content|
+        expect(content).not_to include("clientReferences: rscClientReferences")
+        expect(content).not_to include("const rscClientReferences =")
+      end
+      assert_file "config/webpack/clientWebpackConfig.js" do |content|
+        expect(content).not_to include("clientReferences: rscClientReferences")
+        expect(content).not_to include("const rscClientReferences =")
+      end
+
+      warning_text = GeneratorMessages.messages.join("\n")
+      expect(warning_text).to include("clientReferences option in serverWebpackConfig.js")
+      expect(warning_text).to include("clientReferences option in clientWebpackConfig.js")
+    end
+  end
+
   context "when the client webpack config uses aliased imports" do
     before(:all) do
       prepare_destination

You can send follow-ups to the cloud agent here.

Comment thread react_on_rails/lib/generators/react_on_rails/rsc_setup.rb
@claude
Copy link
Copy Markdown
Contributor

claude Bot commented May 4, 2026

Code Review — PR #3219: Scope RSC client reference discovery

Overview

This PR fixes a real CI problem (#3201) cleanly: by scoping clientReferences to config.source_path the plugin stops accidentally compiling vendored gem templates under vendor/bundle. The helper-method extraction is a good structural improvement, the templates are updated consistently, and the test matrix covers the main new behaviours.

I found four issues worth addressing before merge — one is a silent failure path.


Issues

1. Silent no-op when user has a customised RSCWebpackPlugin call (medium — silent failure)

update_existing_rsc_webpack_config matches with:

/new RSCWebpackPlugin\(\{\s*isServer: #{is_server}\s*\}\)/

This only matches the original bare form new RSCWebpackPlugin({ isServer: true }). If the user ever added a second option (new RSCWebpackPlugin({ isServer: true, someOtherOption: … })), gsub_file finds no match and the file is left unchanged — but the method returns silently with no warning. The user would then have the new rscClientReferences constant injected into their file but it would never actually be used.

Consider broadening the pattern to tolerate trailing options:

/new RSCWebpackPlugin\(\{([^}]*?isServer: #{is_server}[^}]*?)\}\)/

…and either raise/warn on no-match, or verify the replacement happened.

2. Aliased Shakapacker import generates a duplicate require (minor — potentially confusing)

commonjs_named_imported? checks for binding == binding_name and binding.start_with?("#{binding_name} =") but not for binding_name: (rename alias). So const { config: shakapackerConfig } = require('shakapacker'); is not recognised as an import of config, causing the generator to inject a second require('shakapacker'). The test at line 334 documents this as intentional, but:

  • The generated file now contains two require('shakapacker') statements.
  • If the user intentionally aliased config to avoid a naming collision (e.g. they already have a local config variable), the injected const { config } = require('shakapacker'); silently shadows or conflicts with it.

At minimum the test description "injects a usable config import without redeclaring resolve" should call out that it does create a second Shakapacker require. Better still, recognise config: aliases in commonjs_named_imported?.

3. Idempotency guard can be tricked by a comment (low)

return if content.include?("clientReferences: rscClientReferences")

A line like // clientReferences: rscClientReferences in a comment would cause the guard to fire, skipping the migration silently. A tighter check (e.g. a regex requiring the surrounding new RSCWebpackPlugin context, or checking for the constant definition) would be more robust.

4. Superfluous /m flag on the commonjs_named_imported? regex (nit)

pattern = /^\s*const\s+\{([^}]*)\}\s*=\s*require\(['"]#{Regexp.escape(package_name)}['"]\);?/m

In Ruby the /m flag makes . match newlines — it has no effect here because the pattern uses [^}] (which already spans newlines) and doesn't use . at all. It's harmless but misleading; removing it would help future readers.


What's good

  • The root cause fix (scope directory to source_path) is correct and the generated output looks right.
  • Import-deduplication helpers (shakapacker_config_imported?, path_resolve_imported?) are well-extracted and the multi-line destructuring test is appreciated.
  • The rspack path is handled transparently via destination_config_path — no duplication needed.
  • Changelog and docs update are clear and accurate.

@claude
Copy link
Copy Markdown
Contributor

claude Bot commented May 22, 2026

Code Review: PR #3219 — Scope RSC Client Reference Discovery

Overview

This PR fixes a real CI pain point (#3201): without a scoped clientReferences, RSCWebpackPlugin scans the entire project root — including vendor/bundle — which can pick up gem template files and trigger spurious webpack compilations. The fix emits a rscClientReferences object pointing at Shakapacker's source_path and threads it through both the generator templates (new setups) and a migration path (existing configs).

The scope is substantial: ~900 lines of Ruby for the JS state-machine scanner and migration logic, plus ~1,700 lines of new tests. The tests are exceptionally thorough — CRLF handling, commented-out anchors, nested template literals, pretend mode, double-import guards, etc.


Strengths

  • Safety-first approach throughout. The scanner warns and skips rather than producing a corrupt rewrite when it can't parse a section cleanly (e.g. regex literals with unmatched braces). This is the correct default for generator migration code.
  • Pretend-mode parity. rewrite_rsc_plugin_client_references handles options[:pretend] explicitly before calling File.write directly — necessary because multi-point rewrites can't be expressed as one gsub_file.
  • Known limitations are documented. Nested template literals, regex literals with brace quantifiers, and ESM configs all get clear in-code notes and, where appropriate, user-facing warnings.
  • Test coverage is excellent. The describe 'existing RSC webpack config migration helpers' block methodically covers the edge cases that would normally slip through integration tests.

Issues & Suggestions

Misleading method name
content_before_rsc_setup_anchor slices up to anchor.end(0) — it includes the anchor text, not just the content before it. The name says "before" but the implementation returns "up to and including". Callers pass this to import-detection helpers so the semantics are actually correct, but the name will mislead the next maintainer.

Suggestion: rename to content_through_rsc_setup_anchor or add a brief inline note explaining the inclusive slice.

Duplicated O(n×m) comment
The complexity caveat appears almost verbatim in two spots — once in the NOTE docstring on rsc_plugin_option_sections and again inside rsc_plugin_option_sections_partition. One copy is enough.

add_rsc_client_references_setup guard duplication
The method opens with two guards that the only caller (ensure_rsc_client_references_setup) has already evaluated. The comment says "belt-and-suspenders", which is fine, but if the caller's logic is ever refactored, the helper will silently no-op instead of warning. Consider raising (assert) rather than silently returning if you want the safety net to actually catch regressions.

ESM import limitation not surfaced clearly in the warning
When an existing config uses ESM (import/export), the anchor is not found and the user gets the generic "expected webpack import anchor was not found" warning. Existing-config users who converted to ESM mid-way will see this with no actionable context about why. The warn_missing_rsc_client_references_anchor message could mention ESM explicitly as one likely cause.

rewrite_rsc_plugin_client_references returns false silently
When rewrites.empty?, the method returns false and the caller (update_existing_rsc_webpack_config) calls warn_missing_rsc_plugin_target. This is correct, but the short-circuit return false is the only indication — a reader has to trace the caller to find the warning. A brief inline comment would make this explicit.


Minor Notes

  • JS_STRING_DELIMITERS and JS_COMMENT_STATES as frozen module constants are a clean way to share the scanner surface across methods.
  • String.new(capacity: options.length) in rsc_plugin_options_without_comments is a nice low-overhead optimization.
  • The regex \bisServer\s*:\s*#{Regexp.escape(is_server.to_s)}\b correctly uses \b to prevent matching isServer: trueish or isServer: falsehood — the word boundary is load-bearing here.

Summary

This is well-designed migration tooling with strong test coverage and appropriate safety guardrails. The items above are refinements rather than blockers. The naming issue with content_before_rsc_setup_anchor is the most likely to cause future confusion; the rest are polish.

Comment thread react_on_rails/lib/generators/react_on_rails/rsc_setup.rb
Comment thread react_on_rails/lib/generators/react_on_rails/rsc_setup.rb
Comment thread react_on_rails/lib/generators/react_on_rails/rsc_setup.rb
Comment thread react_on_rails/lib/generators/react_on_rails/rsc_setup.rb
- Make `rsc_plugin_is_server_match?` depth-aware via `js_top_level_position?` so a
  nested `metadata: { isServer: true }` inside another option's value is no longer
  routed into the wrong `is_server:` partition bucket. Previously the section was
  marked safe for the mismatched bucket, the splice helper correctly bailed (it is
  already depth-aware), and `warn_missing_rsc_plugin_target` then fired with a
  misleading "no plugin options with isServer: <value> could be rewritten" warning
  for a config that was actually fine. Partition and splice now agree on what
  counts as a top-level `isServer:` pair.
- Replace the side-effecting `any?` block in `rsc_client_references_setup_anchor_match`
  with an explicit `each` + early `return` so the "first code-position match wins"
  intent is obvious without an outer-scope capture.
- Document the block-comment exit contract on `advance_js_scan_state` (callers
  whose post-call branch inspects `char` as a significant character must guard on
  `prev_state == :block_comment`) and add an inline note at the one caller that
  relies on this invariant implicitly (`matching_js_closing_brace`).

Adds a regression test that bucketing a section with `metadata: { isServer: true }`
plus top-level `isServer: false` lands the section only in the `is_server: false`
partition, never `is_server: true`.
@justin808
Copy link
Copy Markdown
Member Author

Round 3 review addressed

Pushed commit addressing the three open review threads:

  1. rsc_plugin_is_server_match? depth-awareness — suppresses the misleading "no plugin options with isServer: <value> could be rewritten" warning when a nested metadata: { isServer: ... } shadows the top-level isServer: value. Now uses js_top_level_position? consistent with rsc_plugin_body_has_top_level_key? and add_client_references_to_rsc_plugin_body. Added a regression test covering metadata: { isServer: true } + top-level isServer: false.
  2. rsc_client_references_setup_anchor_match style — replaced the side-effecting any? block with an explicit each + early return so the "first code-position match wins" intent is obvious without an outer-scope capture.
  3. advance_js_scan_state block-comment contract — documented the post-call guard requirement (prev_state == :block_comment after a */ exit) and added an inline note at matching_js_closing_brace flagging its implicit reliance, with a searchable invariant tag (block_comment_exit_guard).

Verification:

  • bundle exec rubocop lib/generators/react_on_rails/rsc_setup.rb spec/react_on_rails/generators/rsc_generator_spec.rb → no offenses
  • bundle exec rspec spec/react_on_rails/generators/rsc_generator_spec.rb → 139 examples, 0 failures

Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: a22b805141

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread react_on_rails/lib/generators/react_on_rails/rsc_setup.rb Outdated
@claude
Copy link
Copy Markdown
Contributor

claude Bot commented May 22, 2026

Code Review

Overview

This PR fixes a real CI reliability problem: without scoped clientReferences, RSCWebpackPlugin scans the entire project root, picking up vendored gem templates under vendor/bundle and causing spurious webpack compile failures. The fix is correct at the template level.

The significant addition is a migration path for existing webpack configs — a ~927-line lightweight JavaScript scanner/rewriter embedded in the Rails generator. That is the part that deserves careful scrutiny.


Strengths

  • Correct fix at the root: templates now emit scoped clientReferences and the migration path rewrites existing configs.
  • Outstanding test coverage: ~1,700 new spec lines cover comment-awareness, CRLF endings, nested objects, pretend mode, aliased imports, regex-literal edge cases, and conflict detection. The ratio of test code to implementation is commendable.
  • Conservative safety posture: unrecognized/unparseable configs get warnings and are left untouched rather than silently corrupted.
  • Documentation updated with a Generator note about the ESM limitation.

Issues

Medium — Direct File.write bypasses Thor's built-in conflict detection

In two places the generator writes files with File.write directly instead of Thor's create_file/force_inject_into_file:

  • rewrite_rsc_plugin_client_references (~line 996)
  • replace_rsc_client_references_setup_anchor (~line 1182)

The comments acknowledge this is intentional because multi-point rewrites cannot be expressed as a single gsub_file call, and pretend mode is handled manually. That rationale is sound, but it means:

  1. Thor's --force / --skip flags are not respected for these writes — a user who runs the generator with --skip will still see the file mutated.
  2. File-permission errors surface as unrescued Ruby exceptions rather than the friendly generator error messages Thor produces.

Recommend either: (a) accepting these constraints and documenting them explicitly, or (b) adding a helper that honors options[:skip] in addition to options[:pretend].

Medium — rsc_client_references_setup_ready? reads the file a third time

The flow for the existing-config path is:

  1. update_existing_rsc_webpack_config receives content from the caller's read.
  2. ensure_rsc_client_references_setupadd_rsc_client_references_setupreplace_rsc_client_references_setup_anchor writes the file.
  3. rewrite_rsc_plugin_client_references re-reads the file (documented: stale offsets after step 2).
  4. rsc_client_references_setup_ready? reads it again to verify the write succeeded.

The triple-read is not a bug (webpack configs are tiny), but step 4 only guards against an already-handled failure mode (File.write either succeeds or raises). Consider whether this verification read can be replaced by checking the return value of replace_rsc_client_references_setup_anchor.

Low — Dead fallback in splice_client_references_at_close_brace

indent = content[last_line_start..][/\A[ \t]*/] || "  "

/\A[ \t]*/ always matches (it can match the empty string), so the || " " fallback is unreachable — indent will be "" when the last line has no leading whitespace, not the intended two-space default. This only matters for edge-case inputs, but the dead default may mislead future readers.

Low — O(n × m) warning comment is duplicated

The O(n × m) cost note appears with nearly identical wording in both rsc_plugin_option_sections and rsc_plugin_option_sections_partition. One can be removed; the other should be the authoritative location.


ESM limitation not surfaced at runtime

The docs note says:

If your config has been converted to ESM syntax, the generator emits an "expected webpack import anchor was not found" warning

That warning fires from warn_missing_rsc_client_references_anchor, but its text says only "expected webpack import anchor was not found" with no mention of ESM. A user whose file uses ESM will only know to look in the docs if they happen to have read the PR or migration guide. Consider appending something like "(ESM configs must be updated manually — see rsc-preparing-app.md)" to the warning message so it is self-contained at run time.


Summary

The fix is correct and the test suite is thorough. The two medium issues (direct File.write not honoring --skip, and the verification re-read) are worth addressing before merge. Everything else is polish.

Comment thread react_on_rails/lib/generators/react_on_rails/rsc_setup.rb
Comment thread react_on_rails/lib/generators/react_on_rails/rsc_setup.rb Outdated
Comment thread react_on_rails/lib/generators/react_on_rails/rsc_setup.rb Outdated
Comment thread react_on_rails/lib/generators/react_on_rails/rsc_setup.rb
Comment thread react_on_rails/lib/generators/react_on_rails/rsc_setup.rb
- Rename `content_before_rsc_setup_anchor` to `content_through_rsc_setup_anchor`
  and add a doc comment explaining the inclusive slice — callers
  (`rsc_setup_blocked_by_later_imports?`, `inject_rsc_*_imports`, import-detection
  helpers) need the anchor itself in the returned content to check whether required
  imports appear up to and including the anchor line.
- Drop the redundant O(n × m) note inside `rsc_plugin_option_sections_partition`;
  the wrapper `rsc_plugin_option_sections` already carries the perf-cost note for
  the public surface.
- Add an inline comment at `return false if rewrites.empty?` documenting that the
  sole caller (`update_existing_rsc_webpack_config`) is responsible for warning via
  `warn_missing_rsc_plugin_target`, so the silent return is intentional.
- Expand `warn_missing_rsc_client_references_anchor` to mention that the generator
  looks for the CommonJS `require`-style anchor and that ESM `import` syntax is
  unsupported, since "config converted to ESM" is the most common reason users hit
  this warning.
@justin808
Copy link
Copy Markdown
Member Author

Round 4 review addressed

Pushed commit addressing the four new threads opened against round 3:

  1. Rename content_before_rsc_setup_anchorcontent_through_rsc_setup_anchor — the slice is inclusive of the anchor (callers need it for import-position checks). Added a doc comment explaining why. Updated the 3 internal callers.
  2. Dedup the O(n×m) perf note — dropped the redundant in-method comment from rsc_plugin_option_sections_partition; the wrapper rsc_plugin_option_sections already carries it on the public surface.
  3. Document the silent-return contract — inline note at return false if rewrites.empty? pointing out that update_existing_rsc_webpack_config translates this into warn_missing_rsc_plugin_target, so callers see the warn behavior without tracing the chain.
  4. Mention ESM in the missing-anchor warningwarn_missing_rsc_client_references_anchor now hints that the generator scans for the CommonJS require-style anchor and that ESM import syntax is unsupported (the most common reason users hit this warning). Preserved the existing substring asserted by the spec.

Verification:

  • bundle exec rubocop lib/generators/react_on_rails/rsc_setup.rb → no offenses
  • bundle exec rspec spec/react_on_rails/generators/rsc_generator_spec.rb → 139 examples, 0 failures

- Tolerate a single trailing comma between an RSCWebpackPlugin options object
  and the closing `)` so configs formatted with Prettier's `trailingComma: "all"`
  (`new RSCWebpackPlugin({...},)`) are no longer marked unparseable. A second
  comma still bails — that would be invalid JS. Adds a regression test that the
  partition routes a trailing-comma section into the `:safe` bucket.
- Fix dead-code fallback in `splice_client_references_at_close_brace`: the
  previous indent regex `/\A[ \t]*/` (zero-or-more) always matched at least the
  empty string, so the `|| "  "` fallback could never fire. Switched to
  `/\A[ \t]+/` (one-or-more) so an unindented last line correctly hits the
  fallback instead of producing a zero-width indent.
@justin808
Copy link
Copy Markdown
Member Author

Round 5 review addressed

Round 5 produced 6 new threads — 2 substantive bugs, 1 intentional-as-designed, and 3 that were already resolved by round 4. Pushed commit with the 2 fixes plus reply-only resolution on the rest.

Fixed:

  1. Trailing comma after plugin options ([codex]): rsc_plugin_options_followed_by_close_paren? now tolerates a single , between } and ) so Prettier-trailingComma: "all" configs (new RSCWebpackPlugin({...},)) aren't flagged as unparseable. Regression test added.
  2. Dead-code fallback in indent regex: splice_client_references_at_close_brace was using /\A[ \t]*/ (zero-or-more), so the || " " branch was unreachable. Switched to /\A[ \t]+/ so the fallback actually fires for unindented last lines.

Reply-only resolutions:

  • options[:skip] guard: intentional — --skip isn't a documented dry-run path on this generator; --pretend is. Migrations are only attempted when the user already has RSCWebpackPlugin in the file, so silently bypassing them would be the opposite of expected behavior.
  • O(n×m) duplicate note: already addressed in round 4 (the internal note was removed; wrapper kept).
  • Self-contained missing-anchor warning: already addressed in round 4 (the ESM hint is now inline in the warning, not just the docs).
  • Third file read in verification: kept as-is since the cost is negligible on tiny webpack configs; left as a future-refactor pointer if the migration path is ever extended.

Verification:

  • bundle exec rubocop lib/generators/react_on_rails/rsc_setup.rb spec/react_on_rails/generators/rsc_generator_spec.rb → no offenses
  • bundle exec rspec spec/react_on_rails/generators/rsc_generator_spec.rb → 140 examples, 0 failures

@claude
Copy link
Copy Markdown
Contributor

claude Bot commented May 22, 2026

Code Review — PR #3219: Fix RSC client reference scope

Overview

This PR fixes a real CI regression (#3201) where RSCWebpackPlugin without a scoped clientReferences option would recursively scan vendor/bundle, causing webpack to attempt compiling vendored gem templates. The fix has three parts:

  1. Fresh-generated templates (clientWebpackConfig.js.tt, serverWebpackConfig.js.tt) now always emit a rscClientReferences = { directory: resolve(config.source_path), ... } helper and pass it to the plugin.
  2. Existing-config migration — the react_on_rails:rsc generator can now detect existing RSCWebpackPlugin calls and patch them in place with the scoped helper, including safe detection and injection of the missing require('shakapacker') / require('path') imports.
  3. Docs make clientReferences prescriptive rather than advisory.

Code quality

Strengths

  • The lightweight JavaScript scanner (~300 lines of Ruby: advance_js_scan_state, matching_js_closing_brace, js_top_level_position?, etc.) is well-documented and handles the known limitations explicitly — regex literals with unmatched braces are flagged as unparseable rather than silently corrupted, and all callers correctly observe the block_comment_exit_guard contract.
  • The migration path emits specific, actionable warnings for every failure mode (conflicting bindings, misplaced imports, ESM anchors, etc.) rather than failing silently.
  • Test coverage is excellent — 1,691 new lines covering CRLF line endings, double-quoted import anchors, aliased destructuring, function-scoped vs. module-scoped bindings, multi-point rewrites, pretend mode, and many edge-case plugin shapes.
  • The reverse-order splice in rewrite_rsc_plugin_client_references correctly keeps earlier byte offsets valid while applying multiple rewrites to the same file.

Concerns

1. O(n²×m) actual complexity (comment at line 699 says O(n×m))

The noted complexity is understated. js_code_position? scans from index 0 to the target on every call, making it O(n). It is called once per new RSCWebpackPlugin( hit inside rsc_plugin_option_sections_partition, yielding O(n²×m). For small webpack configs this is irrelevant, but the comment should be corrected so future readers don't build further O(n) callers under the assumption only a single linear pass exists.

2. advance_js_scan_state block-comment exit quirk is a maintenance risk

The block_comment_exit_guard invariant (when returning from */, char = '*' but index already points at '/') is correctly observed by all six callers today. The problem is that this is an unusual contract: the function both clears state and leaves the caller's char variable pointing at a non-significant character. Any future caller that checks char for significance (e.g. adds a char == '/' branch) without reading the contract comment will misfire. See inline note at the method.

3. splice_client_references_at_close_brace indent fallback

When the last line of the plugin options body starts at column 0 (no indentation before the closing brace), content[last_line_start..][/\A[ \t]*/] returns "" and the fallback || " " kicks in, injecting 2-space indentation regardless of whether the file uses tabs or a different width. This is cosmetic but could trigger a linter in strict projects.

4. ESM config detection happens late

When a config uses ESM (import/export) syntax, the generator proceeds through rsc_plugin_sections_safe_to_rewrite?rewritable_rsc_plugin?ensure_rsc_client_references_setup before finally failing inside rsc_client_references_setup_anchor? and emitting the "expected webpack import anchor was not found" warning. No files are modified in this path, which is correct — but users may be surprised to see multiple prior generator steps succeed before the warning appears. This is documented in the docs and code but not in the warning message itself.


Security

No concerns — this is a local config generator with no network I/O.

Verdict

The fix is correct, the test coverage is thorough, and the failure modes are all handled gracefully. The concerns above are minor; items 1 and 3 are the most actionable. Overall this is a well-crafted PR.

Comment thread react_on_rails/lib/generators/react_on_rails/rsc_setup.rb Outdated
Comment thread react_on_rails/lib/generators/react_on_rails/rsc_setup.rb
trailing = body[/\s*\z/]
content = body[0...(body.length - trailing.length)]
content_without_comments = rsc_plugin_options_without_comments(content).rstrip
separator = content_without_comments.end_with?(",") ? " " : ", "
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When the last non-trailing-whitespace line of the options body starts at column 0 (i.e. no indentation before the closing-brace line), content[last_line_start..][/\A[ \t]*/] returns "" and the || " " fallback injects 2-space indentation unconditionally. In a tab-indented or 4-space project this produces a cosmetically mismatched line that a linter may flag.

A small improvement: prefer the indentation of the nearest non-blank line above rather than hard-coding " " as the default, or at least document the 2-space assumption in the comment above rsc_client_references_js.

# appears inside a function body (which does not produce a module-scope binding).
next false unless js_top_level_position?(content, Regexp.last_match.begin(0))

bindings = captures.first
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

binding.start_with?("#{binding_name} =") requires a space before =. The pattern const { config=fn() } = require('shakapacker') (no space, which JS style guides discourage but do not forbid) would not be detected, causing the generator to inject a duplicate const { config } = require('shakapacker');. This is a very minor edge case — virtually no real webpack config omits the space — but it's worth a note since the import-deduplication logic elsewhere handles whitespace variation explicitly.

@claude
Copy link
Copy Markdown
Contributor

claude Bot commented May 22, 2026

Code Review: Fix RSC client reference scope

Overview

This PR fixes a real CI pain point (Issue #3201) where omitting clientReferences in RSCWebpackPlugin caused webpack to scan vendored gem templates under vendor/bundle. The fix scopes discovery to Shakapacker's config.source_path for both server and client bundles.

The approach is sound: update the generator templates for new installs, and add a migration path for existing configs. The migration path includes a lightweight Ruby-based JS scanner to safely navigate comments, strings, and brace nesting — with clearly documented limitations and extensive safety checks.

Test coverage is exceptional — 25+ new spec contexts covering edge cases including CRLF line endings, aliased imports, nested objects, comment-only matches, pretend mode, and unparseable regex literals.


Issues

Potential binding-conflict false negatives

top_level_config_binding? and top_level_resolve_binding? miss destructured imports from non-expected packages.

Both conflict-detection helpers use patterns like:

pattern = /^[ \t]*(?:(?:const|let|var)\s+config\b|function\s+config\s*\()/

This matches const config = ... but not const { config } = require('not-shakapacker'). If a user has:

const { config } = require('some-internal-config-package');

then shakapacker_config_imported? returns false (wrong package), top_level_config_binding? also returns false (pattern doesn't match destructuring), and the generator proceeds to inject const { config } = require('shakapacker') — creating a binding that shadows the existing one with no warning. The same gap exists for top_level_resolve_binding? vs. const { resolve } = require('some-other-module').

This is a real-world edge case (unlikely in webpack configs), but closing the gap would prevent silent corruption.


Multiple content scans within update_existing_rsc_webpack_config

rsc_plugin_option_sections_partition is O(n × m) and is called separately by rsc_plugin_sections_safe_to_rewrite?, rsc_plugin_uses_scoped_client_references?, and rewritable_rsc_plugin? — all on the same content string. rewrite_rsc_plugin_client_references then re-reads from disk and scans again.

The comment correctly notes this is fine for typical webpack config sizes. For future maintainers: if this method is ever called on many files, memoizing the partition result within one call to update_existing_rsc_webpack_config would be the right fix.


Positives

  • Safety-first design: Every rewrite path has a pre-flight check, and failures always warn rather than silently skip or silently corrupt. The rsc_client_references_setup_ready? post-write verification is a good belt-and-suspenders touch.
  • block_comment_exit_guard contract: The shared state machine has a subtle invariant at */ exit. The searchable invariant name and inline docs make it maintainable for future readers.
  • Pretend mode: Fully propagated through both helper injection and plugin rewrite paths, including the ensure_rsc_client_references_setup → rewrite_rsc_plugin_client_references chain. The spec at line 2604 explicitly verifies pretend mode does not produce a false "helper not written" warning.
  • Trailing-comment comma splice: build_splice_prefix correctly inserts the comma before a trailing // line comment rather than inside it — the test at line 1967 validates this.
  • CRLF support: Setup anchor regexes use \r?\n — tested explicitly.
  • rsc_plugin_client_references_configured? returns true for dynamic invocations: Avoids noisy warnings when the plugin is called as RSCWebpackPlugin(buildOptions()). Right tradeoff.
  • Depth-aware isServer: matching: rsc_plugin_is_server_match? correctly avoids routing metadata: { isServer: true } into the wrong partition bucket — and the spec at line 2395 explicitly covers this.

Minor nits

  • inject_rsc_client_imports and inject_rsc_server_imports are mirror methods that differ only in whether they include shakapacker_config_import_statement. The asymmetry is intentional (server config already requires config via the bundler ternary anchor), but a brief comment in inject_rsc_server_imports explaining the omission would help future readers.
  • The rsc_client_references_setup_anchor_available? vs. rsc_client_references_setup_anchor? naming is slightly confusing — _available? wraps _anchor? with a warning side-effect. A comment noting it is the "warn-and-return" variant of _anchor? would clarify intent.

Summary

The core fix is correct and well-tested. The migration path is defensively coded with clear user-facing warnings. The two binding-conflict false negatives are worth addressing before this lands, but the overall approach and implementation are solid.

Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 96e630f6bc

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

# (`const { config /* primary */ } = require('shakapacker')`) are also unsupported:
# they leave their text in the captured binding so the exact `config` match fails.
# All of these shapes are vanishingly rare in real webpack configs.
binding == binding_name || binding.start_with?("#{binding_name} =")
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Treat self-aliased destructuring as an existing binding

commonjs_named_imported? only accepts config or config = ..., so valid self-alias forms like const { config: config } = require('shakapacker') (and similarly resolve: resolve) are treated as missing imports. The migration then injects const { config } = ... / const { resolve } = ..., which redeclares an already-declared identifier and makes the generated webpack config fail to parse with Identifier has already been declared when rerunning the generator on such customized configs.

Useful? React with 👍 / 👎.

# gsub_file call, and pretend mode is already handled above.
File.write(full_path, content)
say_status(:rewrite, config_path, :green)
end
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The pattern here (and the analogous one in top_level_resolve_binding?) only matches the const config = ... form, not the destructured form const { config } = require('some-other-package').

If a user has a non-shakapacker package that destructures config, shakapacker_config_imported? returns false (wrong package) and top_level_config_binding? also returns false (pattern misses { config }), so the generator injects const { config } = require('shakapacker') and silently shadows the existing binding.

Extending the pattern to also match destructured declarations would close the gap:

Suggested change
end
def top_level_config_binding?(content)
pattern = /^[ \t]*(?:(?:const|let|var)\s+(?:config\b|\{[^}]*\bconfig\b)|function\s+config\s*\()/
content.to_enum(:scan, pattern).any? do
js_top_level_position?(content, Regexp.last_match.begin(0))
end
end

Same fix applies to top_level_resolve_binding?const { resolve } = require('non-path-package') would slip past it today.

- Correct the partition complexity note from O(n × m) to O(n² × m): each
  `js_code_position?` call walks from index 0, so every plugin hit triggers a
  fresh O(n) rescan. The cost stays acceptable for small webpack configs, but
  the note now reflects the actual cost so future contributors don't layer
  additional O(n) callers under the impression only one linear pass exists.
- Add a unit-level invariant test for `advance_js_scan_state`'s
  `block_comment_exit_guard`: when `*/` is consumed, the returned state must be
  nil and the returned index must point at the closing `/`. Several callers
  (`matching_js_closing_brace`, `js_code_position?`, `js_top_level_position?`)
  rely on this without an explicit `prev_state == :block_comment` guard, so a
  regression here would silently misroute downstream `char == '{' / '}' / ')'`
  checks. The unit test catches the break at the helper boundary instead of
  forcing the diagnosis through a downstream caller misbehaving.
# NOTE: internally rescans from index 0 for each `new RSCWebpackPlugin(` hit, so this is
# O(n × m) in content size × plugin count. Acceptable for small webpack configs; carry
# scanner state forward before adopting this pattern for larger shared inputs.
def rsc_plugin_option_sections(content, is_server:)
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The O(n × m) comment is accurate and fine for typical webpack config sizes. Worth noting for future maintainers: rsc_plugin_option_sections_partition is called on the same content string three separate times within update_existing_rsc_webpack_config — via rsc_plugin_sections_safe_to_rewrite?, rsc_plugin_uses_scoped_client_references?, and rewritable_rsc_plugin? — plus once more after a disk re-read in rewrite_rsc_plugin_client_references. If this method is ever extended to batch-migrate many files, threading the partition result through rather than recomputing would be the natural fix.

@justin808
Copy link
Copy Markdown
Member Author

Round 6 review addressed

Pushed commit addressing 2 new threads:

  1. Correct partition complexity note — O(n × m) was understated; js_code_position? is itself O(n), so the partition is O(n² × m). Updated the comment so future contributors see the actual cost.
  2. Block-comment exit invariant test — added a unit-level test for advance_js_scan_state's block_comment_exit_guard (state must clear and index must point at the closing /). Catches regressions at the helper boundary rather than via downstream caller misbehavior. Skipped the larger sentinel-state refactor since the contract is already documented + flagged inline + now tested.

Verification:

  • bundle exec rubocop lib/generators/react_on_rails/rsc_setup.rb spec/react_on_rails/generators/rsc_generator_spec.rb → no offenses
  • bundle exec rspec spec/react_on_rails/generators/rsc_generator_spec.rb → 141 examples, 0 failures

end
end

def inject_rsc_server_imports(config_path, content, existing_imports_content)
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The asymmetry vs inject_rsc_client_imports (which includes shakapacker_config_import_statement) is intentional and correct — the server anchor already requires config to be in scope. A brief comment here would make the omission self-documenting so a future reader doesn't add it thinking it was forgotten:

Suggested change
def inject_rsc_server_imports(config_path, content, existing_imports_content)
def inject_rsc_server_imports(config_path, content, existing_imports_content)
# `shakapacker_config_import_statement` is intentionally omitted: the server anchor
# (`config.assets_bundler === 'rspack'`) requires `config` to already be imported,
# so a missing import is caught earlier by `shakapacker_config_blocker_reason`.
replace_rsc_client_references_setup_anchor(config_path, content, is_server: true) do |anchor|

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

RSCWebpackPlugin defaults to scanning project root and can bundle vendor gem templates

1 participant