react_on_rails 16.2.0.beta.11 → 16.2.0.beta.12
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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +16 -20
- data/CLAUDE.md +44 -0
- data/Gemfile.lock +1 -1
- data/analysis/v8-crash-retry-solution.md +148 -0
- data/lib/react_on_rails/configuration.rb +12 -7
- data/lib/react_on_rails/dev/pack_generator.rb +48 -11
- data/lib/react_on_rails/helper.rb +32 -5
- data/lib/react_on_rails/packs_generator.rb +47 -35
- data/lib/react_on_rails/version.rb +1 -1
- data/lib/tasks/generate_packs.rake +12 -6
- data/react_on_rails_pro/CHANGELOG.md +11 -4
- data/react_on_rails_pro/Gemfile.lock +3 -3
- data/react_on_rails_pro/app/helpers/react_on_rails_pro_helper.rb +2 -1
- data/react_on_rails_pro/lib/react_on_rails_pro/version.rb +1 -1
- data/react_on_rails_pro/package.json +1 -1
- data/react_on_rails_pro/spec/dummy/Gemfile.lock +3 -3
- data/react_on_rails_pro/spec/dummy/spec/helpers/react_on_rails_pro_helper_spec.rb +15 -7
- data/react_on_rails_pro/spec/dummy/spec/requests/renderer_console_logging_spec.rb +9 -4
- data/sig/react_on_rails/dev/pack_generator.rbs +2 -2
- metadata +3 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 1f1f3592776a1d9cc69f832cdd3df34f4f1b696be6dd681d341614830d97a3d3
|
|
4
|
+
data.tar.gz: d7563d35d02ccd555be46984c21be8bf2cc9b9998d0ad70d0aefd4588940cda5
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: b7284eb5e6ef0133546d405f441ae7c0f2099108353d7f6de9d9eff889c0d9d411c2952b37d98c905fb31dd4dc88cb7b3b8ef1dab5d12f6b5856c0172973d0b1
|
|
7
|
+
data.tar.gz: df238dcf4c71cd74547ff523c52974d752aa1bb53d53de50ec3d9c7227b3d55c4d96102c508561991dc41bba3aa3077eb5b3d81f1c08c0e53fa7ebff2ea1430c
|
data/CHANGELOG.md
CHANGED
|
@@ -23,10 +23,20 @@ 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
|
-
|
|
26
|
+
#### Added
|
|
27
|
+
|
|
28
|
+
- **CSP Nonce Support for Console Replay**: Added Content Security Policy (CSP) nonce support for the `consoleReplay` script generated during server-side rendering. When Rails CSP is configured, the console replay script will automatically include the nonce attribute, allowing it to execute under restrictive CSP policies like `script-src: 'self'`. The implementation includes cross-version Rails compatibility (5.2-7.x) and defense-in-depth nonce sanitization to prevent attribute injection attacks. [PR 2059](https://github.com/shakacode/react_on_rails/pull/2059) by [justin808](https://github.com/justin808).
|
|
29
|
+
|
|
30
|
+
#### Bug Fixes
|
|
31
|
+
|
|
32
|
+
- [PR 2085](https://github.com/shakacode/react_on_rails/pull/2085) by [justin808](https://github.com/justin808): Fix pack generation in bin/dev when running from Bundler context. Pack generation was failing with "Could not find command 'react_on_rails:generate_packs'" because Bundler was intercepting the subprocess call. The fix wraps the bundle exec call with `Bundler.with_unbundled_env` to prevent interception.
|
|
33
|
+
|
|
34
|
+
### [v16.2.0.beta.11] - 2025-11-19
|
|
27
35
|
|
|
28
36
|
#### Added
|
|
29
37
|
|
|
38
|
+
- **Shakapacker 9.0+ Private Output Path Integration**: Added automatic detection and integration of Shakapacker 9.0+ `private_output_path` configuration. React on Rails now automatically reads `private_output_path` from `shakapacker.yml` and sets server bundle paths, eliminating manual configuration synchronization. Includes version-aware generator templates, enhanced doctor command diagnostics for configuration validation and upgrade recommendations, and improved security with `enforce_private_server_bundles` option. [PR 2028](https://github.com/shakacode/react_on_rails/pull/2028) by [justin808](https://github.com/justin808).
|
|
39
|
+
|
|
30
40
|
- **Rspack Support**: Added `--rspack` flag to `react_on_rails:install` generator for significantly faster builds (~20x improvement with SWC). Includes unified webpack/rspack configuration templates and `bin/switch-bundler` utility to switch between bundlers post-installation. [PR #1852](https://github.com/shakacode/react_on_rails/pull/1852) by [justin808](https://github.com/justin808).
|
|
31
41
|
|
|
32
42
|
- **Attribution Comment**: Added HTML comment attribution to Rails views containing React on Rails functionality. The comment automatically displays which version is in use (open source React on Rails or React on Rails Pro) and, for Pro users, shows the license status. This helps identify React on Rails usage across your application. [PR #1857](https://github.com/shakacode/react_on_rails/pull/1857) by [AbanoubGhadban](https://github.com/AbanoubGhadban).
|
|
@@ -37,8 +47,6 @@ Changes since the last non-beta release.
|
|
|
37
47
|
|
|
38
48
|
- **Improved RSC Payload Error Handling**: Errors that happen during generation of RSC payload are transferred properly to rails side and logs the error message and stack. [PR #1888](https://github.com/shakacode/react_on_rails/pull/1888) by [AbanoubGhadban](https://github.com/AbanoubGhadban).
|
|
39
49
|
|
|
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).
|
|
41
|
-
|
|
42
50
|
- **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
51
|
|
|
44
52
|
#### Changed
|
|
@@ -63,25 +71,13 @@ Changes since the last non-beta release.
|
|
|
63
71
|
|
|
64
72
|
- **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).
|
|
65
73
|
|
|
66
|
-
#### Deprecated
|
|
67
|
-
|
|
68
|
-
- **Node Renderer Configuration**: Renamed `bundlePath` configuration option to `serverBundleCachePath` in the node renderer to better describe its purpose and avoid confusion with Shakapacker's public bundle path. The old `bundlePath` option continues to work with deprecation warnings. Both `RENDERER_SERVER_BUNDLE_CACHE_PATH` (new) and `RENDERER_BUNDLE_PATH` (deprecated) environment variables are supported. [PR #2008](https://github.com/shakacode/react_on_rails/pull/2008) by [justin808](https://github.com/justin808).
|
|
69
|
-
|
|
70
74
|
#### Fixed
|
|
71
75
|
|
|
72
|
-
- **
|
|
73
|
-
|
|
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).
|
|
75
|
-
|
|
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).
|
|
76
|
+
- **Doctor Command Version Mismatch Detection**: Fixed false version mismatch warnings in `rake react_on_rails:doctor` when using beta/prerelease versions. The command now correctly recognizes that gem version `16.2.0.beta.10` matches npm version `16.2.0-beta.10` using proper semver conversion instead of string normalization that stripped prerelease identifiers. [PR 2064](https://github.com/shakacode/react_on_rails/pull/2064) by [ihabadham](https://github.com/ihabadham).
|
|
77
77
|
|
|
78
|
-
- **
|
|
78
|
+
- **Rails 5.2-6.0 Compatibility**: Fixed compatibility with Rails 5.2-6.0 by adding polyfill for `compact_blank` method (introduced in Rails 6.1). Also refactored webpack asset handling to conditionally include React on Rails Pro files only when available, preventing NoMethodErrors in open-source installations. [PR 2058](https://github.com/shakacode/react_on_rails/pull/2058) by [justin808](https://github.com/justin808).
|
|
79
79
|
|
|
80
|
-
- **
|
|
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).
|
|
80
|
+
- **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).
|
|
85
81
|
|
|
86
82
|
#### Breaking Changes
|
|
87
83
|
|
|
@@ -1843,8 +1839,8 @@ such as:
|
|
|
1843
1839
|
|
|
1844
1840
|
- Fix several generator-related issues.
|
|
1845
1841
|
|
|
1846
|
-
[unreleased]: https://github.com/shakacode/react_on_rails/compare/v16.2.0.beta.
|
|
1847
|
-
[v16.2.0.beta.
|
|
1842
|
+
[unreleased]: https://github.com/shakacode/react_on_rails/compare/v16.2.0.beta.11...master
|
|
1843
|
+
[v16.2.0.beta.11]: https://github.com/shakacode/react_on_rails/compare/16.1.1...v16.2.0.beta.11
|
|
1848
1844
|
[16.1.1]: https://github.com/shakacode/react_on_rails/compare/16.1.0...16.1.1
|
|
1849
1845
|
[16.1.0]: https://github.com/shakacode/react_on_rails/compare/16.0.0...16.1.0
|
|
1850
1846
|
[16.0.0]: https://github.com/shakacode/react_on_rails/compare/14.2.0...16.0.0
|
data/CLAUDE.md
CHANGED
|
@@ -214,6 +214,50 @@ When making changes, update the **appropriate changelog(s)**:
|
|
|
214
214
|
- Pro: `cd react_on_rails_pro && bundle exec rake update_changelog`
|
|
215
215
|
- **Examples**: Run `grep -A 3 "^#### " CHANGELOG.md | head -30` to see real formatting examples
|
|
216
216
|
|
|
217
|
+
### Beta Release Changelog Curation
|
|
218
|
+
|
|
219
|
+
When consolidating beta versions into a stable release, carefully curate entries to include only user-facing changes:
|
|
220
|
+
|
|
221
|
+
**Remove these types of entries:**
|
|
222
|
+
|
|
223
|
+
1. **Developer-only tooling**:
|
|
224
|
+
- yalc publish fixes (local development tool)
|
|
225
|
+
- Git dependency support (contributor workflow)
|
|
226
|
+
- CI/build script improvements
|
|
227
|
+
- Internal tooling changes
|
|
228
|
+
|
|
229
|
+
2. **Beta-specific fixes**:
|
|
230
|
+
- Bugs introduced during the beta cycle (not present in last stable)
|
|
231
|
+
- Fixes for new beta-only features (e.g., bin/dev in 16.2.0.beta)
|
|
232
|
+
- Generator handling of beta/RC version formats
|
|
233
|
+
|
|
234
|
+
3. **Pro-specific features** (move to Pro changelog):
|
|
235
|
+
- Node renderer fixes/improvements
|
|
236
|
+
- Streaming-related changes
|
|
237
|
+
- Async loading features (Pro-exclusive)
|
|
238
|
+
|
|
239
|
+
**Keep these types of entries:**
|
|
240
|
+
|
|
241
|
+
1. **User-facing fixes**:
|
|
242
|
+
- Bugs that existed in previous stable release (e.g., 16.1.x)
|
|
243
|
+
- Compatibility fixes (Rails version support, etc.)
|
|
244
|
+
- Performance improvements affecting all users
|
|
245
|
+
|
|
246
|
+
2. **Breaking changes**:
|
|
247
|
+
- API changes requiring migration
|
|
248
|
+
- Removed methods/features
|
|
249
|
+
- Configuration changes
|
|
250
|
+
|
|
251
|
+
**Investigation process:**
|
|
252
|
+
|
|
253
|
+
For each suspicious entry:
|
|
254
|
+
1. Check git history: `git log --oneline <last_stable>..<current_beta> -- <file>`
|
|
255
|
+
2. Determine when bug was introduced (stable vs beta cycle)
|
|
256
|
+
3. Verify whether fix applies to stable users or only beta users
|
|
257
|
+
4. Check PR description for context about what was broken
|
|
258
|
+
|
|
259
|
+
**Example reference:** See [PR #2072](https://github.com/shakacode/react_on_rails/pull/2072) for a complete example of beta changelog curation with detailed investigation notes.
|
|
260
|
+
|
|
217
261
|
## ⚠️ FORMATTING RULES
|
|
218
262
|
|
|
219
263
|
**Prettier is the SOLE authority for formatting non-Ruby files, and RuboCop for formatting Ruby files. NEVER manually format code.**
|
data/Gemfile.lock
CHANGED
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
# V8 Crash Retry Solution for CI
|
|
2
|
+
|
|
3
|
+
## Problem
|
|
4
|
+
|
|
5
|
+
CI jobs occasionally fail with a transient V8 bytecode deserialization crash during the Node.js setup phase. The error manifests as:
|
|
6
|
+
|
|
7
|
+
```
|
|
8
|
+
Fatal error in , line 0
|
|
9
|
+
Check failed: ReadSingleBytecodeData( source_.Get(), SlotAccessorForHandle<IsolateT>(&ret, isolate())) == 1.
|
|
10
|
+
```
|
|
11
|
+
|
|
12
|
+
This error occurs during the `yarn cache dir` command execution within the `actions/setup-node@v4` action.
|
|
13
|
+
|
|
14
|
+
## Root Cause
|
|
15
|
+
|
|
16
|
+
This is a known bug in Node.js/V8 that occurs sporadically:
|
|
17
|
+
|
|
18
|
+
- **Node.js Issue**: https://github.com/nodejs/node/issues/56010
|
|
19
|
+
- **Setup-node Issue**: https://github.com/actions/setup-node/issues/1028
|
|
20
|
+
|
|
21
|
+
The crash happens when V8 attempts to deserialize cached bytecode and encounters corrupted or incompatible data. It's a transient issue that typically resolves on retry.
|
|
22
|
+
|
|
23
|
+
## Previous Workarounds
|
|
24
|
+
|
|
25
|
+
Before this fix, the codebase used two workarounds:
|
|
26
|
+
|
|
27
|
+
1. **Completely disable yarn caching** in `examples.yml`:
|
|
28
|
+
|
|
29
|
+
```yaml
|
|
30
|
+
# TODO: Re-enable yarn caching once Node.js V8 cache crash is fixed
|
|
31
|
+
# Tracking: https://github.com/actions/setup-node/issues/1028
|
|
32
|
+
# cache: yarn
|
|
33
|
+
# cache-dependency-path: '**/yarn.lock'
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
2. **Conditionally disable caching for Node 22** in `integration-tests.yml`:
|
|
37
|
+
```yaml
|
|
38
|
+
cache: ${{ matrix.node-version != '22' && 'yarn' || '' }}
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
Both workarounds significantly slowed down CI by preventing yarn dependency caching.
|
|
42
|
+
|
|
43
|
+
## Solution
|
|
44
|
+
|
|
45
|
+
Created a custom composite GitHub action at `.github/actions/setup-node-with-retry/` that:
|
|
46
|
+
|
|
47
|
+
### Key Features
|
|
48
|
+
|
|
49
|
+
1. **Pre-validation**: Tests `yarn cache dir` works before running `setup-node`
|
|
50
|
+
2. **Automatic retry**: Retries up to 3 times when V8 crashes are detected
|
|
51
|
+
3. **Smart error detection**: Only retries on V8 crashes, fails fast on other errors
|
|
52
|
+
4. **Clear diagnostics**: Provides warning annotations in CI logs
|
|
53
|
+
5. **Configurable**: Allows customizing max retries (defaults to 3)
|
|
54
|
+
6. **Backward compatible**: Drop-in replacement for `actions/setup-node@v4`
|
|
55
|
+
|
|
56
|
+
### How It Works
|
|
57
|
+
|
|
58
|
+
```yaml
|
|
59
|
+
- name: Setup Node.js with retry
|
|
60
|
+
shell: bash
|
|
61
|
+
run: |
|
|
62
|
+
# Pre-validate yarn cache dir works
|
|
63
|
+
if timeout 30 yarn cache dir > "$TEMP_OUTPUT" 2>&1; then
|
|
64
|
+
echo "Yarn cache dir command succeeded"
|
|
65
|
+
else
|
|
66
|
+
# Check for V8 crash signature
|
|
67
|
+
if grep -q "Fatal error in.*Check failed: ReadSingleBytecodeData" "$TEMP_OUTPUT"; then
|
|
68
|
+
echo "::warning::V8 bytecode deserialization error detected"
|
|
69
|
+
# Retry logic...
|
|
70
|
+
fi
|
|
71
|
+
fi
|
|
72
|
+
|
|
73
|
+
- name: Actually setup Node.js
|
|
74
|
+
uses: actions/setup-node@v4
|
|
75
|
+
# ... standard setup-node configuration
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
### Usage
|
|
79
|
+
|
|
80
|
+
```yaml
|
|
81
|
+
- name: Setup Node
|
|
82
|
+
uses: ./.github/actions/setup-node-with-retry
|
|
83
|
+
with:
|
|
84
|
+
node-version: 22
|
|
85
|
+
cache: yarn
|
|
86
|
+
cache-dependency-path: '**/yarn.lock'
|
|
87
|
+
max-retries: 3 # Optional, defaults to 3
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
## Changes Made
|
|
91
|
+
|
|
92
|
+
Updated all 8 CI workflow files to use the new action:
|
|
93
|
+
|
|
94
|
+
1. ✅ `examples.yml` - **Re-enabled yarn caching**
|
|
95
|
+
2. ✅ `integration-tests.yml` - **Re-enabled yarn caching for Node 22**
|
|
96
|
+
3. ✅ `lint-js-and-ruby.yml`
|
|
97
|
+
4. ✅ `package-js-tests.yml`
|
|
98
|
+
5. ✅ `playwright.yml`
|
|
99
|
+
6. ✅ `pro-integration-tests.yml`
|
|
100
|
+
7. ✅ `pro-lint.yml`
|
|
101
|
+
8. ✅ `pro-test-package-and-gem.yml`
|
|
102
|
+
|
|
103
|
+
## Benefits
|
|
104
|
+
|
|
105
|
+
1. **Improved reliability**: CI no longer fails due to transient V8 crashes
|
|
106
|
+
2. **Better performance**: Yarn caching re-enabled across all workflows
|
|
107
|
+
3. **Clear diagnostics**: Warning annotations show when retries occur
|
|
108
|
+
4. **Maintainable**: Centralized retry logic in a reusable action
|
|
109
|
+
5. **Future-proof**: Can be updated independently if V8 crash patterns change
|
|
110
|
+
|
|
111
|
+
## Monitoring
|
|
112
|
+
|
|
113
|
+
To verify the retry logic is working when V8 crashes occur:
|
|
114
|
+
|
|
115
|
+
1. Watch CI logs for these warning messages:
|
|
116
|
+
|
|
117
|
+
```
|
|
118
|
+
::warning::V8 bytecode deserialization error detected (attempt 1/3)
|
|
119
|
+
Retrying in 5 seconds...
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
2. Check that jobs succeed after retry instead of failing
|
|
123
|
+
|
|
124
|
+
3. If a job exhausts all retries, it will show:
|
|
125
|
+
```
|
|
126
|
+
::error::All 3 retry attempts failed
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
## Implementation Details
|
|
130
|
+
|
|
131
|
+
- **Timeout**: Each retry attempt has a 30-second timeout for `yarn cache dir`
|
|
132
|
+
- **Retry delay**: 5 seconds between attempts to allow transient issues to clear
|
|
133
|
+
- **Max retries**: Defaults to 3, configurable via input
|
|
134
|
+
- **Error detection**: Regex pattern matches V8 crash signature in stderr/stdout
|
|
135
|
+
|
|
136
|
+
## Future Improvements
|
|
137
|
+
|
|
138
|
+
If the V8 crash persists even with retries, consider:
|
|
139
|
+
|
|
140
|
+
1. Updating Node.js to a version with the fix (when available)
|
|
141
|
+
2. Increasing max-retries for particularly flaky environments
|
|
142
|
+
3. Adding exponential backoff between retries
|
|
143
|
+
4. Implementing cache clearing before retry
|
|
144
|
+
|
|
145
|
+
## Pull Request
|
|
146
|
+
|
|
147
|
+
- **PR**: https://github.com/shakacode/react_on_rails/pull/2082
|
|
148
|
+
- **Branch**: `jg-/ci-retry-v8-crash`
|
|
@@ -429,18 +429,23 @@ module ReactOnRails
|
|
|
429
429
|
end
|
|
430
430
|
|
|
431
431
|
def ensure_webpack_generated_files_exists
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
files = ["manifest.json", server_bundle_js_file]
|
|
432
|
+
all_required_files = ["manifest.json", server_bundle_js_file]
|
|
435
433
|
|
|
436
434
|
if ReactOnRails::Utils.react_on_rails_pro?
|
|
437
435
|
pro_config = ReactOnRailsPro.configuration
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
436
|
+
all_required_files << pro_config.rsc_bundle_js_file
|
|
437
|
+
all_required_files << pro_config.react_client_manifest_file
|
|
438
|
+
all_required_files << pro_config.react_server_client_manifest_file
|
|
441
439
|
end
|
|
442
440
|
|
|
443
|
-
|
|
441
|
+
all_required_files = all_required_files.compact_blank
|
|
442
|
+
|
|
443
|
+
if webpack_generated_files.empty?
|
|
444
|
+
self.webpack_generated_files = all_required_files
|
|
445
|
+
else
|
|
446
|
+
missing_files = all_required_files.reject { |file| webpack_generated_files.include?(file) }
|
|
447
|
+
self.webpack_generated_files += missing_files if missing_files.any?
|
|
448
|
+
end
|
|
444
449
|
end
|
|
445
450
|
|
|
446
451
|
def configure_skip_display_none_deprecation
|
|
@@ -38,29 +38,40 @@ module ReactOnRails
|
|
|
38
38
|
|
|
39
39
|
if verbose
|
|
40
40
|
puts "📦 Generating React on Rails packs..."
|
|
41
|
-
success = run_pack_generation
|
|
41
|
+
success = run_pack_generation(silent: false, verbose: true)
|
|
42
42
|
else
|
|
43
43
|
print "📦 Generating packs... "
|
|
44
|
-
success = run_pack_generation(silent: true)
|
|
44
|
+
success = run_pack_generation(silent: true, verbose: false)
|
|
45
45
|
puts success ? "✅" : "❌"
|
|
46
46
|
end
|
|
47
47
|
|
|
48
48
|
return if success
|
|
49
49
|
|
|
50
50
|
puts "❌ Pack generation failed"
|
|
51
|
+
unless verbose
|
|
52
|
+
puts ""
|
|
53
|
+
puts "💡 Run with #{Rainbow('--verbose').cyan.bold} flag for detailed output:"
|
|
54
|
+
puts " #{Rainbow('bin/dev --verbose').green.bold}"
|
|
55
|
+
end
|
|
51
56
|
exit 1
|
|
52
57
|
end
|
|
53
58
|
|
|
54
59
|
private
|
|
55
60
|
|
|
56
|
-
def run_pack_generation(silent: false)
|
|
61
|
+
def run_pack_generation(silent: false, verbose: false)
|
|
62
|
+
# Set environment variable for child processes to respect verbose mode
|
|
63
|
+
ENV["REACT_ON_RAILS_VERBOSE"] = verbose ? "true" : "false"
|
|
64
|
+
|
|
57
65
|
# If we're already inside a Bundler context AND Rails is available (e.g., called from bin/dev),
|
|
58
66
|
# we can directly require and run the task. Otherwise, use bundle exec.
|
|
59
67
|
if should_run_directly?
|
|
60
68
|
run_rake_task_directly(silent: silent)
|
|
61
69
|
else
|
|
62
|
-
run_via_bundle_exec(silent: silent)
|
|
70
|
+
run_via_bundle_exec(silent: silent, verbose: verbose)
|
|
63
71
|
end
|
|
72
|
+
ensure
|
|
73
|
+
# Clean up environment variable
|
|
74
|
+
ENV.delete("REACT_ON_RAILS_VERBOSE")
|
|
64
75
|
end
|
|
65
76
|
|
|
66
77
|
def should_run_directly?
|
|
@@ -140,14 +151,40 @@ module ReactOnRails
|
|
|
140
151
|
# rubocop:enable Style/StderrPuts, Style/GlobalStdStream
|
|
141
152
|
end
|
|
142
153
|
|
|
143
|
-
def run_via_bundle_exec(silent: false)
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
154
|
+
def run_via_bundle_exec(silent: false, verbose: false)
|
|
155
|
+
# Environment variable is already set in run_pack_generation, but we make it explicit here
|
|
156
|
+
# for clarity and to ensure it's passed to the subprocess
|
|
157
|
+
env = { "REACT_ON_RAILS_VERBOSE" => verbose ? "true" : "false" }
|
|
158
|
+
|
|
159
|
+
# Need to unbundle to prevent Bundler from intercepting our bundle exec call
|
|
160
|
+
# when already running inside a Bundler context (e.g., from bin/dev)
|
|
161
|
+
with_unbundled_context do
|
|
162
|
+
if silent
|
|
163
|
+
system(
|
|
164
|
+
env,
|
|
165
|
+
"bundle", "exec", "rake", "react_on_rails:generate_packs",
|
|
166
|
+
out: File::NULL, err: File::NULL
|
|
167
|
+
)
|
|
168
|
+
else
|
|
169
|
+
system(env, "bundle", "exec", "rake", "react_on_rails:generate_packs")
|
|
170
|
+
end
|
|
171
|
+
end
|
|
172
|
+
end
|
|
173
|
+
|
|
174
|
+
# DRY helper method for Bundler context switching with API compatibility
|
|
175
|
+
# Supports both new (with_unbundled_env) and legacy (with_clean_env) Bundler APIs
|
|
176
|
+
def with_unbundled_context(&block)
|
|
177
|
+
if defined?(Bundler)
|
|
178
|
+
if Bundler.respond_to?(:with_unbundled_env)
|
|
179
|
+
Bundler.with_unbundled_env(&block)
|
|
180
|
+
elsif Bundler.respond_to?(:with_clean_env)
|
|
181
|
+
Bundler.with_clean_env(&block)
|
|
182
|
+
else
|
|
183
|
+
# Fallback if neither method is available (very old Bundler versions)
|
|
184
|
+
yield
|
|
185
|
+
end
|
|
149
186
|
else
|
|
150
|
-
|
|
187
|
+
yield
|
|
151
188
|
end
|
|
152
189
|
end
|
|
153
190
|
end
|
|
@@ -226,7 +226,7 @@ module ReactOnRails
|
|
|
226
226
|
}
|
|
227
227
|
}
|
|
228
228
|
|
|
229
|
-
consoleReplayScript = ReactOnRails.
|
|
229
|
+
consoleReplayScript = ReactOnRails.getConsoleReplayScript();
|
|
230
230
|
|
|
231
231
|
return JSON.stringify({
|
|
232
232
|
html: htmlResult,
|
|
@@ -242,8 +242,9 @@ module ReactOnRails
|
|
|
242
242
|
.server_render_js_with_console_logging(js_code, render_options)
|
|
243
243
|
|
|
244
244
|
html = result["html"]
|
|
245
|
-
|
|
246
|
-
|
|
245
|
+
console_script = result["consoleReplayScript"]
|
|
246
|
+
console_script_tag = wrap_console_script_with_nonce(console_script) if render_options.replay_console
|
|
247
|
+
raw("#{html}#{console_script_tag}")
|
|
247
248
|
rescue ExecJS::ProgramError => err
|
|
248
249
|
raise ReactOnRails::PrerenderError.new(component_name: "N/A (server_render_js called)",
|
|
249
250
|
err: err,
|
|
@@ -394,7 +395,7 @@ module ReactOnRails
|
|
|
394
395
|
server_rendered_html.html_safe,
|
|
395
396
|
content_tag_options)
|
|
396
397
|
|
|
397
|
-
result_console_script = render_options.replay_console ? console_script : ""
|
|
398
|
+
result_console_script = render_options.replay_console ? wrap_console_script_with_nonce(console_script) : ""
|
|
398
399
|
result = compose_react_component_html_with_spec_and_console(
|
|
399
400
|
component_specification_tag, rendered_output, result_console_script
|
|
400
401
|
)
|
|
@@ -419,7 +420,7 @@ module ReactOnRails
|
|
|
419
420
|
server_rendered_html[COMPONENT_HTML_KEY].html_safe,
|
|
420
421
|
content_tag_options)
|
|
421
422
|
|
|
422
|
-
result_console_script = render_options.replay_console ? console_script : ""
|
|
423
|
+
result_console_script = render_options.replay_console ? wrap_console_script_with_nonce(console_script) : ""
|
|
423
424
|
result = compose_react_component_html_with_spec_and_console(
|
|
424
425
|
component_specification_tag, rendered_output, result_console_script
|
|
425
426
|
)
|
|
@@ -436,6 +437,32 @@ module ReactOnRails
|
|
|
436
437
|
)
|
|
437
438
|
end
|
|
438
439
|
|
|
440
|
+
# Wraps console replay JavaScript code in a script tag with CSP nonce if available.
|
|
441
|
+
# The console_script_code is already sanitized by scriptSanitizedVal() in the JavaScript layer,
|
|
442
|
+
# so using html_safe here is secure.
|
|
443
|
+
def wrap_console_script_with_nonce(console_script_code)
|
|
444
|
+
return "" if console_script_code.blank?
|
|
445
|
+
|
|
446
|
+
# Get the CSP nonce if available (Rails 5.2+)
|
|
447
|
+
# Rails 5.2-6.0 use content_security_policy_nonce with no arguments
|
|
448
|
+
# Rails 6.1+ accept an optional directive argument
|
|
449
|
+
nonce = if respond_to?(:content_security_policy_nonce)
|
|
450
|
+
begin
|
|
451
|
+
content_security_policy_nonce(:script)
|
|
452
|
+
rescue ArgumentError
|
|
453
|
+
# Fallback for Rails versions that don't accept arguments
|
|
454
|
+
content_security_policy_nonce
|
|
455
|
+
end
|
|
456
|
+
end
|
|
457
|
+
|
|
458
|
+
# Build the script tag with nonce if available
|
|
459
|
+
script_options = { id: "consoleReplayLog" }
|
|
460
|
+
script_options[:nonce] = nonce if nonce.present?
|
|
461
|
+
|
|
462
|
+
# Safe to use html_safe because content is pre-sanitized via scriptSanitizedVal()
|
|
463
|
+
content_tag(:script, console_script_code.html_safe, script_options)
|
|
464
|
+
end
|
|
465
|
+
|
|
439
466
|
def compose_react_component_html_with_spec_and_console(component_specification_tag, rendered_output,
|
|
440
467
|
console_script)
|
|
441
468
|
# IMPORTANT: Ensure that we mark string as html_safe to avoid escaping.
|
|
@@ -25,40 +25,42 @@ module ReactOnRails
|
|
|
25
25
|
def generate_packs_if_stale
|
|
26
26
|
return unless ReactOnRails.configuration.auto_load_bundle
|
|
27
27
|
|
|
28
|
+
verbose = ENV["REACT_ON_RAILS_VERBOSE"] == "true"
|
|
29
|
+
|
|
28
30
|
add_generated_pack_to_server_bundle
|
|
29
31
|
|
|
30
32
|
# Clean any non-generated files from directories
|
|
31
|
-
clean_non_generated_files_with_feedback
|
|
33
|
+
clean_non_generated_files_with_feedback(verbose: verbose)
|
|
32
34
|
|
|
33
35
|
are_generated_files_present_and_up_to_date = Dir.exist?(generated_packs_directory_path) &&
|
|
34
36
|
File.exist?(generated_server_bundle_file_path) &&
|
|
35
37
|
!stale_or_missing_packs?
|
|
36
38
|
|
|
37
39
|
if are_generated_files_present_and_up_to_date
|
|
38
|
-
puts Rainbow("✅ Generated packs are up to date, no regeneration needed").green
|
|
40
|
+
puts Rainbow("✅ Generated packs are up to date, no regeneration needed").green if verbose
|
|
39
41
|
return
|
|
40
42
|
end
|
|
41
43
|
|
|
42
|
-
clean_generated_directories_with_feedback
|
|
43
|
-
generate_packs
|
|
44
|
+
clean_generated_directories_with_feedback(verbose: verbose)
|
|
45
|
+
generate_packs(verbose: verbose)
|
|
44
46
|
end
|
|
45
47
|
|
|
46
48
|
private
|
|
47
49
|
|
|
48
|
-
def generate_packs
|
|
49
|
-
common_component_to_path.each_value { |component_path| create_pack(component_path) }
|
|
50
|
-
client_component_to_path.each_value { |component_path| create_pack(component_path) }
|
|
50
|
+
def generate_packs(verbose: false)
|
|
51
|
+
common_component_to_path.each_value { |component_path| create_pack(component_path, verbose: verbose) }
|
|
52
|
+
client_component_to_path.each_value { |component_path| create_pack(component_path, verbose: verbose) }
|
|
51
53
|
|
|
52
|
-
create_server_pack if ReactOnRails.configuration.server_bundle_js_file.present?
|
|
54
|
+
create_server_pack(verbose: verbose) if ReactOnRails.configuration.server_bundle_js_file.present?
|
|
53
55
|
end
|
|
54
56
|
|
|
55
|
-
def create_pack(file_path)
|
|
57
|
+
def create_pack(file_path, verbose: false)
|
|
56
58
|
output_path = generated_pack_path(file_path)
|
|
57
59
|
content = pack_file_contents(file_path)
|
|
58
60
|
|
|
59
61
|
File.write(output_path, content)
|
|
60
62
|
|
|
61
|
-
puts(Rainbow("Generated Packs: #{output_path}").yellow)
|
|
63
|
+
puts(Rainbow("Generated Packs: #{output_path}").yellow) if verbose
|
|
62
64
|
end
|
|
63
65
|
|
|
64
66
|
def first_js_statement_in_code(content) # rubocop:disable Metrics/CyclomaticComplexity,Metrics/PerceivedComplexity
|
|
@@ -126,11 +128,11 @@ module ReactOnRails
|
|
|
126
128
|
FILE_CONTENT
|
|
127
129
|
end
|
|
128
130
|
|
|
129
|
-
def create_server_pack
|
|
131
|
+
def create_server_pack(verbose: false)
|
|
130
132
|
File.write(generated_server_bundle_file_path, generated_server_pack_file_content)
|
|
131
133
|
|
|
132
134
|
add_generated_pack_to_server_bundle
|
|
133
|
-
puts(Rainbow("Generated Server Bundle: #{generated_server_bundle_file_path}").orange)
|
|
135
|
+
puts(Rainbow("Generated Server Bundle: #{generated_server_bundle_file_path}").orange) if verbose
|
|
134
136
|
end
|
|
135
137
|
|
|
136
138
|
def build_server_pack_content(component_on_server_imports, server_components, client_components)
|
|
@@ -200,17 +202,17 @@ module ReactOnRails
|
|
|
200
202
|
"#{generated_nonentrypoints_path}/#{generated_server_bundle_file_name}.js"
|
|
201
203
|
end
|
|
202
204
|
|
|
203
|
-
def clean_non_generated_files_with_feedback
|
|
205
|
+
def clean_non_generated_files_with_feedback(verbose: false)
|
|
204
206
|
directories_to_clean = [generated_packs_directory_path, generated_server_bundle_directory_path].compact.uniq
|
|
205
207
|
expected_files = build_expected_files_set
|
|
206
208
|
|
|
207
|
-
puts Rainbow("🧹 Cleaning non-generated files...").yellow
|
|
209
|
+
puts Rainbow("🧹 Cleaning non-generated files...").yellow if verbose
|
|
208
210
|
|
|
209
211
|
total_deleted = directories_to_clean.sum do |dir_path|
|
|
210
|
-
clean_unexpected_files_from_directory(dir_path, expected_files)
|
|
212
|
+
clean_unexpected_files_from_directory(dir_path, expected_files, verbose: verbose)
|
|
211
213
|
end
|
|
212
214
|
|
|
213
|
-
display_cleanup_summary(total_deleted)
|
|
215
|
+
display_cleanup_summary(total_deleted, verbose: verbose) if verbose
|
|
214
216
|
end
|
|
215
217
|
|
|
216
218
|
def build_expected_files_set
|
|
@@ -225,17 +227,17 @@ module ReactOnRails
|
|
|
225
227
|
{ pack_files: expected_pack_files, server_bundle: expected_server_bundle }
|
|
226
228
|
end
|
|
227
229
|
|
|
228
|
-
def clean_unexpected_files_from_directory(dir_path, expected_files)
|
|
230
|
+
def clean_unexpected_files_from_directory(dir_path, expected_files, verbose: false)
|
|
229
231
|
return 0 unless Dir.exist?(dir_path)
|
|
230
232
|
|
|
231
233
|
existing_files = Dir.glob("#{dir_path}/**/*").select { |f| File.file?(f) }
|
|
232
234
|
unexpected_files = find_unexpected_files(existing_files, dir_path, expected_files)
|
|
233
235
|
|
|
234
236
|
if unexpected_files.any?
|
|
235
|
-
delete_unexpected_files(unexpected_files, dir_path)
|
|
237
|
+
delete_unexpected_files(unexpected_files, dir_path, verbose: verbose)
|
|
236
238
|
unexpected_files.length
|
|
237
239
|
else
|
|
238
|
-
puts Rainbow(" No unexpected files found in #{dir_path}").cyan
|
|
240
|
+
puts Rainbow(" No unexpected files found in #{dir_path}").cyan if verbose
|
|
239
241
|
0
|
|
240
242
|
end
|
|
241
243
|
end
|
|
@@ -250,15 +252,21 @@ module ReactOnRails
|
|
|
250
252
|
end
|
|
251
253
|
end
|
|
252
254
|
|
|
253
|
-
def delete_unexpected_files(unexpected_files, dir_path)
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
255
|
+
def delete_unexpected_files(unexpected_files, dir_path, verbose: false)
|
|
256
|
+
if verbose
|
|
257
|
+
puts Rainbow(" Deleting #{unexpected_files.length} unexpected files from #{dir_path}:").cyan
|
|
258
|
+
unexpected_files.each do |file|
|
|
259
|
+
puts Rainbow(" - #{File.basename(file)}").blue
|
|
260
|
+
File.delete(file)
|
|
261
|
+
end
|
|
262
|
+
else
|
|
263
|
+
unexpected_files.each { |file| File.delete(file) }
|
|
258
264
|
end
|
|
259
265
|
end
|
|
260
266
|
|
|
261
|
-
def display_cleanup_summary(total_deleted)
|
|
267
|
+
def display_cleanup_summary(total_deleted, verbose: false)
|
|
268
|
+
return unless verbose
|
|
269
|
+
|
|
262
270
|
if total_deleted.positive?
|
|
263
271
|
puts Rainbow("🗑️ Deleted #{total_deleted} unexpected files total").red
|
|
264
272
|
else
|
|
@@ -266,15 +274,17 @@ module ReactOnRails
|
|
|
266
274
|
end
|
|
267
275
|
end
|
|
268
276
|
|
|
269
|
-
def clean_generated_directories_with_feedback
|
|
277
|
+
def clean_generated_directories_with_feedback(verbose: false)
|
|
270
278
|
directories_to_clean = [
|
|
271
279
|
generated_packs_directory_path,
|
|
272
280
|
generated_server_bundle_directory_path
|
|
273
281
|
].compact.uniq
|
|
274
282
|
|
|
275
|
-
puts Rainbow("🧹 Cleaning generated directories...").yellow
|
|
283
|
+
puts Rainbow("🧹 Cleaning generated directories...").yellow if verbose
|
|
284
|
+
|
|
285
|
+
total_deleted = directories_to_clean.sum { |dir_path| clean_directory_with_feedback(dir_path, verbose: verbose) }
|
|
276
286
|
|
|
277
|
-
|
|
287
|
+
return unless verbose
|
|
278
288
|
|
|
279
289
|
if total_deleted.positive?
|
|
280
290
|
puts Rainbow("🗑️ Deleted #{total_deleted} generated files total").red
|
|
@@ -283,27 +293,29 @@ module ReactOnRails
|
|
|
283
293
|
end
|
|
284
294
|
end
|
|
285
295
|
|
|
286
|
-
def clean_directory_with_feedback(dir_path)
|
|
287
|
-
return create_directory_with_feedback(dir_path) unless Dir.exist?(dir_path)
|
|
296
|
+
def clean_directory_with_feedback(dir_path, verbose: false)
|
|
297
|
+
return create_directory_with_feedback(dir_path, verbose: verbose) unless Dir.exist?(dir_path)
|
|
288
298
|
|
|
289
299
|
files = Dir.glob("#{dir_path}/**/*").select { |f| File.file?(f) }
|
|
290
300
|
|
|
291
301
|
if files.any?
|
|
292
|
-
|
|
293
|
-
|
|
302
|
+
if verbose
|
|
303
|
+
puts Rainbow(" Deleting #{files.length} files from #{dir_path}:").cyan
|
|
304
|
+
files.each { |file| puts Rainbow(" - #{File.basename(file)}").blue }
|
|
305
|
+
end
|
|
294
306
|
FileUtils.rm_rf(dir_path)
|
|
295
307
|
FileUtils.mkdir_p(dir_path)
|
|
296
308
|
files.length
|
|
297
309
|
else
|
|
298
|
-
puts Rainbow(" Directory #{dir_path} is already empty").cyan
|
|
310
|
+
puts Rainbow(" Directory #{dir_path} is already empty").cyan if verbose
|
|
299
311
|
FileUtils.rm_rf(dir_path)
|
|
300
312
|
FileUtils.mkdir_p(dir_path)
|
|
301
313
|
0
|
|
302
314
|
end
|
|
303
315
|
end
|
|
304
316
|
|
|
305
|
-
def create_directory_with_feedback(dir_path)
|
|
306
|
-
puts Rainbow(" Directory #{dir_path} does not exist, creating...").cyan
|
|
317
|
+
def create_directory_with_feedback(dir_path, verbose: false)
|
|
318
|
+
puts Rainbow(" Directory #{dir_path} does not exist, creating...").cyan if verbose
|
|
307
319
|
FileUtils.mkdir_p(dir_path)
|
|
308
320
|
0
|
|
309
321
|
end
|
|
@@ -17,18 +17,24 @@ namespace :react_on_rails do
|
|
|
17
17
|
DESC
|
|
18
18
|
|
|
19
19
|
task generate_packs: :environment do
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
20
|
+
verbose = ENV["REACT_ON_RAILS_VERBOSE"] == "true"
|
|
21
|
+
|
|
22
|
+
if verbose
|
|
23
|
+
puts Rainbow("🚀 Starting React on Rails pack generation...").bold
|
|
24
|
+
puts Rainbow("📁 Auto-load bundle: #{ReactOnRails.configuration.auto_load_bundle}").cyan
|
|
25
|
+
puts Rainbow("📂 Components subdirectory: #{ReactOnRails.configuration.components_subdirectory}").cyan
|
|
26
|
+
puts ""
|
|
27
|
+
end
|
|
24
28
|
|
|
25
29
|
begin
|
|
26
30
|
start_time = Time.now
|
|
27
31
|
ReactOnRails::PacksGenerator.instance.generate_packs_if_stale
|
|
28
32
|
end_time = Time.now
|
|
29
33
|
|
|
30
|
-
|
|
31
|
-
|
|
34
|
+
if verbose
|
|
35
|
+
puts ""
|
|
36
|
+
puts Rainbow("✨ Pack generation completed in #{((end_time - start_time) * 1000).round(1)}ms").green
|
|
37
|
+
end
|
|
32
38
|
rescue ReactOnRails::Error => e
|
|
33
39
|
handle_react_on_rails_error(e)
|
|
34
40
|
exit 1
|
|
@@ -20,13 +20,12 @@ You can find the **package** version numbers from this repo's tags and below in
|
|
|
20
20
|
_Add changes in master not yet tagged._
|
|
21
21
|
|
|
22
22
|
### Improved
|
|
23
|
-
- Significantly improved streaming performance by processing React components concurrently instead of sequentially. This reduces latency and improves responsiveness when using `stream_view_containing_react_components`.
|
|
24
23
|
|
|
25
|
-
|
|
26
|
-
- Added `config.concurrent_component_streaming_buffer_size` configuration option to control the memory buffer size for concurrent component streaming (defaults to 64). This allows fine-tuning of memory usage vs. performance for streaming applications.
|
|
24
|
+
- **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. This significantly reduces latency and improves responsiveness when using `stream_view_containing_react_components`. [PR 2015](https://github.com/shakacode/react_on_rails/pull/2015) by [ihabadham](https://github.com/ihabadham).
|
|
27
25
|
|
|
28
26
|
### Added
|
|
29
27
|
|
|
28
|
+
- Added `config.concurrent_component_streaming_buffer_size` configuration option to control the memory buffer size for concurrent component streaming (defaults to 64). This allows fine-tuning of memory usage vs. performance for streaming applications.
|
|
30
29
|
- Added `cached_stream_react_component` helper method, similar to `cached_react_component` but for streamed components.
|
|
31
30
|
- **License Validation System**: Implemented comprehensive JWT-based license validation with offline verification using RSA-256 signatures. License validation occurs at startup in both Ruby and Node.js environments. Supports required fields (`sub`, `iat`, `exp`) and optional fields (`plan`, `organization`, `iss`). FREE evaluation licenses are available for 3 months at [shakacode.com/react-on-rails-pro](https://shakacode.com/react-on-rails-pro). [PR #1857](https://github.com/shakacode/react_on_rails/pull/1857) by [AbanoubGhadban](https://github.com/AbanoubGhadban).
|
|
32
31
|
- **Pro-Specific Configurations Moved from Open-Source**: The following React Server Components (RSC) configurations are now exclusively in the Pro gem and should be configured in `ReactOnRailsPro.configure`:
|
|
@@ -46,9 +45,17 @@ _Add changes in master not yet tagged._
|
|
|
46
45
|
|
|
47
46
|
- **Node Renderer Gem Version Validation**: The node renderer now validates that the Ruby gem version (`react_on_rails_pro`) matches the node renderer package version (`@shakacode-tools/react-on-rails-pro-node-renderer`) on every render request. Environment-aware: strict enforcement in development (returns 412 Precondition Failed on mismatch), permissive in production (allows with warning). Includes version normalization to handle Ruby gem vs NPM format differences (e.g., `4.0.0.rc.1` vs `4.0.0-rc.1`). [PR #1881](https://github.com/shakacode/react_on_rails/pull/1881) by [AbanoubGhadban](https://github.com/AbanoubGhadban).
|
|
48
47
|
|
|
48
|
+
### Fixed
|
|
49
|
+
|
|
50
|
+
- **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).
|
|
51
|
+
|
|
52
|
+
- **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).
|
|
53
|
+
|
|
49
54
|
### Changed
|
|
50
55
|
|
|
51
|
-
|
|
56
|
+
### Deprecated
|
|
57
|
+
|
|
58
|
+
- **Node Renderer Configuration**: Renamed `bundlePath` configuration option to `serverBundleCachePath` in the node renderer to better describe its purpose and avoid confusion with Shakapacker's public bundle path. The old `bundlePath` option continues to work with deprecation warnings. Both `RENDERER_SERVER_BUNDLE_CACHE_PATH` (new) and `RENDERER_BUNDLE_PATH` (deprecated) environment variables are supported. [PR 2008](https://github.com/shakacode/react_on_rails/pull/2008) by [justin808](https://github.com/justin808).
|
|
52
59
|
|
|
53
60
|
### Changed (Breaking)
|
|
54
61
|
|
|
@@ -9,7 +9,7 @@ GIT
|
|
|
9
9
|
PATH
|
|
10
10
|
remote: ..
|
|
11
11
|
specs:
|
|
12
|
-
react_on_rails (16.2.0.beta.
|
|
12
|
+
react_on_rails (16.2.0.beta.12)
|
|
13
13
|
addressable
|
|
14
14
|
connection_pool
|
|
15
15
|
execjs (~> 2.5)
|
|
@@ -20,7 +20,7 @@ PATH
|
|
|
20
20
|
PATH
|
|
21
21
|
remote: .
|
|
22
22
|
specs:
|
|
23
|
-
react_on_rails_pro (16.2.0.beta.
|
|
23
|
+
react_on_rails_pro (16.2.0.beta.12)
|
|
24
24
|
addressable
|
|
25
25
|
async (>= 2.6)
|
|
26
26
|
connection_pool
|
|
@@ -28,7 +28,7 @@ PATH
|
|
|
28
28
|
httpx (~> 1.5)
|
|
29
29
|
jwt (~> 2.7)
|
|
30
30
|
rainbow
|
|
31
|
-
react_on_rails (= 16.2.0.beta.
|
|
31
|
+
react_on_rails (= 16.2.0.beta.12)
|
|
32
32
|
|
|
33
33
|
GEM
|
|
34
34
|
remote: https://rubygems.org/
|
|
@@ -347,7 +347,8 @@ module ReactOnRailsProHelper
|
|
|
347
347
|
render_options: render_options
|
|
348
348
|
)
|
|
349
349
|
else
|
|
350
|
-
|
|
350
|
+
console_script = chunk_json_result["consoleReplayScript"]
|
|
351
|
+
result_console_script = render_options.replay_console ? wrap_console_script_with_nonce(console_script) : ""
|
|
351
352
|
# No need to prepend component_specification_tag or add rails context again
|
|
352
353
|
# as they're already included in the first chunk
|
|
353
354
|
compose_react_component_html_with_spec_and_console(
|
|
@@ -9,7 +9,7 @@ GIT
|
|
|
9
9
|
PATH
|
|
10
10
|
remote: ../../..
|
|
11
11
|
specs:
|
|
12
|
-
react_on_rails (16.2.0.beta.
|
|
12
|
+
react_on_rails (16.2.0.beta.12)
|
|
13
13
|
addressable
|
|
14
14
|
connection_pool
|
|
15
15
|
execjs (~> 2.5)
|
|
@@ -20,7 +20,7 @@ PATH
|
|
|
20
20
|
PATH
|
|
21
21
|
remote: ../..
|
|
22
22
|
specs:
|
|
23
|
-
react_on_rails_pro (16.2.0.beta.
|
|
23
|
+
react_on_rails_pro (16.2.0.beta.12)
|
|
24
24
|
addressable
|
|
25
25
|
async (>= 2.6)
|
|
26
26
|
connection_pool
|
|
@@ -28,7 +28,7 @@ PATH
|
|
|
28
28
|
httpx (~> 1.5)
|
|
29
29
|
jwt (~> 2.7)
|
|
30
30
|
rainbow
|
|
31
|
-
react_on_rails (= 16.2.0.beta.
|
|
31
|
+
react_on_rails (= 16.2.0.beta.12)
|
|
32
32
|
|
|
33
33
|
GEM
|
|
34
34
|
remote: https://rubygems.org/
|
|
@@ -295,13 +295,13 @@ describe ReactOnRailsProHelper do
|
|
|
295
295
|
let(:chunks) do
|
|
296
296
|
[
|
|
297
297
|
{ html: "<div>Chunk 1: Stream React Server Components</div>",
|
|
298
|
-
consoleReplayScript: "
|
|
299
|
-
"['Chunk 1: Console Message'])
|
|
298
|
+
consoleReplayScript: "console.log.apply(console, " \
|
|
299
|
+
"['Chunk 1: Console Message'])" },
|
|
300
300
|
{ html: "<div>Chunk 2: More content</div>",
|
|
301
|
-
consoleReplayScript: "
|
|
301
|
+
consoleReplayScript: "console.log.apply(console, " \
|
|
302
302
|
"['Chunk 2: Console Message']);\n" \
|
|
303
303
|
"console.error.apply(console, " \
|
|
304
|
-
"['Chunk 2: Console Error'])
|
|
304
|
+
"['Chunk 2: Console Error']);" },
|
|
305
305
|
{ html: "<div>Chunk 3: Final content</div>", consoleReplayScript: "" }
|
|
306
306
|
]
|
|
307
307
|
end
|
|
@@ -373,7 +373,12 @@ describe ReactOnRailsProHelper do
|
|
|
373
373
|
mock_request_and_response
|
|
374
374
|
initial_result = stream_react_component(component_name, props: props, **component_options)
|
|
375
375
|
expect(initial_result).to include(react_component_div_with_initial_chunk)
|
|
376
|
-
|
|
376
|
+
# consoleReplayScript is now wrapped in a script tag with id="consoleReplayLog"
|
|
377
|
+
if chunks.first[:consoleReplayScript].present?
|
|
378
|
+
script = chunks.first[:consoleReplayScript]
|
|
379
|
+
wrapped = "<script id=\"consoleReplayLog\">#{script}</script>"
|
|
380
|
+
expect(initial_result).to include(wrapped)
|
|
381
|
+
end
|
|
377
382
|
expect(initial_result).not_to include("More content", "Final content")
|
|
378
383
|
expect(chunks_read.count).to eq(1)
|
|
379
384
|
end
|
|
@@ -386,9 +391,12 @@ describe ReactOnRailsProHelper do
|
|
|
386
391
|
expect(fiber).to be_alive
|
|
387
392
|
|
|
388
393
|
second_result = fiber.resume
|
|
389
|
-
# regex that matches the html and consoleReplayScript
|
|
394
|
+
# regex that matches the html and wrapped consoleReplayScript
|
|
395
|
+
# Note: consoleReplayScript is now wrapped in a script tag with id="consoleReplayLog"
|
|
396
|
+
script = chunks[1][:consoleReplayScript]
|
|
397
|
+
wrapped = script.present? ? "<script id=\"consoleReplayLog\">#{script}</script>" : ""
|
|
390
398
|
expect(second_result).to match(
|
|
391
|
-
/#{Regexp.escape(chunks[1][:html])}\s+#{Regexp.escape(
|
|
399
|
+
/#{Regexp.escape(chunks[1][:html])}\s+#{Regexp.escape(wrapped)}/
|
|
392
400
|
)
|
|
393
401
|
expect(second_result).not_to include("Stream React Server Components", "Final content")
|
|
394
402
|
expect(chunks_read.count).to eq(2)
|
|
@@ -21,15 +21,20 @@ describe "Console logging from server" do
|
|
|
21
21
|
console.log.apply(console, ["[SERVER] Script4\\"</div>\\"(/script <script>alert('WTF4')(/script>"]);
|
|
22
22
|
console.log.apply(console, ["[SERVER] Script5:\\"</div>\\"(/script> <script>alert('WTF5')(/script>"]);
|
|
23
23
|
console.log.apply(console, ["[SERVER] railsContext.serverSide is ","true"]);
|
|
24
|
+
console.log.apply(console, ["[SERVER] RENDERED ReduxSharedStoreApp to dom node with id: ReduxSharedStoreApp-react-component-1"]);
|
|
24
25
|
JS
|
|
25
26
|
|
|
26
27
|
expected_lines = expected.split("\n")
|
|
27
28
|
|
|
28
|
-
|
|
29
|
-
|
|
29
|
+
# When multiple components with replay_console are rendered, each creates its own script tag
|
|
30
|
+
# with id="consoleReplayLog". Nokogiri's .text concatenates them without separators, which
|
|
31
|
+
# breaks parsing. Instead, we explicitly join them with newlines.
|
|
32
|
+
script_nodes = html_nodes.css("script#consoleReplayLog")
|
|
33
|
+
script_text = script_nodes.map(&:text).join("\n")
|
|
34
|
+
script_lines = script_text.split("\n")
|
|
30
35
|
|
|
31
|
-
#
|
|
32
|
-
script_lines.shift
|
|
36
|
+
# Remove leading blank line if present (old format had it, new format doesn't)
|
|
37
|
+
script_lines.shift if script_lines.first && script_lines.first.empty?
|
|
33
38
|
|
|
34
39
|
# Create external iterators for expected and found console replay script lines:
|
|
35
40
|
expected_lines_iterator = expected_lines.to_enum
|
|
@@ -5,7 +5,7 @@ module ReactOnRails
|
|
|
5
5
|
|
|
6
6
|
private
|
|
7
7
|
|
|
8
|
-
def self.run_pack_generation: (?silent: bool) -> bool
|
|
8
|
+
def self.run_pack_generation: (?silent: bool, ?verbose: bool) -> bool
|
|
9
9
|
def self.should_run_directly?: () -> bool
|
|
10
10
|
def self.rails_available?: () -> bool
|
|
11
11
|
def self.run_rake_task_directly: (?silent: bool) -> bool
|
|
@@ -13,7 +13,7 @@ module ReactOnRails
|
|
|
13
13
|
def self.prepare_rake_task: () -> untyped
|
|
14
14
|
def self.capture_output: (bool) { () -> bool } -> bool
|
|
15
15
|
def self.handle_rake_error: (Exception, bool) -> void
|
|
16
|
-
def self.run_via_bundle_exec: (?silent: bool) -> (bool | nil)
|
|
16
|
+
def self.run_via_bundle_exec: (?silent: bool, ?verbose: bool) -> (bool | nil)
|
|
17
17
|
end
|
|
18
18
|
end
|
|
19
19
|
end
|
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: react_on_rails
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 16.2.0.beta.
|
|
4
|
+
version: 16.2.0.beta.12
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Justin Gordon
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: exe
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2025-11-
|
|
11
|
+
date: 2025-11-21 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: addressable
|
|
@@ -141,6 +141,7 @@ files:
|
|
|
141
141
|
- TODO.md
|
|
142
142
|
- WARP.md
|
|
143
143
|
- analysis/rake-task-duplicate-analysis.md
|
|
144
|
+
- analysis/v8-crash-retry-solution.md
|
|
144
145
|
- app/helpers/react_on_rails_helper.rb
|
|
145
146
|
- bin/ci-local
|
|
146
147
|
- bin/ci-rerun-failures
|