Commit c32a19d
Add bidirectional streaming for async props (pull mode) (#4048)
## Summary
Implements
[#4046](#4046):
bidirectional streaming of async props where React components can lazily
request props from Rails via HTTP/2 streaming, complementing the
existing push model.
### User-facing API
```erb
<%# Pure pull mode — all props requested lazily by React %>
<%= stream_react_component_with_async_props("Dashboard", push_props: []) do |emit|
while (prop_name = emit.pull_requests.dequeue)
emit.call(prop_name, fetch_prop(prop_name))
end
end %>
<%# Mixed mode — stats pushed eagerly, rest pulled on demand %>
<%= stream_react_component_with_async_props("Dashboard", push_props: %w[stats]) do |emit|
emit.call("stats", compute_stats)
while (prop_name = emit.pull_requests.dequeue)
case prop_name
when "recommendations" then emit.call(prop_name, Recommendation.all)
else emit.reject(prop_name, "Unknown prop: #{prop_name}")
end
end
end %>
```
### Architecture
- **React side**: `getProp()` in `AsyncPropsManager` emits `propRequest`
control messages for non-push props
- **Node renderer**: Injects a PassThrough stream that multiplexes HTML
chunks + propRequest/renderComplete control messages
- **Rails side**: `StreamRequest` parses control messages, routes
`propRequest` → `PullRequestQueue`, `renderComplete` → closes queue
- **Wire format**: Length-prefixed chunks with `messageType` metadata
field for control messages
### Key changes
| Layer | File | Change |
|-------|------|--------|
| TS (Pro) | `AsyncPropsManager.ts` | Pull request emission,
`rejectProp()`, buffered requests, `flushPendingPullRequests()` |
| TS (Pro) | `streamingUtils.ts` | `formatPropRequestChunk()`,
`formatRenderCompleteChunk()` |
| TS (Renderer) | `handleIncrementalRenderRequest.ts` | Injectable
PassThrough stream, propRequest emitter, renderComplete on stream end |
| TS (Renderer) | `vm.ts` | Expose `sharedExecutionContext` on
`ExecutionContext` type |
| Ruby (OSS) | `length_prefixed_parser.rb` | Route `messageType` control
messages without HTML payload |
| Ruby (Pro) | `async_props_emitter.rb` | `PullRequestQueue`,
`reject()`, `render_complete!()`, pushed props tracking |
| Ruby (Pro) | `request.rb` | `pull_enabled` flag,
`pullEnabled`/`pushProps` in NDJSON, return `[response, emitter]` tuple
|
| Ruby (Pro) | `stream_request.rb` | Destructure pull tuple, route
control messages, safety-net `render_complete!` |
| Ruby (Pro) | `node_rendering_pool.rb` | Pass `push_props` to
incremental render |
| Dummy app | 5 views, 3 components, controller, routes, E2E fixtures |
Test pages for pure pull, mixed, and rejection scenarios |
## Test plan
- [ ] Existing `LengthPrefixedParser` specs pass (8/8)
- [ ] Existing node renderer tests pass (pre-existing timeout failures
excluded)
- [ ] TypeScript builds clean for `react-on-rails-pro` and
`react-on-rails-pro-node-renderer`
- [ ] Rubocop passes on all modified Ruby files
- [ ] Mixed props page SSR renders pushed stats + pulled
recommendations/relatedPosts
- [ ] Rejection page SSR renders allowed items + error boundary for
forbidden data
- [ ] Existing streaming pages (`/stream_async_components`,
`/test_incremental_rendering`) have no regressions
- [ ] E2E tests for lazy/mixed props via Redis fixtures
🤖 Generated with [Claude Code](https://claude.com/claude-code)
<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit
* **New Features**
* Added pull-based async prop loading for incremental renders, including
mixed push/pull behavior via an optional eager prop list.
* Added async prop rejection support, including client-visible error
boundaries and coordinated render-complete signaling.
* **Bug Fixes**
* Improved streaming control-message handling so control chunks aren’t
misinterpreted as HTML.
* **Tests**
* Expanded end-to-end coverage for pull/Redis scenarios and prop
rejection, plus updated React fixtures/components and queue/emitter unit
tests.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Co-authored-by: Justin Gordon <justin@shakacode.com>1 parent 91f2e16 commit c32a19d
33 files changed
Lines changed: 2271 additions & 60 deletions
File tree
- packages
- react-on-rails-pro-node-renderer
- src/worker
- tests
- react-on-rails-pro
- src
- tests
- react_on_rails_pro
- lib/react_on_rails_pro
- server_rendering_pool
- spec
- dummy
- app
- controllers
- views/pages
- client/app/ror-auto-load-components
- config
- e2e-tests
- spec/controllers
- react_on_rails_pro
- server_rendering_pool
- react_on_rails
- lib/react_on_rails
- spec/lib/react_on_rails
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
36 | 36 | | |
37 | 37 | | |
38 | 38 | | |
| 39 | + | |
39 | 40 | | |
40 | 41 | | |
41 | 42 | | |
| |||
Lines changed: 150 additions & 1 deletion
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
13 | 13 | | |
14 | 14 | | |
15 | 15 | | |
| 16 | + | |
| 17 | + | |
16 | 18 | | |
17 | 19 | | |
18 | 20 | | |
19 | 21 | | |
20 | 22 | | |
21 | 23 | | |
| 24 | + | |
| 25 | + | |
| 26 | + | |
| 27 | + | |
| 28 | + | |
| 29 | + | |
| 30 | + | |
| 31 | + | |
22 | 32 | | |
23 | 33 | | |
24 | 34 | | |
| |||
32 | 42 | | |
33 | 43 | | |
34 | 44 | | |
| 45 | + | |
| 46 | + | |
| 47 | + | |
| 48 | + | |
| 49 | + | |
| 50 | + | |
| 51 | + | |
| 52 | + | |
| 53 | + | |
35 | 54 | | |
36 | 55 | | |
37 | 56 | | |
| |||
52 | 71 | | |
53 | 72 | | |
54 | 73 | | |
| 74 | + | |
| 75 | + | |
| 76 | + | |
| 77 | + | |
| 78 | + | |
| 79 | + | |
| 80 | + | |
| 81 | + | |
| 82 | + | |
| 83 | + | |
| 84 | + | |
| 85 | + | |
| 86 | + | |
| 87 | + | |
| 88 | + | |
| 89 | + | |
| 90 | + | |
| 91 | + | |
| 92 | + | |
| 93 | + | |
| 94 | + | |
| 95 | + | |
| 96 | + | |
| 97 | + | |
| 98 | + | |
| 99 | + | |
| 100 | + | |
| 101 | + | |
| 102 | + | |
| 103 | + | |
| 104 | + | |
| 105 | + | |
| 106 | + | |
| 107 | + | |
| 108 | + | |
| 109 | + | |
| 110 | + | |
| 111 | + | |
| 112 | + | |
| 113 | + | |
| 114 | + | |
55 | 115 | | |
56 | 116 | | |
57 | 117 | | |
| |||
61 | 121 | | |
62 | 122 | | |
63 | 123 | | |
| 124 | + | |
| 125 | + | |
64 | 126 | | |
65 | 127 | | |
66 | 128 | | |
| |||
79 | 141 | | |
80 | 142 | | |
81 | 143 | | |
| 144 | + | |
| 145 | + | |
| 146 | + | |
| 147 | + | |
| 148 | + | |
| 149 | + | |
| 150 | + | |
| 151 | + | |
| 152 | + | |
| 153 | + | |
| 154 | + | |
| 155 | + | |
82 | 156 | | |
83 | 157 | | |
84 | 158 | | |
| |||
139 | 213 | | |
140 | 214 | | |
141 | 215 | | |
| 216 | + | |
| 217 | + | |
| 218 | + | |
| 219 | + | |
| 220 | + | |
| 221 | + | |
| 222 | + | |
| 223 | + | |
| 224 | + | |
| 225 | + | |
| 226 | + | |
| 227 | + | |
| 228 | + | |
| 229 | + | |
| 230 | + | |
| 231 | + | |
| 232 | + | |
| 233 | + | |
| 234 | + | |
| 235 | + | |
| 236 | + | |
| 237 | + | |
| 238 | + | |
| 239 | + | |
| 240 | + | |
| 241 | + | |
| 242 | + | |
| 243 | + | |
| 244 | + | |
| 245 | + | |
| 246 | + | |
| 247 | + | |
| 248 | + | |
| 249 | + | |
| 250 | + | |
| 251 | + | |
| 252 | + | |
| 253 | + | |
| 254 | + | |
| 255 | + | |
| 256 | + | |
| 257 | + | |
| 258 | + | |
| 259 | + | |
| 260 | + | |
| 261 | + | |
| 262 | + | |
| 263 | + | |
| 264 | + | |
| 265 | + | |
| 266 | + | |
| 267 | + | |
| 268 | + | |
| 269 | + | |
| 270 | + | |
| 271 | + | |
| 272 | + | |
| 273 | + | |
| 274 | + | |
| 275 | + | |
| 276 | + | |
| 277 | + | |
| 278 | + | |
| 279 | + | |
| 280 | + | |
| 281 | + | |
| 282 | + | |
| 283 | + | |
| 284 | + | |
| 285 | + | |
| 286 | + | |
| 287 | + | |
| 288 | + | |
| 289 | + | |
| 290 | + | |
142 | 291 | | |
143 | 292 | | |
144 | | - | |
| 293 | + | |
145 | 294 | | |
146 | 295 | | |
147 | 296 | | |
| |||
Lines changed: 29 additions & 0 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
| 19 | + | |
| 20 | + | |
| 21 | + | |
| 22 | + | |
| 23 | + | |
| 24 | + | |
| 25 | + | |
| 26 | + | |
| 27 | + | |
| 28 | + | |
| 29 | + | |
Lines changed: 2 additions & 0 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
496 | 496 | | |
497 | 497 | | |
498 | 498 | | |
| 499 | + | |
499 | 500 | | |
500 | 501 | | |
501 | 502 | | |
| |||
661 | 662 | | |
662 | 663 | | |
663 | 664 | | |
| 665 | + | |
664 | 666 | | |
665 | 667 | | |
666 | 668 | | |
| |||
Lines changed: 139 additions & 0 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
| 19 | + | |
| 20 | + | |
| 21 | + | |
| 22 | + | |
| 23 | + | |
| 24 | + | |
| 25 | + | |
| 26 | + | |
| 27 | + | |
| 28 | + | |
| 29 | + | |
| 30 | + | |
| 31 | + | |
| 32 | + | |
| 33 | + | |
| 34 | + | |
| 35 | + | |
| 36 | + | |
| 37 | + | |
| 38 | + | |
| 39 | + | |
| 40 | + | |
| 41 | + | |
| 42 | + | |
| 43 | + | |
| 44 | + | |
| 45 | + | |
| 46 | + | |
| 47 | + | |
| 48 | + | |
| 49 | + | |
| 50 | + | |
| 51 | + | |
| 52 | + | |
| 53 | + | |
| 54 | + | |
| 55 | + | |
| 56 | + | |
| 57 | + | |
| 58 | + | |
| 59 | + | |
| 60 | + | |
| 61 | + | |
| 62 | + | |
| 63 | + | |
| 64 | + | |
| 65 | + | |
| 66 | + | |
| 67 | + | |
| 68 | + | |
| 69 | + | |
| 70 | + | |
| 71 | + | |
| 72 | + | |
| 73 | + | |
| 74 | + | |
| 75 | + | |
| 76 | + | |
| 77 | + | |
| 78 | + | |
| 79 | + | |
| 80 | + | |
| 81 | + | |
| 82 | + | |
| 83 | + | |
| 84 | + | |
| 85 | + | |
| 86 | + | |
| 87 | + | |
| 88 | + | |
| 89 | + | |
| 90 | + | |
| 91 | + | |
| 92 | + | |
| 93 | + | |
| 94 | + | |
| 95 | + | |
| 96 | + | |
| 97 | + | |
| 98 | + | |
| 99 | + | |
| 100 | + | |
| 101 | + | |
| 102 | + | |
| 103 | + | |
| 104 | + | |
| 105 | + | |
| 106 | + | |
| 107 | + | |
| 108 | + | |
| 109 | + | |
| 110 | + | |
| 111 | + | |
| 112 | + | |
| 113 | + | |
| 114 | + | |
| 115 | + | |
| 116 | + | |
| 117 | + | |
| 118 | + | |
| 119 | + | |
| 120 | + | |
| 121 | + | |
| 122 | + | |
| 123 | + | |
| 124 | + | |
| 125 | + | |
| 126 | + | |
| 127 | + | |
| 128 | + | |
| 129 | + | |
| 130 | + | |
| 131 | + | |
| 132 | + | |
| 133 | + | |
| 134 | + | |
| 135 | + | |
| 136 | + | |
| 137 | + | |
| 138 | + | |
| 139 | + | |
0 commit comments