react_on_rails 16.2.0.beta.8 → 16.2.0.beta.11

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (37) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +19 -13
  3. data/CLAUDE.md +136 -5
  4. data/CONTRIBUTING.md +3 -1
  5. data/Gemfile.lock +1 -1
  6. data/Steepfile +4 -0
  7. data/analysis/rake-task-duplicate-analysis.md +149 -0
  8. data/bin/ci-run-failed-specs +6 -4
  9. data/bin/ci-switch-config +4 -3
  10. data/knip.ts +1 -1
  11. data/lib/generators/react_on_rails/base_generator.rb +5 -119
  12. data/lib/generators/react_on_rails/generator_helper.rb +29 -0
  13. data/lib/generators/react_on_rails/install_generator.rb +5 -180
  14. data/lib/generators/react_on_rails/js_dependency_manager.rb +354 -0
  15. data/lib/generators/react_on_rails/templates/base/base/config/initializers/react_on_rails.rb.tt +19 -0
  16. data/lib/generators/react_on_rails/templates/base/base/config/{shakapacker.yml → shakapacker.yml.tt} +18 -2
  17. data/lib/generators/react_on_rails/templates/base/base/config/webpack/serverWebpackConfig.js.tt +38 -4
  18. data/lib/react_on_rails/configuration.rb +82 -8
  19. data/lib/react_on_rails/dev/pack_generator.rb +1 -0
  20. data/lib/react_on_rails/dev/server_manager.rb +1 -0
  21. data/lib/react_on_rails/doctor.rb +94 -4
  22. data/lib/react_on_rails/engine.rb +2 -5
  23. data/lib/react_on_rails/system_checker.rb +7 -4
  24. data/lib/react_on_rails/utils.rb +54 -0
  25. data/lib/react_on_rails/version.rb +1 -1
  26. data/react_on_rails_pro/Gemfile.lock +3 -3
  27. data/react_on_rails_pro/lib/react_on_rails_pro/version.rb +1 -1
  28. data/react_on_rails_pro/package.json +1 -1
  29. data/react_on_rails_pro/spec/dummy/Gemfile.lock +3 -3
  30. data/react_on_rails_pro/spec/dummy/bin/shakapacker-precompile-hook +19 -0
  31. data/react_on_rails_pro/spec/dummy/config/shakapacker.yml +5 -0
  32. data/sig/react_on_rails/dev/file_manager.rbs +15 -0
  33. data/sig/react_on_rails/dev/pack_generator.rbs +19 -0
  34. data/sig/react_on_rails/dev/process_manager.rbs +22 -0
  35. data/sig/react_on_rails/dev/server_manager.rbs +39 -0
  36. data/sig/react_on_rails/generators/js_dependency_manager.rbs +123 -0
  37. metadata +11 -3
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: e2d569c0148019a12155710806cbd6f3d94f6641ffd074099dc9b4255b0202d6
4
- data.tar.gz: 8abd5fedee13d2d2d1bfa6fc6a03e8a6d11710ece7fb84b26fb3afc387156d75
3
+ metadata.gz: 94e2c46e66774ffdeb5902068d74c22daf9b2eafd08b45f1d353be5de6df24b1
4
+ data.tar.gz: 49e2fc58fbc4e1df4d5a0573940b5bbe2c346c7bf226f6535e80b8e1fd82e216
5
5
  SHA512:
6
- metadata.gz: 22ef214ab6d5c12257a9ebc3fbf01c56c4211405ac4398628b1d69813d236d39dfe02d48168d4fabb0a0eb5d54ce624fb37790776952e4300547e502170432a4
7
- data.tar.gz: e1fdf86179a42c10af444265839d2e95fe15b3771f0606038ce1c01611efcd96c03d34c1e94682b718b6011c6f0d9c049b59b1713bdb75fa942a23e08e0c60b2
6
+ metadata.gz: ca050de67f3ae49a02674b0130b1b2532d090605565cff6055fa51f779741109d2415a4069ceb470a819fdcb988fab51795b278eff644c122599c484a1e6aada
7
+ data.tar.gz: 99e95152c8015ae37ca106f70ac64bb1b64e52c006b0fd0836f84a091bf00db014d2f09c81199b19624ddc8f6db68ea75c4d74ace02445a7d25cf7db3d79f529
data/CHANGELOG.md CHANGED
@@ -23,17 +23,7 @@ After a release, please make sure to run `bundle exec rake update_changelog`. Th
23
23
 
24
24
  Changes since the last non-beta release.
25
25
 
26
- #### Changed
27
-
28
- - **Generator Configuration Modernization**: Updated the generator to enable recommended configurations by default for new applications:
29
-
30
- - `config.build_test_command` is now uncommented and set to `"RAILS_ENV=test bin/shakapacker"` by default, enabling automatic asset building during tests for better integration test reliability
31
- - `config.auto_load_bundle = true` is now set by default, enabling automatic loading of component bundles
32
- - `config.components_subdirectory = "ror_components"` is now set by default, organizing React components in a dedicated subdirectory
33
-
34
- **Note:** These changes only affect newly generated applications. Existing applications are unaffected and do not need to make any changes. If you want to adopt these settings in an existing app, you can manually add them to your `config/initializers/react_on_rails.rb` file. [PR 2039](https://github.com/shakacode/react_on_rails/pull/2039) by [justin808](https://github.com/justin808).
35
-
36
- ### [16.2.0.beta.4] - 2025-11-12
26
+ ### [v16.2.0.beta.10] - 2025-11-18
37
27
 
38
28
  #### Added
39
29
 
@@ -49,6 +39,8 @@ Changes since the last non-beta release.
49
39
 
50
40
  - **Use as Git dependency**: All packages can now be installed as Git dependencies. This is useful for development and testing purposes. See [CONTRIBUTING.md](./CONTRIBUTING.md#git-dependencies) for documentation. [PR #1873](https://github.com/shakacode/react_on_rails/pull/1873) by [alexeyr-ci2](https://github.com/alexeyr-ci2).
51
41
 
42
+ - **Doctor Checks for :async Loading Strategy**: Added proactive diagnostic checks to the React on Rails doctor tool to detect usage of the `:async` loading strategy in projects without React on Rails Pro. The feature scans view files and initializer configuration, providing clear guidance to either upgrade to Pro or use alternative loading strategies like `:defer` or `:sync` to avoid component registration race conditions. [PR 2010](https://github.com/shakacode/react_on_rails/pull/2010) by [justin808](https://github.com/justin808).
43
+
52
44
  #### Changed
53
45
 
54
46
  - **Shakapacker 9.0.0 Upgrade**: Upgraded Shakapacker from 8.2.0 to 9.0.0 with Babel transpiler configuration for compatibility. Key changes include:
@@ -63,6 +55,10 @@ Changes since the last non-beta release.
63
55
 
64
56
  - **`generated_component_packs_loading_strategy` now defaults based on Pro license**: When using Shakapacker >= 8.2.0, the default loading strategy is now `:async` for Pro users and `:defer` for non-Pro users. This provides optimal performance for Pro users while maintaining compatibility for non-Pro users. You can still explicitly set the strategy in your configuration. [PR #1993](https://github.com/shakacode/react_on_rails/pull/1993) by [AbanoubGhadban](https://github.com/AbanoubGhadban).
65
57
 
58
+ - **Generator Configuration Modernization**: Updated the generator to enable recommended configurations by default for new applications. `config.build_test_command` is now uncommented and set to `"RAILS_ENV=test bin/shakapacker"` by default, enabling automatic asset building during tests for better integration test reliability. `config.auto_load_bundle = true` is now set by default, enabling automatic loading of component bundles. `config.components_subdirectory = "ror_components"` is now set by default, organizing React components in a dedicated subdirectory. **Note:** These changes only affect newly generated applications. Existing applications are unaffected and do not need to make any changes. If you want to adopt these settings in an existing app, you can manually add them to your `config/initializers/react_on_rails.rb` file. [PR 2039](https://github.com/shakacode/react_on_rails/pull/2039) by [justin808](https://github.com/justin808).
59
+
60
+ - **Removed Babel Dependency Installation**: The generator no longer installs `@babel/preset-react` or `@babel/preset-typescript` packages. Shakapacker handles JavaScript transpiler configuration (Babel, SWC, or esbuild) via the `javascript_transpiler` setting in `shakapacker.yml`. SWC is now the default transpiler and includes built-in support for React and TypeScript. Users who explicitly choose Babel will need to manually install and configure the required presets. This change reduces unnecessary dependencies and aligns with Shakapacker's modular transpiler approach. [PR 2051](https://github.com/shakacode/react_on_rails/pull/2051) by [justin808](https://github.com/justin808).
61
+
66
62
  #### Documentation
67
63
 
68
64
  - **Simplified Configuration Files**: Improved configuration documentation and generator template for better clarity and usability. Reduced generator template from 67 to 42 lines (37% reduction). Added comprehensive testing configuration guide. Reorganized configuration docs into Essential vs Advanced sections. Enhanced Doctor program with diagnostics for server rendering and test compilation consistency. [PR #2011](https://github.com/shakacode/react_on_rails/pull/2011) by [justin808](https://github.com/justin808).
@@ -73,10 +69,20 @@ Changes since the last non-beta release.
73
69
 
74
70
  #### Fixed
75
71
 
72
+ - **Duplicate Rake Task Execution**: Fixed rake tasks executing twice during asset precompilation and other rake operations. Rails Engine was loading task files twice: once via explicit `load` calls in the `rake_tasks` block (Railtie layer) and once via automatic file loading from `lib/tasks/` (Engine layer). This caused `react_on_rails:assets:webpack`, `react_on_rails:generate_packs`, and `react_on_rails:locale` tasks to run twice, significantly increasing build times. Removed explicit `load` calls and now rely on Rails Engine's standard auto-loading behavior. [PR 2052](https://github.com/shakacode/react_on_rails/pull/2052) by [justin808](https://github.com/justin808).
73
+
76
74
  - **Node Renderer Worker Restart**: Fixed "descriptor closed" error that occurred when the node renderer restarts while handling an in-progress request (especially streaming requests). Workers now perform graceful shutdowns: they disconnect from the cluster to stop receiving new requests, wait for active requests to complete, then shut down cleanly. A configurable `gracefulWorkerRestartTimeout` ensures workers are forcibly killed if they don't shut down in time. [PR 1970](https://github.com/shakacode/react_on_rails/pull/1970) by [AbanoubGhadban](https://github.com/AbanoubGhadban).
77
75
 
78
76
  - **Body Duplication Bug On Streaming**: Fixed a bug that happens while streaming if the node renderer connection closed after streaming some chunks to the client. [PR #1995](https://github.com/shakacode/react_on_rails/pull/1995) by [AbanoubGhadban](https://github.com/AbanoubGhadban).
79
77
 
78
+ - **bin/dev --verbose Option**: Fixed `OptionParser::InvalidOption` error that occurred when using the `--verbose`/`-v` flag. The flag was documented in help text but not functional. Now properly implemented with flag parsing and passing verbose option through method calls. [PR 2023](https://github.com/shakacode/react_on_rails/pull/2023) by [justin808](https://github.com/justin808).
79
+
80
+ - **Shakapacker Template Configuration**: Fixed shakapacker.yml template to prevent unnecessary "Slow setup for development" warnings by setting `compile: false` as the default configuration. This allows development environments to inherit the setting instead of forcing on-demand compilation when using Procfiles with `bin/dev` that already run the shakapacker dev server or watch mode. [PR 2021](https://github.com/shakacode/react_on_rails/pull/2021) by [justin808](https://github.com/justin808).
81
+
82
+ #### Improved
83
+
84
+ - **Concurrent Streaming Performance**: Implemented concurrent draining of streamed React components using the async gem. Instead of processing components sequentially, the system now uses a producer-consumer pattern with bounded buffering to allow multiple components to stream simultaneously while maintaining per-component chunk ordering. [PR 2015](https://github.com/shakacode/react_on_rails/pull/2015) by [ihabadham](https://github.com/ihabadham).
85
+
80
86
  #### Breaking Changes
81
87
 
82
88
  - **`config.immediate_hydration` configuration removed**: The `config.immediate_hydration` setting in `config/initializers/react_on_rails.rb` has been removed. Immediate hydration is now automatically enabled for React on Rails Pro users and automatically disabled for non-Pro users.
@@ -1837,8 +1843,8 @@ such as:
1837
1843
 
1838
1844
  - Fix several generator-related issues.
1839
1845
 
1840
- [unreleased]: https://github.com/shakacode/react_on_rails/compare/16.2.0.beta.4...master
1841
- [16.2.0.beta.4]: https://github.com/shakacode/react_on_rails/compare/16.1.1...16.2.0.beta.4
1846
+ [unreleased]: https://github.com/shakacode/react_on_rails/compare/v16.2.0.beta.10...master
1847
+ [v16.2.0.beta.10]: https://github.com/shakacode/react_on_rails/compare/16.1.1...v16.2.0.beta.10
1842
1848
  [16.1.1]: https://github.com/shakacode/react_on_rails/compare/16.1.0...16.1.1
1843
1849
  [16.1.0]: https://github.com/shakacode/react_on_rails/compare/16.0.0...16.1.0
1844
1850
  [16.0.0]: https://github.com/shakacode/react_on_rails/compare/14.2.0...16.0.0
data/CLAUDE.md CHANGED
@@ -2,6 +2,22 @@
2
2
 
3
3
  This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
4
4
 
5
+ ## Project Structure Guidelines
6
+
7
+ ### Analysis Documents
8
+
9
+ When creating analysis documents (deep dives, investigations, historical context):
10
+ - **Location**: Place in `/analysis` directory
11
+ - **Format**: Use Markdown (.md)
12
+ - **Naming**: Use descriptive kebab-case names (e.g., `rake-task-duplicate-analysis.md`)
13
+ - **Purpose**: Keep detailed analyses separate from top-level project files
14
+
15
+ Examples:
16
+ - `/analysis/rake-task-duplicate-analysis.md` - Historical analysis of duplicate rake task bug
17
+ - `/analysis/feature-investigation.md` - Investigation of a specific feature or issue
18
+
19
+ Top-level documentation (like README.md, CONTRIBUTING.md) should remain at the root.
20
+
5
21
  ## ⚠️ CRITICAL REQUIREMENTS
6
22
 
7
23
  **BEFORE EVERY COMMIT/PUSH:**
@@ -178,11 +194,24 @@ cd react_on_rails_pro && bundle exec rake rbs:validate
178
194
  ```
179
195
  ## Changelog
180
196
 
197
+ **IMPORTANT: This is a monorepo with TWO separate changelogs:**
198
+ - **Open Source**: `/CHANGELOG.md` - for react_on_rails gem and npm package
199
+ - **Pro**: `/react_on_rails_pro/CHANGELOG.md` - for react_on_rails_pro gem and npm packages
200
+
201
+ When making changes, update the **appropriate changelog(s)**:
202
+ - Open-source features/fixes → Update `/CHANGELOG.md`
203
+ - Pro-only features/fixes → Update `/react_on_rails_pro/CHANGELOG.md`
204
+ - Changes affecting both → Update **BOTH** changelogs
205
+
206
+ ### Changelog Guidelines
207
+
181
208
  - **Update CHANGELOG.md for user-visible changes only** (features, bug fixes, breaking changes, deprecations, performance improvements)
182
209
  - **Do NOT add entries for**: linting, formatting, refactoring, tests, or documentation fixes
183
210
  - **Format**: `[PR 1818](https://github.com/shakacode/react_on_rails/pull/1818) by [username](https://github.com/username)` (no hash in PR number)
184
211
  - **Use `/update-changelog` command** for guided changelog updates with automatic formatting
185
- - **Version management**: Run `bundle exec rake update_changelog` after releases to update version headers
212
+ - **Version management after releases**:
213
+ - Open source: `bundle exec rake update_changelog`
214
+ - Pro: `cd react_on_rails_pro && bundle exec rake update_changelog`
186
215
  - **Examples**: Run `grep -A 3 "^#### " CHANGELOG.md | head -30` to see real formatting examples
187
216
 
188
217
  ## ⚠️ FORMATTING RULES
@@ -198,12 +227,25 @@ cd react_on_rails_pro && bundle exec rake rbs:validate
198
227
  **CRITICAL**: When resolving merge conflicts, follow this exact sequence:
199
228
 
200
229
  1. **Resolve logical conflicts only** - don't worry about formatting
201
- 2. **Add resolved files**: `git add .` (or specific files)
202
- 3. **Auto-fix everything**: `rake autofix`
203
- 4. **Add any formatting changes**: `git add .`
204
- 5. **Continue rebase/merge**: `git rebase --continue` or `git commit`
230
+ 2. **VERIFY FILE PATHS** - if the conflict involved directory structure:
231
+ - Check if any hardcoded paths need updating
232
+ - Run: `grep -r "old/path" . --exclude-dir=node_modules`
233
+ - Pay special attention to package-scripts.yml, webpack configs, package.json
234
+ - **Test affected scripts:** If package-scripts.yml changed, run `yarn run prepack`
235
+ 3. **Add resolved files**: `git add .` (or specific files)
236
+ 4. **Auto-fix everything**: `rake autofix`
237
+ 5. **Add any formatting changes**: `git add .`
238
+ 6. **Continue rebase/merge**: `git rebase --continue` or `git commit`
239
+ 7. **TEST CRITICAL SCRIPTS if build configs changed:**
240
+ ```bash
241
+ yarn run prepack # Test prepack script
242
+ yarn run yalc.publish # Test yalc publish if package structure changed
243
+ rake run_rspec:gem # Run relevant test suites
244
+ ```
205
245
 
206
246
  **❌ NEVER manually format during conflict resolution** - this causes formatting wars between tools.
247
+ **❌ NEVER blindly accept path changes** - verify they're correct for current structure.
248
+ **❌ NEVER skip testing after resolving conflicts in build configs** - silent failures are dangerous.
207
249
 
208
250
  ### Debugging Formatting Issues
209
251
  - Check current formatting: `yarn start format.listDifferent`
@@ -223,6 +265,18 @@ cd react_on_rails_pro && bundle exec rake rbs:validate
223
265
  - **Gem-only tests**: `rake run_rspec:gem`
224
266
  - **All tests except examples**: `rake all_but_examples`
225
267
 
268
+ ## Testing Build and Package Scripts
269
+
270
+ @.claude/docs/testing-build-scripts.md
271
+
272
+ ## Master Branch Health Monitoring
273
+
274
+ @.claude/docs/master-health-monitoring.md
275
+
276
+ ## Managing File Paths in Configuration Files
277
+
278
+ @.claude/docs/managing-file-paths.md
279
+
226
280
  ## Project Architecture
227
281
 
228
282
  ### Monorepo Structure
@@ -504,6 +558,83 @@ Playwright E2E tests run automatically in CI via GitHub Actions (`.github/workfl
504
558
  - Uploads HTML reports as artifacts (available for 30 days)
505
559
  - Auto-starts Rails server before running tests
506
560
 
561
+ ## Rails Engine Development Nuances
562
+
563
+ React on Rails is a **Rails Engine**, which has important implications for development:
564
+
565
+ ### Automatic Rake Task Loading
566
+
567
+ **CRITICAL**: Rails::Engine automatically loads all `.rake` files from `lib/tasks/` directory. **DO NOT** use a `rake_tasks` block to explicitly load them, as this causes duplicate task execution.
568
+
569
+ ```ruby
570
+ # ❌ WRONG - Causes duplicate execution
571
+ module ReactOnRails
572
+ class Engine < ::Rails::Engine
573
+ rake_tasks do
574
+ load File.expand_path("../tasks/generate_packs.rake", __dir__)
575
+ load File.expand_path("../tasks/assets.rake", __dir__)
576
+ load File.expand_path("../tasks/locale.rake", __dir__)
577
+ end
578
+ end
579
+ end
580
+
581
+ # ✅ CORRECT - Rails::Engine loads lib/tasks/*.rake automatically
582
+ module ReactOnRails
583
+ class Engine < ::Rails::Engine
584
+ # Rake tasks are automatically loaded from lib/tasks/*.rake by Rails::Engine
585
+ # No explicit loading needed
586
+ end
587
+ end
588
+ ```
589
+
590
+ **When to use `rake_tasks` block:**
591
+ - Tasks are in a **non-standard location** (not `lib/tasks/`)
592
+ - You need to **programmatically generate** tasks
593
+ - You need to **pass context** to the tasks
594
+
595
+ **Historical Context**: PR #1770 added explicit rake task loading, causing webpack builds and pack generation to run twice during `rake assets:precompile`. This was fixed in PR #2052. See `analysis/rake-task-duplicate-analysis.md` for full details.
596
+
597
+ ### Engine Initializers and Hooks
598
+
599
+ Engines have specific initialization hooks that run at different times:
600
+
601
+ ```ruby
602
+ module ReactOnRails
603
+ class Engine < ::Rails::Engine
604
+ # Runs after Rails initializes but before routes are loaded
605
+ config.to_prepare do
606
+ ReactOnRails::ServerRenderingPool.reset_pool
607
+ end
608
+
609
+ # Runs during Rails initialization, use for validations
610
+ initializer "react_on_rails.validate_version" do
611
+ config.after_initialize do
612
+ # Validation logic here
613
+ end
614
+ end
615
+ end
616
+ end
617
+ ```
618
+
619
+ ### Engine vs Application Code
620
+
621
+ - **Engine code** (`lib/react_on_rails/`): Runs in the gem context, has limited access to host application
622
+ - **Host application code**: The Rails app that includes the gem
623
+ - **Generators** (`lib/generators/react_on_rails/`): Run in host app context during setup
624
+
625
+ ### Testing Engines
626
+
627
+ - **Dummy app** (`spec/dummy/`): Full Rails app for integration testing
628
+ - **Unit tests** (`spec/react_on_rails/`): Test gem code in isolation
629
+ - Always test both contexts: gem code alone and gem + host app integration
630
+
631
+ ### Common Pitfalls
632
+
633
+ 1. **Assuming host app structure**: Don't assume `app/javascript/` exists—it might not in older apps
634
+ 2. **Path resolution**: Use `Rails.root` for host app paths, not relative paths
635
+ 3. **Autoloading**: Engine code follows Rails autoloading rules but with a different load path
636
+ 4. **Configuration**: Engine config is separate from host app config—use `ReactOnRails.configure`
637
+
507
638
  ## IDE Configuration
508
639
 
509
640
  Exclude these directories to prevent IDE slowdowns:
data/CONTRIBUTING.md CHANGED
@@ -426,12 +426,14 @@ For more details, see [`docs/CI_OPTIMIZATION.md`](./docs/CI_OPTIMIZATION.md).
426
426
 
427
427
  React on Rails provides PR comment commands to control CI behavior:
428
428
 
429
- #### `/run-skipped-ci` - Enable Full CI Mode
429
+ #### `/run-skipped-ci` (or `/run-skipped-tests`) - Enable Full CI Mode
430
430
 
431
431
  Runs all skipped CI checks and enables full CI mode for the PR:
432
432
 
433
433
  ```
434
434
  /run-skipped-ci
435
+ # or use the shorter alias:
436
+ /run-skipped-tests
435
437
  ```
436
438
 
437
439
  **What it does:**
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- react_on_rails (16.2.0.beta.8)
4
+ react_on_rails (16.2.0.beta.11)
5
5
  addressable
6
6
  connection_pool
7
7
  execjs (~> 2.5)
data/Steepfile CHANGED
@@ -28,6 +28,10 @@ target :lib do
28
28
  check "lib/react_on_rails.rb"
29
29
  check "lib/react_on_rails/configuration.rb"
30
30
  check "lib/react_on_rails/controller.rb"
31
+ check "lib/react_on_rails/dev/file_manager.rb"
32
+ check "lib/react_on_rails/dev/pack_generator.rb"
33
+ check "lib/react_on_rails/dev/process_manager.rb"
34
+ check "lib/react_on_rails/dev/server_manager.rb"
31
35
  check "lib/react_on_rails/git_utils.rb"
32
36
  check "lib/react_on_rails/helper.rb"
33
37
  check "lib/react_on_rails/packer_utils.rb"
@@ -0,0 +1,149 @@
1
+ # Analysis: Why rake_tasks Block Was Added in PR #1770 and Caused Duplicate Execution
2
+
3
+ ## Summary
4
+
5
+ In PR #1770 (commit `8f3d178` - "Generator Overhaul & Developer Experience Enhancement"), Ihab added a `rake_tasks` block to `lib/react_on_rails/engine.rb` that explicitly loaded three rake task files. This was part of a **massive generator overhaul** that introduced new rake tasks for the file-system auto-registration feature. However, this caused those tasks to execute **twice** during operations like `rake assets:precompile`, which was fixed in PR #2052.
6
+
7
+ ## The Problem: Double Loading of Rake Tasks
8
+
9
+ ### What Was Added in PR #1770 (8f3d178)
10
+
11
+ ```ruby
12
+ # lib/react_on_rails/engine.rb
13
+ module ReactOnRails
14
+ class Engine < ::Rails::Engine
15
+ # ... existing code ...
16
+
17
+ rake_tasks do
18
+ load File.expand_path("../tasks/generate_packs.rake", __dir__)
19
+ load File.expand_path("../tasks/assets.rake", __dir__)
20
+ load File.expand_path("../tasks/locale.rake", __dir__)
21
+ end
22
+ end
23
+ end
24
+ ```
25
+
26
+ ### Why This Caused Duplicate Execution
27
+
28
+ Rails Engines have **two different mechanisms** for loading rake tasks, and this code inadvertently activated both:
29
+
30
+ 1. **Automatic Loading (Engine Layer)**: Rails::Engine automatically loads all `.rake` files from `lib/tasks/` directory
31
+ 2. **Manual Loading (Railtie Layer)**: The `rake_tasks` block explicitly loads specific files
32
+
33
+ Because the task files existed in `lib/tasks/`:
34
+
35
+ - `lib/tasks/assets.rake`
36
+ - `lib/tasks/generate_packs.rake`
37
+ - `lib/tasks/locale.rake`
38
+
39
+ They were being loaded **twice**:
40
+
41
+ - Once automatically by Rails::Engine from the `lib/tasks/` directory
42
+ - Once explicitly by the `rake_tasks` block
43
+
44
+ ## Why Was This Added in PR #1770?
45
+
46
+ PR #1770 was a **major overhaul** with 97 files changed. Looking at the context:
47
+
48
+ ### The Generator Overhaul Introduced:
49
+
50
+ 1. **File-system auto-registration**: New feature where components auto-register under `app/javascript/src/.../ror_components`
51
+ 2. **New `react_on_rails:generate_packs` rake task**: Critical new task for the auto-registration system
52
+ 3. **Enhanced dev tooling**: New `ReactOnRails::Dev` namespace with ServerManager, ProcessManager, PackGenerator
53
+ 4. **Shakapacker as required dependency**: Made Shakapacker mandatory (removed Webpacker support)
54
+
55
+ ### Why the Explicit Loading Was Added:
56
+
57
+ Based on the PR context and commit message, the most likely reasons:
58
+
59
+ 1. **Ensuring Critical Task Availability**: The `react_on_rails:generate_packs` task was brand new and absolutely critical to the file-system auto-registration feature. Ihab may have wanted to guarantee it would be loaded in all contexts.
60
+
61
+ 2. **Following Common Rails Engine Patterns**: The `rake_tasks` block is a well-documented pattern in Rails engines. Many gems use it explicitly, even when files are in `lib/tasks/`. Ihab likely followed this pattern as "best practice."
62
+
63
+ 3. **Massive PR Complexity**: With 97 files changed, this was a huge refactor. The `rake_tasks` block addition was a tiny part of the overall changes, and the duplicate loading issue was subtle enough that it wasn't caught during review.
64
+
65
+ 4. **Lack of Awareness About Automatic Loading**: Rails::Engine's automatic loading of `lib/tasks/*.rake` files is not as well-known as it should be. Many developers (even experienced ones) don't realize this happens automatically.
66
+
67
+ 5. **"Belt and Suspenders" Approach**: Given the criticality of the new auto-registration feature, Ihab may have intentionally added explicit loading as a safety measure, not realizing it would cause duplication.
68
+
69
+ **The commit message doesn't mention the rake_tasks addition at all**—it focuses on generator improvements, dev experience, and component architecture. This suggests the `rake_tasks` block was added as a routine implementation detail, not something Ihab thought needed explanation.
70
+
71
+ ## The Impact
72
+
73
+ Tasks affected by duplicate execution:
74
+
75
+ - `react_on_rails:assets:webpack` - Webpack builds ran twice
76
+ - `react_on_rails:generate_packs` - Pack generation ran twice
77
+ - `react_on_rails:locale` - Locale file generation ran twice
78
+
79
+ This meant:
80
+
81
+ - **2x build times** during asset precompilation
82
+ - **Slower CI** builds
83
+ - **Confusing console output** showing duplicate webpack compilation messages
84
+ - **Wasted resources** running the same expensive operations twice
85
+
86
+ ## The Fix (PR #2052)
87
+
88
+ The fix was simple—remove the redundant `rake_tasks` block and rely solely on Rails' automatic loading:
89
+
90
+ ```ruby
91
+ # lib/react_on_rails/engine.rb
92
+ module ReactOnRails
93
+ class Engine < ::Rails::Engine
94
+ # ... existing code ...
95
+
96
+ # Rake tasks are automatically loaded from lib/tasks/*.rake by Rails::Engine
97
+ # No need to explicitly load them here to avoid duplicate loading
98
+ end
99
+ end
100
+ ```
101
+
102
+ ## Key Lesson
103
+
104
+ **Rails::Engine Best Practice**: If your rake task files are in `lib/tasks/`, you don't need a `rake_tasks` block. Rails will load them automatically. Only use `rake_tasks do` if:
105
+
106
+ - Tasks are in a non-standard location
107
+ - You need to programmatically generate tasks
108
+ - You need to pass context to the tasks
109
+
110
+ ## Timeline
111
+
112
+ - **Sep 16, 2025** (PR #1770, commit 8f3d178): Ihab adds `rake_tasks` block as part of massive Generator Overhaul (97 files changed)
113
+ - **Nov 18, 2025** (PR #2052, commit 3f6df6be9): Justin discovers and fixes duplicate execution issue by removing the block (~2 months later)
114
+
115
+ ## What We Learned
116
+
117
+ ### For Code Reviews
118
+
119
+ This incident highlights the challenge of reviewing massive PRs:
120
+
121
+ - **97 files changed** made it nearly impossible to catch subtle issues
122
+ - The `rake_tasks` addition was 6 lines in a file that wasn't the focus of the PR
123
+ - The duplicate loading bug only manifested during asset precompilation, not during normal development
124
+ - Smaller, focused PRs would have made this easier to catch
125
+
126
+ ### For Testing
127
+
128
+ The duplicate execution bug was subtle:
129
+
130
+ - **Didn't cause failures**—just slower builds (2x time)
131
+ - **Hard to notice locally**—developers might not realize builds were taking twice as long
132
+ - **Only obvious in CI**—where build times are closely monitored
133
+ - **Needed production-like scenarios**—requires running `rake assets:precompile` to trigger
134
+
135
+ ### For Documentation
136
+
137
+ Better documentation of Rails::Engine automatic loading would help:
138
+
139
+ - Many Rails guides show `rake_tasks` blocks without mentioning automatic loading
140
+ - The Rails Engine guide doesn't clearly state when NOT to use `rake_tasks`
141
+ - This leads to cargo-culting of the pattern
142
+
143
+ ## References
144
+
145
+ - **Original PR**: [#1770 - "React on Rails Generator Overhaul & Developer Experience Enhancement"](https://github.com/shakacode/react_on_rails/pull/1770)
146
+ - **Original commit**: `8f3d178` - 97 files changed, massive refactor
147
+ - **Fix PR**: [#2052 - "Fix duplicate rake task execution by removing explicit task loading"](https://github.com/shakacode/react_on_rails/pull/2052)
148
+ - **Fix commit**: `3f6df6be9` - Simple 6-line removal
149
+ - **Rails Engine documentation**: https://guides.rubyonrails.org/engines.html#rake-tasks
@@ -139,10 +139,12 @@ echo ""
139
139
 
140
140
  # Determine the working directory (check if we need to be in spec/dummy)
141
141
  WORKING_DIR="."
142
- if [ ${#UNIQUE_SPECS[@]} -gt 0 ] && ([[ "${UNIQUE_SPECS[0]}" == *"spec/system"* ]] || [[ "${UNIQUE_SPECS[0]}" == *"spec/helpers"* ]]); then
143
- if [ -d "spec/dummy" ]; then
144
- WORKING_DIR="spec/dummy"
145
- echo -e "${BLUE}Running from spec/dummy directory${NC}"
142
+ if [ ${#UNIQUE_SPECS[@]} -gt 0 ]; then
143
+ if [[ "${UNIQUE_SPECS[0]}" == *"spec/system"* ]] || [[ "${UNIQUE_SPECS[0]}" == *"spec/helpers"* ]]; then
144
+ if [ -d "spec/dummy" ]; then
145
+ WORKING_DIR="spec/dummy"
146
+ echo -e "${BLUE}Running from spec/dummy directory${NC}"
147
+ fi
146
148
  fi
147
149
  fi
148
150
 
data/bin/ci-switch-config CHANGED
@@ -255,9 +255,10 @@ EOF
255
255
  set_node_version "20.18.1" "$VERSION_MANAGER"
256
256
 
257
257
  # Run conversion script
258
- # NOTE: This uses whatever 'ruby' is in PATH after version manager updates above.
259
- # The version manager may not have reloaded yet, so ensure your current Ruby is
260
- # compatible with script/convert (Ruby 2.6+ should work).
258
+ # NOTE: This executes 'ruby' before the version manager reloads in your current shell.
259
+ # The script/convert file requires Ruby 2.6+ and uses basic file I/O operations.
260
+ # Most modern Ruby versions (2.6+) are compatible. The version manager changes above
261
+ # only take effect after shell reload, so this uses your current Ruby installation.
261
262
  print_header "Running script/convert to downgrade dependencies"
262
263
  cd "$PROJECT_ROOT"
263
264
  ruby script/convert
data/knip.ts CHANGED
@@ -162,7 +162,7 @@ const config: KnipConfig = {
162
162
  'url-loader',
163
163
  // Transitive dependency of shakapacker but listed as direct dependency
164
164
  'webpack-merge',
165
- // Dependencies not detected in production mode
165
+ // Dependencies not detected in production mode (runtime injected or dynamic imports)
166
166
  '@babel/runtime',
167
167
  'mini-css-extract-plugin',
168
168
  'css-loader',
@@ -4,10 +4,12 @@ require "rails/generators"
4
4
  require "fileutils"
5
5
  require_relative "generator_messages"
6
6
  require_relative "generator_helper"
7
+ require_relative "js_dependency_manager"
7
8
  module ReactOnRails
8
9
  module Generators
9
10
  class BaseGenerator < Rails::Generators::Base
10
11
  include GeneratorHelper
12
+ include JsDependencyManager
11
13
 
12
14
  Rails::Generators.hide_namespace(namespace)
13
15
  source_root(File.expand_path("templates", __dir__))
@@ -99,7 +101,8 @@ module ReactOnRails
99
101
  puts "Adding Shakapacker #{ReactOnRails::PackerUtils.shakapacker_version} config"
100
102
  base_path = "base/base/"
101
103
  config = "config/shakapacker.yml"
102
- copy_file("#{base_path}#{config}", config)
104
+ # Use template to enable version-aware configuration
105
+ template("#{base_path}#{config}.tt", config)
103
106
  configure_rspack_in_shakapacker if options.rspack?
104
107
  end
105
108
 
@@ -107,7 +110,7 @@ module ReactOnRails
107
110
  run "bundle"
108
111
  end
109
112
 
110
- def update_gitignore_for_generated_bundles
113
+ def update_gitignore_for_auto_registration
111
114
  gitignore_path = File.join(destination_root, ".gitignore")
112
115
  return unless File.exist?(gitignore_path)
113
116
 
@@ -146,123 +149,6 @@ module ReactOnRails
146
149
 
147
150
  private
148
151
 
149
- def setup_js_dependencies
150
- add_js_dependencies
151
- install_js_dependencies
152
- end
153
-
154
- def add_js_dependencies
155
- add_react_on_rails_package
156
- add_react_dependencies
157
- add_css_dependencies
158
- add_dev_dependencies
159
- end
160
-
161
- def add_react_on_rails_package
162
- major_minor_patch_only = /\A\d+\.\d+\.\d+\z/
163
-
164
- # Try to use package_json gem first, fall back to direct npm commands
165
- react_on_rails_pkg = if ReactOnRails::VERSION.match?(major_minor_patch_only)
166
- ["react-on-rails@#{ReactOnRails::VERSION}"]
167
- else
168
- puts "Adding the latest react-on-rails NPM module. " \
169
- "Double check this is correct in package.json"
170
- ["react-on-rails"]
171
- end
172
-
173
- puts "Installing React on Rails package..."
174
- return if add_npm_dependencies(react_on_rails_pkg)
175
-
176
- puts "Using direct npm commands as fallback"
177
- success = system("npm", "install", *react_on_rails_pkg)
178
- handle_npm_failure("react-on-rails package", react_on_rails_pkg) unless success
179
- end
180
-
181
- def add_react_dependencies
182
- puts "Installing React dependencies..."
183
- react_deps = %w[
184
- react
185
- react-dom
186
- @babel/preset-react
187
- prop-types
188
- babel-plugin-transform-react-remove-prop-types
189
- babel-plugin-macros
190
- ]
191
- return if add_npm_dependencies(react_deps)
192
-
193
- success = system("npm", "install", *react_deps)
194
- handle_npm_failure("React dependencies", react_deps) unless success
195
- end
196
-
197
- def add_css_dependencies
198
- puts "Installing CSS handling dependencies..."
199
- css_deps = %w[
200
- css-loader
201
- css-minimizer-webpack-plugin
202
- mini-css-extract-plugin
203
- style-loader
204
- ]
205
- return if add_npm_dependencies(css_deps)
206
-
207
- success = system("npm", "install", *css_deps)
208
- handle_npm_failure("CSS dependencies", css_deps) unless success
209
- end
210
-
211
- def add_dev_dependencies
212
- puts "Installing development dependencies..."
213
- dev_deps = %w[
214
- @pmmmwh/react-refresh-webpack-plugin
215
- react-refresh
216
- ]
217
- return if add_npm_dependencies(dev_deps, dev: true)
218
-
219
- success = system("npm", "install", "--save-dev", *dev_deps)
220
- handle_npm_failure("development dependencies", dev_deps, dev: true) unless success
221
- end
222
-
223
- def install_js_dependencies
224
- # Detect which package manager to use
225
- success = if File.exist?(File.join(destination_root, "yarn.lock"))
226
- system("yarn", "install")
227
- elsif File.exist?(File.join(destination_root, "pnpm-lock.yaml"))
228
- system("pnpm", "install")
229
- elsif File.exist?(File.join(destination_root, "package-lock.json")) ||
230
- File.exist?(File.join(destination_root, "package.json"))
231
- # Use npm for package-lock.json or as default fallback
232
- system("npm", "install")
233
- else
234
- true # No package manager detected, skip
235
- end
236
-
237
- unless success
238
- GeneratorMessages.add_warning(<<~MSG.strip)
239
- ⚠️ JavaScript dependencies installation failed.
240
-
241
- This could be due to network issues or missing package manager.
242
- You can install dependencies manually later by running:
243
- • npm install (if using npm)
244
- • yarn install (if using yarn)
245
- • pnpm install (if using pnpm)
246
- MSG
247
- end
248
-
249
- success
250
- end
251
-
252
- def handle_npm_failure(dependency_type, packages, dev: false)
253
- install_command = dev ? "npm install --save-dev" : "npm install"
254
- GeneratorMessages.add_warning(<<~MSG.strip)
255
- ⚠️ Failed to install #{dependency_type}.
256
-
257
- The following packages could not be installed automatically:
258
- #{packages.map { |pkg| " • #{pkg}" }.join("\n")}
259
-
260
- This could be due to network issues or missing package manager.
261
- You can install them manually later by running:
262
- #{install_command} #{packages.join(' ')}
263
- MSG
264
- end
265
-
266
152
  def copy_webpack_main_config(base_path, config)
267
153
  webpack_config_path = "config/webpack/webpack.config.js"
268
154