Skip to content

Commit ba38c98

Browse files
[WIP] handle errors happen in rsc payload (#1663)
* stream rsc payload in json objects like streamed react components * make path to rsc bundle and react client manifest configurable * feat: Improve client manifest path handling for dev server - Add `dev_server_url` helper to centralize dev server URL construction - Add `public_output_uri_path` to get relative webpack output path - Add `asset_uri_from_packer` to handle asset URIs consistently - Update `react_client_manifest_file_path` to return dev server URLs when appropriate - Add comprehensive specs for new asset URI handling This change ensures client manifest paths are properly resolved to dev server URLs during development, improving hot-reloading functionality. * fix: normalize RSC URL path by absorbing leading/trailing slashes * specify Shakapacker as top-level module * add tests for RSCClientRoot * Make RSCClientRoot tests run with react 18 * Update webpack asset path configuration for client manifest --------- Co-authored-by: Judah Meek <[email protected]>
1 parent 94131d9 commit ba38c98

29 files changed

+893
-109
lines changed

.github/workflows/package-js-tests.yml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,3 +43,6 @@ jobs:
4343
sudo yarn global add yalc
4444
- name: Run JS unit tests for Renderer package
4545
run: yarn test
46+
# TODO: Remove this once we made these tests compatible with React 19
47+
- name: Run JS unit tests for Renderer package with React 18 (for tests not compatible with React 19)
48+
run: yarn test:react-18

Gemfile.lock

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -385,11 +385,6 @@ GEM
385385
nokogiri (~> 1.6)
386386
rubyzip (>= 1.3.0)
387387
selenium-webdriver (~> 4.0, < 4.11)
388-
webpacker (6.0.0.rc.6)
389-
activesupport (>= 5.2)
390-
rack-proxy (>= 0.6.1)
391-
railties (>= 5.2)
392-
semantic_range (>= 2.3.0)
393388
webrick (1.8.1)
394389
websocket (1.2.10)
395390
websocket-driver (0.7.6)
@@ -444,7 +439,6 @@ DEPENDENCIES
444439
turbolinks
445440
uglifier
446441
webdrivers (= 5.3.0)
447-
webpacker (= 6.0.0.rc.6)
448442

449443
BUNDLED WITH
450444
2.5.9

jest.config.js

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,4 +2,15 @@ module.exports = {
22
preset: 'ts-jest/presets/js-with-ts',
33
testEnvironment: 'jsdom',
44
setupFiles: ['<rootDir>/node_package/tests/jest.setup.js'],
5+
// TODO: Remove this once we made RSCClientRoot compatible with React 19
6+
moduleNameMapper: process.env.USE_REACT_18
7+
? {
8+
'^react$': '<rootDir>/node_modules/react-18',
9+
'^react/(.*)$': '<rootDir>/node_modules/react-18/$1',
10+
'^react-dom$': '<rootDir>/node_modules/react-dom-18',
11+
'^react-dom/(.*)$': '<rootDir>/node_modules/react-dom-18/$1',
12+
}
13+
: {
14+
'react-server-dom-webpack/client': '<rootDir>/node_package/tests/emptyForTesting.js',
15+
},
516
};

knip.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,10 @@ const config: KnipConfig = {
3232
'eslint-plugin-jsx-a11y',
3333
'eslint-plugin-react',
3434
'react-server-dom-webpack',
35+
'cross-fetch',
36+
'jsdom',
37+
'react-18',
38+
'react-dom-18',
3539
],
3640
},
3741
'spec/dummy': {

lib/react_on_rails/configuration.rb

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ def self.configure
99
end
1010

1111
DEFAULT_GENERATED_ASSETS_DIR = File.join(%w[public webpack], Rails.env).freeze
12+
DEFAULT_REACT_CLIENT_MANIFEST_FILE = "react-client-manifest.json"
1213

1314
def self.configuration
1415
@configuration ||= Configuration.new(
@@ -18,6 +19,7 @@ def self.configuration
1819
generated_assets_dir: "",
1920
server_bundle_js_file: "",
2021
rsc_bundle_js_file: "",
22+
react_client_manifest_file: DEFAULT_REACT_CLIENT_MANIFEST_FILE,
2123
prerender: false,
2224
auto_load_bundle: false,
2325
replay_console: true,
@@ -57,8 +59,8 @@ class Configuration
5759
:server_render_method, :random_dom_id, :auto_load_bundle,
5860
:same_bundle_for_client_and_server, :rendering_props_extension,
5961
:make_generated_server_bundle_the_entrypoint,
60-
:defer_generated_component_packs, :rsc_bundle_js_file,
61-
:force_load
62+
:defer_generated_component_packs, :force_load, :rsc_bundle_js_file,
63+
:react_client_manifest_file
6264

6365
# rubocop:disable Metrics/AbcSize
6466
def initialize(node_modules_location: nil, server_bundle_js_file: nil, prerender: nil,
@@ -74,7 +76,7 @@ def initialize(node_modules_location: nil, server_bundle_js_file: nil, prerender
7476
i18n_dir: nil, i18n_yml_dir: nil, i18n_output_format: nil, i18n_yml_safe_load_options: nil,
7577
random_dom_id: nil, server_render_method: nil, rendering_props_extension: nil,
7678
components_subdirectory: nil, auto_load_bundle: nil, force_load: nil,
77-
rsc_bundle_js_file: nil)
79+
rsc_bundle_js_file: nil, react_client_manifest_file: nil)
7880
self.node_modules_location = node_modules_location.present? ? node_modules_location : Rails.root
7981
self.generated_assets_dirs = generated_assets_dirs
8082
self.generated_assets_dir = generated_assets_dir
@@ -102,6 +104,7 @@ def initialize(node_modules_location: nil, server_bundle_js_file: nil, prerender
102104
# Server rendering:
103105
self.server_bundle_js_file = server_bundle_js_file
104106
self.rsc_bundle_js_file = rsc_bundle_js_file
107+
self.react_client_manifest_file = react_client_manifest_file
105108
self.same_bundle_for_client_and_server = same_bundle_for_client_and_server
106109
self.server_renderer_pool_size = self.development_mode ? 1 : server_renderer_pool_size
107110
self.server_renderer_timeout = server_renderer_timeout # seconds
@@ -247,6 +250,8 @@ def ensure_webpack_generated_files_exists
247250
files = ["manifest.json"]
248251
files << server_bundle_js_file if server_bundle_js_file.present?
249252
files << rsc_bundle_js_file if rsc_bundle_js_file.present?
253+
files << react_client_manifest_file if react_client_manifest_file.present?
254+
250255
self.webpack_generated_files = files
251256
end
252257

lib/react_on_rails/helper.rb

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -436,7 +436,7 @@ def internal_rsc_react_component(react_component_name, options = {})
436436
render_options = create_render_options(react_component_name, options)
437437
json_stream = server_rendered_react_component(render_options)
438438
json_stream.transform do |chunk|
439-
chunk[:html].html_safe
439+
"#{chunk.to_json}\n".html_safe
440440
end
441441
end
442442

@@ -691,10 +691,7 @@ def server_rendered_react_component(render_options) # rubocop:disable Metrics/Cy
691691
js_code: js_code)
692692
end
693693

694-
# TODO: handle errors for rsc streams
695-
return result if render_options.rsc?
696-
697-
if render_options.stream?
694+
if render_options.stream? || render_options.rsc?
698695
result.transform do |chunk_json_result|
699696
if should_raise_streaming_prerender_error?(chunk_json_result, render_options)
700697
raise_prerender_error(chunk_json_result, react_component_name, props, js_code)

lib/react_on_rails/packer_utils.rb

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,10 @@ def self.dev_server_running?
4545
packer.dev_server.running?
4646
end
4747

48+
def self.dev_server_url
49+
"#{packer.dev_server.protocol}://#{packer.dev_server.host_with_port}"
50+
end
51+
4852
def self.shakapacker_version
4953
return @shakapacker_version if defined?(@shakapacker_version)
5054
return nil unless ReactOnRails::Utils.gem_available?("shakapacker")
@@ -79,12 +83,27 @@ def self.bundle_js_uri_from_packer(bundle_name)
7983

8084
if packer.dev_server.running? && (!is_bundle_running_on_server ||
8185
ReactOnRails.configuration.same_bundle_for_client_and_server)
82-
"#{packer.dev_server.protocol}://#{packer.dev_server.host_with_port}#{hashed_bundle_name}"
86+
"#{dev_server_url}#{hashed_bundle_name}"
8387
else
8488
File.expand_path(File.join("public", hashed_bundle_name)).to_s
8589
end
8690
end
8791

92+
def self.public_output_uri_path
93+
"#{packer.config.public_output_path.relative_path_from(packer.config.public_path)}/"
94+
end
95+
96+
# The function doesn't ensure that the asset exists.
97+
# - It just returns url to the asset if dev server is running
98+
# - Otherwise it returns file path to the asset
99+
def self.asset_uri_from_packer(asset_name)
100+
if dev_server_running?
101+
"#{dev_server_url}/#{public_output_uri_path}#{asset_name}"
102+
else
103+
File.join(packer_public_output_path, asset_name).to_s
104+
end
105+
end
106+
88107
def self.precompile?
89108
return ::Webpacker.config.webpacker_precompile? if using_webpacker_const?
90109
return ::Shakapacker.config.shakapacker_precompile? if using_shakapacker_const?

lib/react_on_rails/server_rendering_pool/ruby_embedded_java_script.rb

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -234,8 +234,6 @@ def file_url_to_string(url)
234234
end
235235

236236
def parse_result_and_replay_console_messages(result_string, render_options)
237-
return { html: result_string } if render_options.rsc?
238-
239237
result = nil
240238
begin
241239
result = JSON.parse(result_string)

lib/react_on_rails/test_helper/webpack_assets_status_checker.rb

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -50,8 +50,8 @@ def stale_generated_files(files)
5050
def all_compiled_assets
5151
@all_compiled_assets ||= begin
5252
webpack_generated_files = @webpack_generated_files.map do |bundle_name|
53-
if bundle_name == ReactOnRails.configuration.server_bundle_js_file
54-
ReactOnRails::Utils.server_bundle_js_file_path
53+
if bundle_name == ReactOnRails.configuration.react_client_manifest_file
54+
ReactOnRails::Utils.react_client_manifest_file_path
5555
else
5656
ReactOnRails::Utils.bundle_js_file_path(bundle_name)
5757
end

lib/react_on_rails/utils.rb

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,17 @@ def self.rsc_bundle_js_file_path
109109
@rsc_bundle_path = bundle_js_file_path(bundle_name)
110110
end
111111

112+
def self.react_client_manifest_file_path
113+
return @react_client_manifest_path if @react_client_manifest_path && !Rails.env.development?
114+
115+
file_name = ReactOnRails.configuration.react_client_manifest_file
116+
@react_client_manifest_path = if ReactOnRails::PackerUtils.using_packer?
117+
ReactOnRails::PackerUtils.asset_uri_from_packer(file_name)
118+
else
119+
File.join(generated_assets_full_path, file_name)
120+
end
121+
end
122+
112123
def self.running_on_windows?
113124
(/cygwin|mswin|mingw|bccwin|wince|emx/ =~ RUBY_PLATFORM) != nil
114125
end

0 commit comments

Comments
 (0)