From 1fd1c3c7188ffb2439ebf5b3079353254fd5ced3 Mon Sep 17 00:00:00 2001 From: Abanoub Ghadban Date: Thu, 12 Dec 2024 13:10:38 +0200 Subject: [PATCH] make path to rsc bundel and react client manifest configurable --- lib/react_on_rails/configuration.rb | 12 +++++++++--- lib/react_on_rails/utils.rb | 7 +++++++ node_package/src/ReactOnRailsRSC.ts | 14 ++++++-------- node_package/src/loadReactClientManifest.ts | 17 +++++++++++++++++ node_package/src/serverRenderReactComponent.ts | 10 +++++----- node_package/src/types/index.ts | 8 ++++++-- 6 files changed, 50 insertions(+), 18 deletions(-) create mode 100644 node_package/src/loadReactClientManifest.ts diff --git a/lib/react_on_rails/configuration.rb b/lib/react_on_rails/configuration.rb index 1a14cfb8f..3bbb65cf1 100644 --- a/lib/react_on_rails/configuration.rb +++ b/lib/react_on_rails/configuration.rb @@ -10,6 +10,7 @@ def self.configure DEFAULT_GENERATED_ASSETS_DIR = File.join(%w[public webpack], Rails.env).freeze DEFAULT_RSC_RENDERING_URL = "rsc/".freeze + DEFAULT_REACT_CLIENT_MANIFEST_FILE = "react-client-manifest.json".freeze def self.configuration @configuration ||= Configuration.new( @@ -19,6 +20,7 @@ def self.configuration generated_assets_dir: "", server_bundle_js_file: "", rsc_bundle_js_file: "", + react_client_manifest_file: DEFAULT_REACT_CLIENT_MANIFEST_FILE, prerender: false, auto_load_bundle: false, replay_console: true, @@ -59,8 +61,8 @@ class Configuration :server_render_method, :random_dom_id, :auto_load_bundle, :same_bundle_for_client_and_server, :rendering_props_extension, :make_generated_server_bundle_the_entrypoint, - :defer_generated_component_packs, :rsc_bundle_js_file, - :force_load, :auto_load_server_components, :rsc_rendering_url + :defer_generated_component_packs, :force_load, :auto_load_server_components, + :rsc_rendering_url, :rsc_bundle_js_file, :react_client_manifest_file # rubocop:disable Metrics/AbcSize def initialize(node_modules_location: nil, server_bundle_js_file: nil, prerender: nil, @@ -76,7 +78,8 @@ def initialize(node_modules_location: nil, server_bundle_js_file: nil, prerender i18n_dir: nil, i18n_yml_dir: nil, i18n_output_format: nil, random_dom_id: nil, server_render_method: nil, rendering_props_extension: nil, components_subdirectory: nil, auto_load_bundle: nil, force_load: nil, - rsc_bundle_js_file: nil, auto_load_server_components: nil, rsc_rendering_url: nil) + auto_load_server_components: nil, rsc_rendering_url: nil, rsc_bundle_js_file: nil, + react_client_manifest_file: nil) self.node_modules_location = node_modules_location.present? ? node_modules_location : Rails.root self.generated_assets_dirs = generated_assets_dirs self.generated_assets_dir = generated_assets_dir @@ -103,6 +106,7 @@ def initialize(node_modules_location: nil, server_bundle_js_file: nil, prerender # Server rendering: self.server_bundle_js_file = server_bundle_js_file self.rsc_bundle_js_file = rsc_bundle_js_file + self.react_client_manifest_file = react_client_manifest_file self.same_bundle_for_client_and_server = same_bundle_for_client_and_server self.server_renderer_pool_size = self.development_mode ? 1 : server_renderer_pool_size self.server_renderer_timeout = server_renderer_timeout # seconds @@ -250,6 +254,8 @@ def ensure_webpack_generated_files_exists files = ["manifest.json"] files << server_bundle_js_file if server_bundle_js_file.present? files << rsc_bundle_js_file if rsc_bundle_js_file.present? + files << react_client_manifest_file if react_client_manifest_file.present? + self.webpack_generated_files = files end diff --git a/lib/react_on_rails/utils.rb b/lib/react_on_rails/utils.rb index 6835aff2a..430c10a3d 100644 --- a/lib/react_on_rails/utils.rb +++ b/lib/react_on_rails/utils.rb @@ -109,6 +109,13 @@ def self.rsc_bundle_js_file_path @rsc_bundle_path = bundle_js_file_path(bundle_name) end + def self.react_client_manifest_file_path + return @react_client_manifest_path if @react_client_manifest_path && !Rails.env.development? + + file_name = ReactOnRails.configuration.react_client_manifest_file + @react_client_manifest_path = bundle_js_file_path(file_name) + end + def self.running_on_windows? (/cygwin|mswin|mingw|bccwin|wince|emx/ =~ RUBY_PLATFORM) != nil end diff --git a/node_package/src/ReactOnRailsRSC.ts b/node_package/src/ReactOnRailsRSC.ts index bf53a7648..23b9b55bf 100644 --- a/node_package/src/ReactOnRailsRSC.ts +++ b/node_package/src/ReactOnRailsRSC.ts @@ -1,9 +1,8 @@ import { renderToPipeableStream } from 'react-server-dom-webpack/server.node'; import { PassThrough, Readable } from 'stream'; import type { ReactElement } from 'react'; -import fs from 'fs'; -import { RenderParams } from './types'; +import { RSCRenderParams } from './types'; import ReactOnRails from './ReactOnRails'; import buildConsoleReplay from './buildConsoleReplay'; import handleError from './handleError'; @@ -14,6 +13,7 @@ import { convertToError, createResultObject, } from './serverRenderReactComponent'; +import loadReactClientManifest from './loadReactClientManifest'; const stringToStream = (str: string) => { const stream = new PassThrough(); @@ -22,10 +22,8 @@ const stringToStream = (str: string) => { return stream; }; -const getBundleConfig = () => JSON.parse(fs.readFileSync('./public/webpack/development/react-client-manifest.json', 'utf8')) - -const streamRenderRSCComponent = (reactElement: ReactElement, options: RenderParams): Readable => { - const { throwJsErrors } = options; +const streamRenderRSCComponent = (reactElement: ReactElement, options: RSCRenderParams): Readable => { + const { throwJsErrors, reactClientManifestFileName } = options; const renderState: StreamRenderState = { result: null, hasErrors: false, @@ -36,7 +34,7 @@ const streamRenderRSCComponent = (reactElement: ReactElement, options: RenderPar try { const rscStream = renderToPipeableStream( reactElement, - getBundleConfig(), + loadReactClientManifest(reactClientManifestFileName), { onError: (err) => { const error = convertToError(err); @@ -61,7 +59,7 @@ const streamRenderRSCComponent = (reactElement: ReactElement, options: RenderPar } }; -ReactOnRails.serverRenderRSCReactComponent = (options: RenderParams) => { +ReactOnRails.serverRenderRSCReactComponent = (options: RSCRenderParams) => { try { return streamServerRenderedComponent(options, streamRenderRSCComponent); } finally { diff --git a/node_package/src/loadReactClientManifest.ts b/node_package/src/loadReactClientManifest.ts new file mode 100644 index 000000000..e79181912 --- /dev/null +++ b/node_package/src/loadReactClientManifest.ts @@ -0,0 +1,17 @@ +import path from 'path'; +import fs from 'fs'; + +const loadedReactClientManifests = new Map(); + +export default function loadReactClientManifest(reactClientManifestFileName: string) { + // React client manifest is uploaded to node renderer as an asset. + // Renderer copies assets to the same place as the server-bundle.js and rsc-bundle.js. + // Thus, the __dirname of this code is where we can find the manifest file. + const manifestPath = path.resolve(__dirname, reactClientManifestFileName); + if (!loadedReactClientManifests.has(manifestPath)) { + const manifest = JSON.parse(fs.readFileSync(manifestPath, 'utf8')); + loadedReactClientManifests.set(manifestPath, manifest); + } + + return loadedReactClientManifests.get(manifestPath)!; +} diff --git a/node_package/src/serverRenderReactComponent.ts b/node_package/src/serverRenderReactComponent.ts index 401095545..3fd0a9f4b 100644 --- a/node_package/src/serverRenderReactComponent.ts +++ b/node_package/src/serverRenderReactComponent.ts @@ -298,14 +298,14 @@ const streamRenderReactComponent = (reactRenderingResult: ReactElement, options: return readableStream; } -export type StreamRenderer = ( +export type StreamRenderer = ( reactElement: ReactElement, - options: RenderParams + options: P ) => T; -export const streamServerRenderedComponent = ( - options: RenderParams, - renderStrategy: StreamRenderer +export const streamServerRenderedComponent = ( + options: P, + renderStrategy: StreamRenderer ): T => { const { name: componentName, domNodeId, trace, props, railsContext, throwJsErrors } = options; diff --git a/node_package/src/types/index.ts b/node_package/src/types/index.ts index 40e529193..5f0cb665d 100644 --- a/node_package/src/types/index.ts +++ b/node_package/src/types/index.ts @@ -2,7 +2,7 @@ /// import type { ReactElement, ReactNode, Component, ComponentType } from 'react'; -import type { Readable, PassThrough } from 'stream'; +import type { Readable } from 'stream'; // Don't import redux just for the type definitions // See https://github.com/shakacode/react_on_rails/issues/1321 @@ -122,6 +122,10 @@ export interface RenderParams extends Params { renderingReturnsPromises: boolean; } +export interface RSCRenderParams extends RenderParams { + reactClientManifestFileName: string; +} + export interface CreateParams extends Params { componentObj: RegisteredComponent; shouldHydrate?: boolean; @@ -181,7 +185,7 @@ export interface ReactOnRails { getOrWaitForComponent(name: string): Promise; serverRenderReactComponent(options: RenderParams): null | string | Promise; streamServerRenderedReactComponent(options: RenderParams): Readable; - serverRenderRSCReactComponent(options: RenderParams): Readable; + serverRenderRSCReactComponent(options: RSCRenderParams): Readable; handleError(options: ErrorOptions): string | undefined; buildConsoleReplay(): string; registeredComponents(): Map;