Skip to content

Commit

Permalink
tmp
Browse files Browse the repository at this point in the history
  • Loading branch information
AbanoubGhadban committed Jul 22, 2024
1 parent ed92489 commit 4e3ad5b
Show file tree
Hide file tree
Showing 8 changed files with 187 additions and 39 deletions.
18 changes: 17 additions & 1 deletion lib/react_on_rails/helper.rb
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,15 @@ def react_component(component_name, options = {})
end
end

def rsc_react_component(component_name, options = {})
res = internal_rsc_react_component(component_name, options)
s = ""
res.each_chunk do |chunk|
s += chunk
end
s
end

def stream_react_component(component_name, options = {})
options = options.merge(stream?: true)
result = internal_react_component(component_name, options)
Expand Down Expand Up @@ -468,6 +477,13 @@ def prepend_render_rails_context(render_value)
"#{rails_context_if_not_already_rendered}\n#{render_value}".html_safe
end

def internal_rsc_react_component(react_component_name, options = {})
options = options.merge(rsc?: true)
render_options = ReactOnRails::ReactComponent::RenderOptions.new(react_component_name: react_component_name,
options: options)
server_rendered_react_component(render_options)
end

def internal_react_component(react_component_name, options = {})
# Create the JavaScript and HTML to allow either client or server rendering of the
# react_component.
Expand Down Expand Up @@ -561,7 +577,7 @@ def server_rendered_react_component(render_options)
end

# TODO: handle errors for streams
return result if render_options.stream?
return result if render_options.stream? || render_options.rsc?

if result["hasErrors"] && render_options.raise_on_prerender_error
# We caught this exception on our backtrace handler
Expand Down
4 changes: 4 additions & 0 deletions lib/react_on_rails/react_component/render_options.rb
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,10 @@ def stream?
options[:stream?]
end

def rsc?
options[:rsc?]
end

private

attr_reader :options
Expand Down
45 changes: 29 additions & 16 deletions lib/react_on_rails/utils.rb
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ def self.server_bundle_path_is_http?
server_bundle_js_file_path =~ %r{https?://}
end

def self.server_bundle_js_file_path
def self.bundle_js_file_path(bundle_name)
# Either:
# 1. Using same bundle for both server and client, so server bundle will be hashed in manifest
# 2. Using a different bundle (different Webpack config), so file is not hashed, and
Expand All @@ -76,32 +76,45 @@ def self.server_bundle_js_file_path
# a. The webpack manifest plugin would have a race condition where the same manifest.json
# is edited by both the webpack-dev-server
# b. There is no good reason to hash the server bundle name.
return @server_bundle_path if @server_bundle_path && !Rails.env.development?

bundle_name = ReactOnRails.configuration.server_bundle_js_file
@server_bundle_path = if ReactOnRails::WebpackerUtils.using_webpacker?
@server_bundle_path = if ReactOnRails::WebpackerUtils.using_webpacker? && bundle_name != "manifest.json"
begin
bundle_js_file_path(bundle_name)
ReactOnRails::WebpackerUtils.bundle_js_uri_from_webpacker(bundle_name)
rescue Webpacker::Manifest::MissingEntryError
File.expand_path(
File.join(ReactOnRails::WebpackerUtils.webpacker_public_output_path,
bundle_name)
)
end
else
bundle_js_file_path(bundle_name)
# Default to the non-hashed name in the specified output directory, which, for legacy
# React on Rails, this is the output directory picked up by the asset pipeline.
# For Webpacker, this is the public output path defined in the webpacker.yml file.
File.join(generated_assets_full_path, bundle_name)
end
end

def self.bundle_js_file_path(bundle_name)
if ReactOnRails::WebpackerUtils.using_webpacker? && bundle_name != "manifest.json"
ReactOnRails::WebpackerUtils.bundle_js_uri_from_webpacker(bundle_name)
else
# Default to the non-hashed name in the specified output directory, which, for legacy
# React on Rails, this is the output directory picked up by the asset pipeline.
# For Webpacker, this is the public output path defined in the webpacker.yml file.
File.join(generated_assets_full_path, bundle_name)
end
def self.server_bundle_js_file_path
# Either:
# 1. Using same bundle for both server and client, so server bundle will be hashed in manifest
# 2. Using a different bundle (different Webpack config), so file is not hashed, and
# bundle_js_path will throw so the default path is used without a hash.
# 3. The third option of having the server bundle hashed and a different configuration than
# the client bundle is not supported for 2 reasons:
# a. The webpack manifest plugin would have a race condition where the same manifest.json
# is edited by both the webpack-dev-server
# b. There is no good reason to hash the server bundle name.
return @server_bundle_path if @server_bundle_path && !Rails.env.development?

bundle_name = ReactOnRails.configuration.server_bundle_js_file
@server_bundle_path = bundle_js_file_path(bundle_name)
end

def self.rsc_bundle_js_file_path
return @rsc_bundle_path if @rsc_bundle_path && !Rails.env.development?

# TODO: make it configurable
bundle_name = "rsc-bundle.js"
@server_bundle_path = bundle_js_file_path(bundle_name)
end

def self.running_on_windows?
Expand Down
9 changes: 9 additions & 0 deletions node_package/src/ReactOnRails.ts
Original file line number Diff line number Diff line change
Expand Up @@ -250,6 +250,15 @@ ctx.ReactOnRails = {
return streamServerRenderedReactComponent(options);
},

/**
* Used by server rendering by Rails
* @param options
*/
// eslint-disable-next-line @typescript-eslint/no-unused-vars
serverRenderRSCReactComponent(options: RenderParams): PassThrough {
throw new Error('serverRenderRSCReactComponent is supported in RSC bundle only.');
},

/**
* Used by Rails to catch errors in rendering
* @param options
Expand Down
79 changes: 79 additions & 0 deletions node_package/src/ReactOnRailsRSC.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
import type { ReactElement } from 'react';
// @ts-expect-error will define this module types later
import { renderToReadableStream } from 'react-server-dom-webpack/server.edge';
import { PassThrough } from 'stream';

import { RenderParams } from './types';
import ComponentRegistry from './ComponentRegistry';
import createReactOutput from './createReactOutput';
import { isPromise, isServerRenderHash } from './isServerRenderResult';
import handleError from './handleError';
import ReactOnRails from './ReactOnRails';

(async () => {
try {
// @ts-expect-error AsyncLocalStorage is not in the node types
globalThis.AsyncLocalStorage = (await import('node:async_hooks')).AsyncLocalStorage;
} catch (e) {
console.log('AsyncLocalStorage not found');
}
})();

const stringToStream = (str: string) => {
const stream = new PassThrough();
stream.push(str);
stream.push(null);
return stream;
};

ReactOnRails.serverRenderRSCReactComponent = (options: RenderParams) => {
const { name, domNodeId, trace, props, railsContext, throwJsErrors } = options;

let renderResult: null | PassThrough = null;

try {
const componentObj = ComponentRegistry.get(name);
if (componentObj.isRenderer) {
throw new Error(`\
Detected a renderer while server rendering component '${name}'. \
See https://github.com/shakacode/react_on_rails#renderer-functions`);
}

const reactRenderingResult = createReactOutput({
componentObj,
domNodeId,
trace,
props,
railsContext,
});

if (isServerRenderHash(reactRenderingResult) || isPromise(reactRenderingResult)) {
throw new Error('Server rendering of streams is not supported for server render hashes or promises.');
}

renderResult = new PassThrough();
const streamReader = renderToReadableStream(reactRenderingResult as ReactElement).getReader();
const processStream = async () => {
const { done, value } = await streamReader.read();
if (done) {
renderResult?.push(null);
return;
}

renderResult?.push(value);
processStream();
}
processStream();
} catch (e: unknown) {
if (throwJsErrors) {
throw e;
}

renderResult = stringToStream(`Error: ${e}`);
}

return renderResult;
};

export * from './types';
export default ReactOnRails;
1 change: 1 addition & 0 deletions node_package/src/types/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,7 @@ export interface ReactOnRails {
getComponent(name: string): RegisteredComponent;
serverRenderReactComponent(options: RenderParams): null | string | Promise<RenderResult>;
streamServerRenderedReactComponent(options: RenderParams): PassThrough;
serverRenderRSCReactComponent(options: RenderParams): PassThrough;
handleError(options: ErrorOptions): string | undefined;
buildConsoleReplay(): string;
registeredComponents(): Map<string, RegisteredComponent>;
Expand Down
12 changes: 9 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,12 @@
"name": "react-on-rails",
"version": "14.0.1",
"description": "react-on-rails JavaScript for react_on_rails Ruby gem",
"main": "node_package/lib/ReactOnRails.js",
"exports": {
".": {
"rsc-server": "./node_package/lib/ReactOnRailsRSC.js",
"default": "./node_package/lib/ReactOnRails.js"
}
},
"directories": {
"doc": "docs"
},
Expand Down Expand Up @@ -39,8 +44,9 @@
"prettier": "^2.8.8",
"prettier-eslint-cli": "^5.0.0",
"prop-types": "^15.8.1",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react": "18.3.0-canary-670811593-20240322",
"react-dom": "18.3.0-canary-670811593-20240322",
"react-server-dom-webpack": "18.3.0-canary-670811593-20240322",
"react-transform-hmr": "^1.0.4",
"redux": "^4.2.1",
"ts-jest": "^29.1.0",
Expand Down
58 changes: 39 additions & 19 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -1972,6 +1972,13 @@ acorn-jsx@^5.3.1:
resolved "https://registry.yarnpkg.com/acorn-jsx/-/acorn-jsx-5.3.1.tgz#fc8661e11b7ac1539c47dbfea2e72b3af34d267b"
integrity sha512-K0Ptm/47OKfQRpNQ2J/oIN/3QYiK6FwW+eJbILhsdxh2WTLdl+30o8aGdTbm5JbffpFFAg/g+zi1E+jvJha5ng==

acorn-loose@^8.3.0:
version "8.4.0"
resolved "https://registry.yarnpkg.com/acorn-loose/-/acorn-loose-8.4.0.tgz#26d3e219756d1e180d006f5bcc8d261a28530f55"
integrity sha512-M0EUka6rb+QC4l9Z3T0nJEzNOO7JcoJlYMrBlyBCiFSXRyxjLKayd4TbQs2FDRWQU1h9FR7QVNHt+PEaoNL5rQ==
dependencies:
acorn "^8.11.0"

acorn-walk@^8.0.2:
version "8.3.1"
resolved "https://registry.yarnpkg.com/acorn-walk/-/acorn-walk-8.3.1.tgz#2f10f5b69329d90ae18c58bf1fa8fccd8b959a43"
Expand Down Expand Up @@ -2002,6 +2009,11 @@ acorn@^8.1.0, acorn@^8.8.1:
resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.11.3.tgz#71e0b14e13a4ec160724b38fb7b0f233b1b81d7a"
integrity sha512-Y9rRfJG5jcKOE0CLisYbojUjIrIEE7AGMzA/Sm4BslANhbS+cDMpgBdcPT91oJ7OuJ9hYJBx59RjbhxVnrF8Xg==

acorn@^8.11.0:
version "8.12.1"
resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.12.1.tgz#71616bdccbe25e27a54439e0046e89ca76df2248"
integrity sha512-tcpGyI9zbizT9JbV6oYE477V6mTlXvvi0T0G3SNIYE2apm/G5huBa1+K89VGeovbg+jycCrfhl3ADxErOuO6Jg==

agent-base@6:
version "6.0.2"
resolved "https://registry.yarnpkg.com/agent-base/-/agent-base-6.0.2.tgz#49fff58577cfee3f37176feab4c22e00f86d7f77"
Expand Down Expand Up @@ -5155,7 +5167,7 @@ loglevel@^1.4.1:
resolved "https://registry.yarnpkg.com/loglevel/-/loglevel-1.7.1.tgz#005fde2f5e6e47068f935ff28573e125ef72f197"
integrity sha512-Hesni4s5UkWkwCGJMQGAh71PaLUmKFM60dHvq0zi/vDhhrzuk+4GgNbTXJ12YYQJn6ZKBDNIjYcuQGKudvqrIw==

loose-envify@^1.1.0, loose-envify@^1.3.1, loose-envify@^1.4.0:
loose-envify@^1.3.1, loose-envify@^1.4.0:
version "1.4.0"
resolved "https://registry.yarnpkg.com/loose-envify/-/loose-envify-1.4.0.tgz#71ee51fa7be4caec1a63839f7e682d8132d30caf"
integrity sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==
Expand Down Expand Up @@ -5337,6 +5349,11 @@ natural-compare@^1.4.0:
resolved "https://registry.yarnpkg.com/natural-compare/-/natural-compare-1.4.0.tgz#4abebfeed7541f2c27acfb29bdbbd15c8d5ba4f7"
integrity sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc=

neo-async@^2.6.1:
version "2.6.2"
resolved "https://registry.yarnpkg.com/neo-async/-/neo-async-2.6.2.tgz#b4aafb93e3aeb2d8174ca53cf163ab7d7308305f"
integrity sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==

nice-try@^1.0.4:
version "1.0.5"
resolved "https://registry.yarnpkg.com/nice-try/-/nice-try-1.0.5.tgz#a3378a7696ce7d223e88fc9b764bd7ef1089e366"
Expand Down Expand Up @@ -5877,13 +5894,12 @@ react-deep-force-update@^1.0.0:
resolved "https://registry.yarnpkg.com/react-deep-force-update/-/react-deep-force-update-1.1.2.tgz#3d2ae45c2c9040cbb1772be52f8ea1ade6ca2ee1"
integrity sha512-WUSQJ4P/wWcusaH+zZmbECOk7H5N2pOIl0vzheeornkIMhu+qrNdGFm0bDZLCb0hSF0jf/kH1SgkNGfBdTc4wA==

react-dom@^18.2.0:
version "18.3.1"
resolved "https://registry.yarnpkg.com/react-dom/-/react-dom-18.3.1.tgz#c2265d79511b57d479b3dd3fdfa51536494c5cb4"
integrity sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==
react-dom@18.3.0-canary-670811593-20240322:
version "18.3.0-canary-670811593-20240322"
resolved "https://registry.yarnpkg.com/react-dom/-/react-dom-18.3.0-canary-670811593-20240322.tgz#ac677b164fd83050272bf985e740ed4ca65337be"
integrity sha512-AHxCnyDzZueXIHY4WA2Uba1yaL7/vbjhO3D3TWPQeruKD5MwgD0/xExZi0T104gBr6Thv6MEsLSxFjBAHhHKKg==
dependencies:
loose-envify "^1.1.0"
scheduler "^0.23.2"
scheduler "0.24.0-canary-670811593-20240322"

react-is@^16.13.1:
version "16.13.1"
Expand All @@ -5903,6 +5919,14 @@ react-proxy@^1.1.7:
lodash "^4.6.1"
react-deep-force-update "^1.0.0"

[email protected]:
version "18.3.0-canary-670811593-20240322"
resolved "https://registry.yarnpkg.com/react-server-dom-webpack/-/react-server-dom-webpack-18.3.0-canary-670811593-20240322.tgz#e9b99b1f0179357e5acbf2fbacaee88dd1e8bf3b"
integrity sha512-YaCk3AvvOXcOo0FL7SlAY2GVBeuZKFQ/5FfAtE48IjpI6MvXTwMBu3QVnT/Ukk9Y4M9GzpIbLtuc8hPjfFAOaw==
dependencies:
acorn-loose "^8.3.0"
neo-async "^2.6.1"

react-transform-hmr@^1.0.4:
version "1.0.4"
resolved "https://registry.yarnpkg.com/react-transform-hmr/-/react-transform-hmr-1.0.4.tgz#e1a40bd0aaefc72e8dfd7a7cda09af85066397bb"
Expand All @@ -5911,12 +5935,10 @@ react-transform-hmr@^1.0.4:
global "^4.3.0"
react-proxy "^1.1.7"

react@^18.2.0:
version "18.3.1"
resolved "https://registry.yarnpkg.com/react/-/react-18.3.1.tgz#49ab892009c53933625bd16b2533fc754cab2891"
integrity sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==
dependencies:
loose-envify "^1.1.0"
[email protected]:
version "18.3.0-canary-670811593-20240322"
resolved "https://registry.yarnpkg.com/react/-/react-18.3.0-canary-670811593-20240322.tgz#3735250b45468d313ed36121324452bb5a732e9b"
integrity sha512-EI6+q3tOT+0z4OkB2sz842Ra/n/yz7b3jOJhSK1HQwi4Ng29VJzLGngWmSuxQ94YfdE3EBhpUKDfgNgzoKM9Vg==

readdirp@~3.6.0:
version "3.6.0"
Expand Down Expand Up @@ -6213,12 +6235,10 @@ saxes@^6.0.0:
dependencies:
xmlchars "^2.2.0"

scheduler@^0.23.2:
version "0.23.2"
resolved "https://registry.yarnpkg.com/scheduler/-/scheduler-0.23.2.tgz#414ba64a3b282892e944cf2108ecc078d115cdc3"
integrity sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==
dependencies:
loose-envify "^1.1.0"
[email protected]:
version "0.24.0-canary-670811593-20240322"
resolved "https://registry.yarnpkg.com/scheduler/-/scheduler-0.24.0-canary-670811593-20240322.tgz#45c5c45f18a127ab4e3c805dd466bc231b20adf3"
integrity sha512-IGX6Fq969h1L0X7jV0sJ/EdI4fr+mRetbBNJl55nn+/RsCuQSVwgKnZG6Q3NByixDNbkRI8nRmWuhOm8NQowGQ==

[email protected]:
version "5.5.0"
Expand Down

0 comments on commit 4e3ad5b

Please sign in to comment.