Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for console replay while streaming component #1647

Conversation

AbanoubGhadban
Copy link
Collaborator

@AbanoubGhadban AbanoubGhadban commented Aug 25, 2024

Summary

This PR introduces the ability to replay console logs that happen in the react components streamed.

It makes every chunk returned from the server has the following format:

{
  "html": "<div>chunk html</div>",
  "consoleReplayScript": "<script>console.log.apply(console, ['hello world']);</script>"
}

On the ruby side, the console replay script is returned in the HTML chunk to be replayed on the browser.

Description Auto-Generated by code-rabbit

Key Concepts in SSR and Streaming

  1. Server-Side Rendering (SSR):
    SSR generates the HTML for React components on the server and sends it to the client. This allows faster initial rendering on the client side because the HTML is already rendered when the page loads.

  2. Streaming:
    Instead of sending the entire HTML content at once after it has been fully rendered on the server, streaming allows the server to send partial chunks of HTML as they are rendered. This can improve the perceived load time for the client, as the client can begin processing the initial HTML while the server continues rendering the rest.

Streaming in the Code

The parts related to streaming are primarily in the streamServerRenderedReactComponent function and involve streaming the React component's HTML output back to the client.

streamServerRenderedReactComponent

export const streamServerRenderedReactComponent = (options: RenderParams): Readable => {
  • This function handles the server-side streaming of a React component's rendered HTML.
  • It takes RenderParams as input, which contains the details of the component to be rendered (name, props, DOM node ID, etc.).

ReactDOMServer.renderToPipeableStream

ReactDOMServer.renderToPipeableStream(reactRenderingResult)
  .pipe(transformStream);
  • ReactDOMServer.renderToPipeableStream is a new React feature introduced in React 18 for streaming SSR.
  • It creates a stream of HTML chunks from the React component as it renders.
  • The pipe method connects this stream to another stream (transformStream), allowing the rendered chunks to be processed and eventually sent to the client.

Transform Stream

const transformStream = new Transform({
  transform(chunk, _, callback) {
    const htmlChunk = chunk.toString();
    const consoleReplayScript = buildConsoleReplay(previouslyReplayedConsoleMessages);
    previouslyReplayedConsoleMessages = console.history?.length || 0;

    const jsonChunk = JSON.stringify({
      html: htmlChunk,
      consoleReplayScript,
    });

    this.push(jsonChunk);
    callback();
  }
});
  • The Transform stream is used to process each chunk of HTML rendered by React.
  • It takes the incoming chunk (HTML), converts it to a string, and appends a script to replay any console messages (consoleReplayScript).
  • After processing the chunk, it pushes the modified chunk (now a JSON string containing the HTML and console messages) to the next stream destination, which could be sent to the client.

Error Handling in Streams

renderResult = stringToStream(handleError({
  e,
  name,
  serverSide: true,
}));
  • If an error occurs during rendering, the handleError function generates an error message in the form of a string.
  • This string is then converted into a stream using stringToStream, ensuring that even errors are streamed back to the client.

stringToStream

const stringToStream = (str: string): Readable => {
  const stream = new PassThrough();
  stream.push(str);
  stream.push(null); // Ends the stream
  return stream;
};
  • stringToStream creates a readable stream from a string.
  • This is useful for sending static error messages or other fixed content back to the client as a stream, ensuring that the streaming approach is maintained even in cases of failure.

Summary of Streaming Flow

  1. The function streamServerRenderedReactComponent gets the component to be rendered and begins the rendering process using ReactDOMServer.renderToPipeableStream.
  2. As React renders the component, it sends chunks of HTML through a stream.
  3. Each chunk is processed by a Transform stream, which appends console logs and serializes the result as JSON.
  4. The transformed HTML and any other required data are streamed back to the client as soon as they are available.
  5. In case of an error during rendering, the error is handled and streamed back using a stringToStream.

This approach allows for efficient server-side rendering with streaming, which can improve performance and user experience by delivering parts of the content to the client as soon as they are ready, without waiting for the entire component to render.

This Ruby file is part of the ReactOnRails gem, which integrates React with Rails and enables server-side rendering (SSR) of React components. The streaming parts of this Ruby file relate closely to the streaming functionality in the TypeScript code you provided. Here's a breakdown of how the Ruby code handles SSR with a focus on streaming:

Key Components of the Ruby File

  • react_component Method:
    This method renders a React component on the server and returns the HTML to be injected into the Rails view. It supports both client-side rendering (CSR) and server-side rendering (SSR).

  • stream_react_component Method:
    This method handles server-side rendering with streaming, which corresponds to the streamServerRenderedReactComponent function in the TypeScript code. Instead of waiting for the entire component to be rendered before sending it to the client, it sends chunks of HTML as they are rendered.

Streaming Logic in Ruby

stream_react_component

def stream_react_component(component_name, options = {})
  rendering_fiber = Fiber.new do
    stream = internal_stream_react_component(component_name, options)
    stream.each_chunk do |chunk|
      Fiber.yield chunk
    end
    Fiber.yield nil
  end

  if @rorp_rendering_fibers.nil?
    raise ReactOnRails::Error, "You must call stream_view_containing_react_components to render the view containing the react component"
  end

  @rorp_rendering_fibers << rendering_fiber

  # return the first chunk of the fiber
  rendering_fiber.resume
end
  • Fiber Usage:
    The stream_react_component method creates a Fiber to control the streaming of HTML chunks. Fibers in Ruby are lightweight, cooperative threads that allow the code to yield and resume execution, which is perfect for handling streaming data in a controlled way.

  • Streaming Chunks:
    The Fiber calls internal_stream_react_component, which returns a stream of chunks of HTML. The Fiber iterates through each chunk and sends it back to the Rails view as it becomes available.

    • The first chunk is rendered immediately and returned to the view (corresponding to the initial part of the React component).
    • Subsequent chunks are sent asynchronously as they are processed and rendered, mimicking the behavior in the TypeScript code where renderToPipeableStream is used.
  • Fiber Management:
    The rendered chunks are managed by storing the rendering Fiber in an instance variable (@rorp_rendering_fibers). The first chunk is sent by calling rendering_fiber.resume.

internal_stream_react_component

def internal_stream_react_component(component_name, options = {})
  options = options.merge(stream?: true)
  result = internal_react_component(component_name, options)
  build_react_component_result_for_server_streamed_content(
    rendered_html_stream: result[:result],
    component_specification_tag: result[:tag],
    render_options: result[:render_options]
  )
end
  • Options Modification:
    This method ensures that the stream? option is set to true, signaling that the component should be streamed rather than fully rendered before being sent to the client.

  • Streamed Content:
    The result of internal_react_component is passed to build_react_component_result_for_server_streamed_content, which prepares the HTML chunks to be streamed back to the client.

build_react_component_result_for_server_streamed_content

def build_react_component_result_for_server_streamed_content(
  rendered_html_stream: required("rendered_html_stream"),
  component_specification_tag: required("component_specification_tag"),
  render_options: required("render_options")
)
  is_first_chunk = true
  rendered_html_stream.transform do |chunk_json_result|
    if is_first_chunk
      is_first_chunk = false
      next build_react_component_result_for_server_rendered_string(
        server_rendered_html: chunk_json_result["html"],
        component_specification_tag: component_specification_tag,
        console_script: chunk_json_result["consoleReplayScript"],
        render_options: render_options
      )
    end

    result_console_script = render_options.replay_console ? chunk_json_result["consoleReplayScript"] : ""
    compose_react_component_html_with_spec_and_console(
      "", chunk_json_result["html"], result_console_script
    )
  end
end
  • Transforming Streamed Chunks:
    This method takes the stream of chunks from the rendered React component and processes them one by one. The first chunk is special because it contains the initial HTML along with the component specification tag (metadata about the component).

  • Subsequent Chunks:
    For subsequent chunks, the HTML is processed and appended to the response, similar to how the Transform stream in the TypeScript code appends the console logs and wraps the HTML in JSON.

  • Console Replay:
    Each chunk includes the option to replay console messages (consoleReplayScript), as in the TypeScript code, where each HTML chunk includes embedded console logs.

Relation to TypeScript Streaming Logic

  • Rendering Flow:
    Both the Ruby and TypeScript code follow a similar flow for streaming:

    • The component is rendered in chunks, with the first chunk being sent immediately, followed by subsequent chunks.
    • Errors during rendering are handled gracefully, with error messages being sent as part of the stream.
  • Chunk Processing:
    The Ruby method build_react_component_result_for_server_streamed_content closely mirrors the functionality of the Transform stream in the TypeScript code. Both process each chunk of HTML, append necessary metadata (like console logs), and send it to the client.

  • Fiber and Stream Handling:
    The use of Fibers in Ruby allows the server to stream the chunks in a non-blocking manner, similar to how the Node.js renderToPipeableStream function allows React to stream chunks without waiting for the entire component to finish rendering.

Summary

The Ruby file is responsible for managing the server-side rendering of React components within a Rails app, including support for streaming the rendered HTML. It parallels the TypeScript streaming logic by using Fibers to control the flow of streamed HTML chunks, ensuring that the client receives content as it becomes available. Both the Ruby and TypeScript implementations focus on improving performance by enabling partial responses to be sent during rendering, reducing the time to the first byte for the client.

Pull Request checklist

Remove this line after checking all the items here. If the item is not applicable to the PR, both check it out and wrap it by ~.

  • Add/update test to cover these changes
  • Update documentation
  • Update CHANGELOG file
    Add the CHANGELOG entry at the top of the file.

Other Information

Remove this paragraph and mention any other important and relevant information such as benchmarks.


This change is Reviewable

Summary by CodeRabbit

Summary by CodeRabbit

  • New Features

    • Introduced streaming capabilities for React components, enhancing server-side rendering performance and debugging.
    • Added methods for replaying console logs during server rendering, allowing better tracking of asynchronous operations.
    • Enhanced ReactOnRails module with a new helper for processing streamed React components.
  • Bug Fixes

    • Improved error handling in server rendering logic for more robust operations.
  • Documentation

    • Updated the changelog to reflect new features, changes, and fixes.
  • Dependency Updates

    • Upgraded core React and React DOM packages to version 18.2.0 for improved features and compatibility.
  • Chores

    • Added new entries to the .gitignore file for cleaner version control, targeting IDE-related files.

Copy link
Contributor

coderabbitai bot commented Aug 25, 2024

Walkthrough

The recent changes encompass updates to several files, enhancing the project's functionality by introducing new methods for streaming React components and improving console log handling during server rendering. Key updates include the addition of the stream_react_component and streamServerRenderedReactComponent methods, modifications to the exec_server_render_js method for better control flow, and enhancements to the console replay system. The changelog has also been updated to reflect these changes, alongside various improvements across the codebase for clarity and maintainability.

Changes

Files Change Summary
CHANGELOG.md Updated to include new features, changes, and fixes, including support for replaying console logs during server rendering.
lib/react_on_rails/helper.rb Simplified parameters in build_react_component_result_for_server_streamed_content and refactored compose_react_component_html_with_spec_and_console.
lib/react_on_rails/server_rendering_pool/ruby_embedded_java_script.rb Restructured exec_server_render_js to conditionally call eval_streaming_js or eval_js based on streaming options; added parse_result_and_replay_console_messages method.
node_package/src/buildConsoleReplay.ts Updated consoleReplay and buildConsoleReplay functions to include numberOfMessagesToSkip parameter for improved console log handling.
node_package/src/serverRenderReactComponent.ts Enhanced streamServerRenderedReactComponent to utilize a Transform stream for processing output and integrated console replay script.

Possibly related PRs

Suggested reviewers

  • alexeyr-ci
  • justin808
  • Judahmeek

🐇 In a world of code so bright,
🐰 Changes hop with pure delight!
New streams flow, the tests now shine,
With every tweak, our work aligns!
So let us cheer, with joy we sing,
For every change, a brighter spring! 🌸


Thank you for using CodeRabbit. We offer it for free to the OSS community and would appreciate your support in helping us grow. If you find it useful, would you consider giving us a shout-out on your favorite social media?

❤️ Share
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Generate unit testing code for this file.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai generate unit testing code for this file.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and generate unit testing code.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

@AbanoubGhadban AbanoubGhadban changed the base branch from master to abanoubghadban/pro403-use-render-to-readable-stream-to-render-async August 25, 2024 18:00
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Caution

Inline review comments failed to post

Actionable comments posted: 5

Outside diff range, codebase verification and nitpick comments (1)
package.json (1)

18-19: Deprecated React API Detected

The codebase contains deprecated React API usage that needs to be addressed for compatibility with React 18:

  • spec/dummy/client/app/startup/HelloWorldES5.jsx: Replace React.createClass with ES6 class components or functional components with hooks.

Please update these instances to ensure compatibility with the latest React version.

Analysis chain

Approve dependency upgrades but verify compatibility.

The upgrades to React and React DOM, along with their type definitions, are essential for staying current with the latest features. However, it's crucial to ensure that these changes are thoroughly tested to avoid potential breaking changes due to the major version upgrade.

The changes to the dependencies are approved.

Run the following script to verify compatibility with the new React version:

Also applies to: 42-43

Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Verify that no deprecated React APIs are used in the codebase.

# Test: Search for deprecated React APIs. Expect: No occurrences.
rg --type javascript --type typescript 'React\.createClass|React\.PropTypes'

Length of output: 113


Script:

#!/bin/bash
# Description: Verify that no deprecated React APIs are used in the codebase.

# Test: Search for deprecated React APIs. Expect: No occurrences.
rg --glob '*.js' --glob '*.jsx' --glob '*.ts' --glob '*.tsx' 'React\.createClass|React\.PropTypes'

Length of output: 215

Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

Commits

Files that changed from the base of the PR and between e81e41c and ce37122.

Files ignored due to path filters (1)
  • yarn.lock is excluded by !**/yarn.lock, !**/*.lock
Files selected for processing (16)
  • .gitignore (1 hunks)
  • jest.config.js (1 hunks)
  • lib/react_on_rails/helper.rb (5 hunks)
  • lib/react_on_rails/react_component/render_options.rb (1 hunks)
  • lib/react_on_rails/server_rendering_pool/ruby_embedded_java_script.rb (3 hunks)
  • node_package/src/ReactOnRails.ts (2 hunks)
  • node_package/src/buildConsoleReplay.ts (2 hunks)
  • node_package/src/serverRenderReactComponent.ts (2 hunks)
  • node_package/src/types/index.ts (2 hunks)
  • node_package/tests/ReactOnRails.test.js (1 hunks)
  • node_package/tests/jest.setup.js (1 hunks)
  • package.json (2 hunks)
  • spec/dummy/config/webpack/alias.js (1 hunks)
  • spec/dummy/config/webpack/commonWebpackConfig.js (1 hunks)
  • spec/dummy/config/webpack/webpackConfig.js (1 hunks)
  • spec/dummy/spec/helpers/react_on_rails_helper_spec.rb (2 hunks)
Files skipped from review due to trivial changes (1)
  • .gitignore
Additional context used
Biome
node_package/src/buildConsoleReplay.ts

[error] 17-17: Use Array.isArray() instead of instanceof Array.

instanceof Array returns false for array-like objects and arrays from other execution contexts.
Unsafe fix: Use Array.isArray() instead.

(lint/suspicious/useIsArray)

rubocop
lib/react_on_rails/server_rendering_pool/ruby_embedded_java_script.rb

[convention] 60-60: Use 2 (not -7) spaces for indentation.

(Layout/IndentationWidth)


[convention] 61-61: Align else with if.

(Layout/ElseAlignment)


[warning] 63-63: end at 63, 12 is not aligned with if at 59, 21.

(Layout/EndAlignment)


[convention] 78-78: Trailing whitespace detected.

(Layout/TrailingWhitespace)

lib/react_on_rails/helper.rb

[warning] 402-402: Useless assignment to variable - rendered_html_stream.

(Lint/UselessAssignment)


[convention] 414-414: Line is too long. [130/120]

(Layout/LineLength)

Additional comments not posted (16)
jest.config.js (1)

4-4: Approved: Addition of setupFiles in Jest configuration.

The addition of setupFiles to the Jest configuration is correctly implemented and enhances the testing setup by allowing for the execution of initialization code before tests run.

spec/dummy/config/webpack/alias.js (1)

7-7: Approved: Addition of stream alias in Webpack configuration.

The new alias mapping for stream to stream-browserify is correctly implemented. This change enhances module resolution and compatibility with browser environments, facilitating the use of streaming functionalities in client-side applications.

spec/dummy/config/webpack/webpackConfig.js (1)

7-7: Approved: Addition of stream-browserify as a fallback.

This change enhances client-side compatibility by providing a fallback for the stream module using stream-browserify. It's a good practice to ensure broader environment support.

The code change is approved.

Consider verifying this fallback integration by testing in environments that do not natively support the Node.js stream module to ensure smooth functionality.

node_package/src/buildConsoleReplay.ts (2)

14-21: Approved: Enhanced flexibility in console message handling.

The addition of the skipFirstNumberOfMessages parameter to both consoleReplay and buildConsoleReplay functions enhances flexibility and user control over the output. This is a valuable feature for managing console logs more effectively during server rendering.

The code changes are approved.

Also applies to: 42-43

Tools
Biome

[error] 17-17: Use Array.isArray() instead of instanceof Array.

instanceof Array returns false for array-like objects and arrays from other execution contexts.
Unsafe fix: Use Array.isArray() instead.

(lint/suspicious/useIsArray)


21-21: Fix required: Use Array.isArray() instead of instanceof Array.

The use of instanceof Array can lead to incorrect type checks in certain contexts. Replace it with Array.isArray() to ensure reliable and accurate type checking.

Apply this diff to address the issue:

-  if (!(console.history instanceof Array)) {
+  if (!Array.isArray(console.history)) {
    return '';
  }

Likely invalid or redundant comment.

spec/dummy/config/webpack/commonWebpackConfig.js (1)

44-44: Approved: Addition of process/browser to ProvidePlugin.

This change enhances client-side compatibility by polyfilling the process variable, which is crucial for libraries that rely on Node.js-like environments. It's a good practice to ensure broader library support.

The code change is approved.

Consider verifying this polyfill integration by testing with libraries that rely on the process object to ensure smooth functionality.

Verification successful

Verification Complete: process/browser Polyfill Integration Validated

The addition of process: 'process/browser' in the Webpack configuration is appropriate. It ensures compatibility for client-side code that relies on the process object, as evidenced by its usage in various configuration files. This change supports the correct functioning of environment-dependent code in client-side applications.

  • The process object is used in several Webpack and configuration files, indicating the necessity of this polyfill.
Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Check the integration of process/browser polyfill in different environments.

# Test: Ensure that libraries relying on the `process` object function correctly with this polyfill.
echo "Manual testing required: Verify process/browser polyfill with libraries depending on the process object."

Length of output: 218


Script:

#!/bin/bash
# Search for occurrences of 'process' in the codebase to identify dependencies or code that might rely on this object.
rg '\bprocess\b' -A 3

Length of output: 22959

lib/react_on_rails/react_component/render_options.rb (1)

106-108: Approve the addition of the stream? method.

The new stream? method is a useful addition for checking the streaming status of components. Ensure that this method is integrated and utilized properly in the context of component rendering.

The addition of the stream? method is approved.

Run the following script to verify the integration of the stream? method:

Verification successful

The stream? method is properly integrated and utilized.

The stream? method is effectively used in the codebase to manage rendering logic based on streaming status. Its integration in helper.rb and ruby_embedded_java_script.rb confirms its role in enhancing component rendering control.

  • File: lib/react_on_rails/helper.rb
  • File: lib/react_on_rails/server_rendering_pool/ruby_embedded_java_script.rb
Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Verify the usage of the `stream?` method in the codebase.

# Test: Search for the usage of the `stream?` method. Expect: Proper integration in component rendering logic.
rg --type ruby 'stream\?'

Length of output: 625

node_package/tests/ReactOnRails.test.js (1)

22-30: Approve the updated test setup but verify its effectiveness.

The modifications to the test setup, including the dynamic creation of the root element and the updated assertion, are well-thought-out improvements. However, it's crucial to ensure that these changes accurately reflect the intended behavior and enhance the test's effectiveness.

The changes to the test setup are approved.

Run the following script to verify the effectiveness of the new test setup:

Verification successful

The test setup in ReactOnRails.test.js is consistent and effective.

The usage of document.createElement and appendChild in both ReactOnRails.test.js and Authenticity.test.js demonstrates a consistent approach to DOM manipulation in the test suite. This consistency supports the effectiveness of the updated test setup in accurately reflecting the rendered output. No issues were found with the current implementation.

Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Verify that the updated test setup accurately reflects the rendered output.

# Test: Search for the usage of the new test setup in other test files. Expect: Consistent implementation and accurate test results.
rg --type javascript 'document\.createElement|appendChild'

Length of output: 95


Script:

#!/bin/bash
# Description: Verify that the updated test setup accurately reflects the rendered output.

# Test: Search for the usage of the new test setup in other test files. Expect: Consistent implementation and accurate test results.
rg 'document\.createElement|appendChild' --glob '*.js'

Length of output: 379

node_package/src/types/index.ts (2)

2-2: Approved: Import statement for Readable.

The import of Readable from the stream module is correctly added to support the new streaming functionality in the ReactOnRails interface.


141-141: Approved: New method streamServerRenderedReactComponent.

The addition of the streamServerRenderedReactComponent method to the ReactOnRails interface is correctly implemented. This method enhances the server-side rendering capabilities by enabling streaming, which aligns with the PR's objectives.

node_package/src/ReactOnRails.ts (1)

245-251: Approved: Integration of streamServerRenderedReactComponent into ReactOnRails.

The method streamServerRenderedReactComponent is correctly integrated into the ctx.ReactOnRails object, making it accessible as part of the module's API. The documentation comment provides clear guidance on its usage, which is beneficial for maintainability and developer understanding.

lib/react_on_rails/server_rendering_pool/ruby_embedded_java_script.rb (2)

229-248: Review new method for parsing and replaying console messages.

The new method parse_result_and_replay_console_messages is crucial for handling console messages during server-side rendering. Ensure that the JSON parsing and error handling are robust, and that the method correctly logs messages to the server when logging_on_server is enabled.

The implementation seems robust, but consider adding more detailed logging or error handling if necessary.


79-83: Ensure proper handling of streamed output.

The transformation of the result to handle streamed output should be carefully reviewed to ensure that each chunk is processed correctly and that there are no performance implications or errors in handling the streamed data.

Run the following script to verify the proper handling of streamed output:

spec/dummy/spec/helpers/react_on_rails_helper_spec.rb (2)

10-10: Confirm inclusion of TagHelper module.

The inclusion of the ActionView::Helpers::TagHelper module should enhance the HTML tag generation capabilities of the PlainReactOnRailsHelper class. Ensure that this inclusion does not introduce any conflicts or issues with existing methods.

The change is approved, but verify that all methods that use HTML tag generation are still functioning as expected.


350-373: Review new tests for rails_context_if_not_already_rendered.

The new tests for the rails_context_if_not_already_rendered method should ensure that it behaves correctly in scenarios where the Rails context has and has not been rendered. Verify that the tests cover all relevant cases and that they are correctly set up to simulate these scenarios.

The tests appear to be well-written and cover the necessary scenarios. Ensure that they are integrated correctly with the rest of the test suite.

lib/react_on_rails/helper.rb (1)

396-419: Ensure efficient handling of streamed content.

The build_react_component_result_for_server_streamed_content method constructs the final output for streamed content. Verify that the method efficiently handles the first chunk differently from subsequent chunks and that it does not introduce any performance issues.

Run the following script to verify the efficiency and correctness of the streamed content handling:

Tools
rubocop

[warning] 402-402: Useless assignment to variable - rendered_html_stream.

(Lint/UselessAssignment)


[convention] 414-414: Line is too long. [130/120]

(Layout/LineLength)

Comments failed to post (5)
node_package/tests/jest.setup.js (1)

1-13: Approved: Addition of TextEncoder and TextDecoder polyfill in jest.setup.js.

The polyfill for TextEncoder and TextDecoder is correctly implemented to ensure compatibility with the jsdom environment used in Jest tests. The conditional checks are well-placed to prevent unnecessary redefinitions.

Consider monitoring future updates to jsdom that might include native support for TextEncoder and TextDecoder, making this polyfill redundant.

node_package/src/serverRenderReactComponent.ts (1)

177-236: Approved with suggestions: New function streamServerRenderedReactComponent.

The implementation of streamServerRenderedReactComponent is robust, incorporating necessary error handling and stream transformations. The use of ReactDOMServer.renderToPipeableStream is appropriate for streaming server-rendered components.

Suggestions:

  • Consider adding more detailed logging for debugging purposes, especially around the stream transformation process.
  • Ensure comprehensive testing of error scenarios to validate the stream's error handling capabilities.
lib/react_on_rails/server_rendering_pool/ruby_embedded_java_script.rb (1)

59-63: Refactor conditional structure for clarity.

The conditional structure for handling streaming and non-streaming scenarios can be simplified to improve readability. Consider using a ternary operator or extracting the condition into a separate method if it grows more complex.

Apply this diff to refactor the conditional structure:

- result = if render_options.stream?
-   js_evaluator.eval_streaming_js(js_code, render_options)
- else
-   js_evaluator.eval_js(js_code, render_options)
- end
+ result = render_options.stream? ? js_evaluator.eval_streaming_js(js_code, render_options) : js_evaluator.eval_js(js_code, render_options)
Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

            result = render_options.stream? ? js_evaluator.eval_streaming_js(js_code, render_options) : js_evaluator.eval_js(js_code, render_options)
Tools
rubocop

[convention] 60-60: Use 2 (not -7) spaces for indentation.

(Layout/IndentationWidth)


[convention] 61-61: Align else with if.

(Layout/ElseAlignment)


[warning] 63-63: end at 63, 12 is not aligned with if at 59, 21.

(Layout/EndAlignment)

lib/react_on_rails/helper.rb (2)

94-114: Review new method for streaming React components.

The new stream_react_component method introduces streaming capabilities for React components using Ruby's Fiber. Ensure that the method handles errors appropriately and that the streaming process is efficient and robust.

Consider adding more detailed error messages and handling potential exceptions during the streaming process more gracefully.


355-362: Optimize streaming content handling.

The internal_stream_react_component method modifies options to include a streaming flag and processes the component rendering accordingly. Ensure that the options modification does not inadvertently affect other parts of the codebase and that the streaming process is optimized for performance.

Address the static analysis hint regarding the useless assignment to rendered_html_stream and ensure that all modifications to options are localized to this method.

Copy link
Member

@justin808 justin808 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  1. One suggestion for readability
  2. I updated the description with the information from ChatGPT on how this works. Please double-check.
  3. Should we have any unit tests?

lib/react_on_rails/helper.rb Show resolved Hide resolved
@AbanoubGhadban AbanoubGhadban force-pushed the abanoubghadban/pro403-use-render-to-readable-stream-to-render-async branch from f38ed50 to 0c2313e Compare September 30, 2024 13:44
@AbanoubGhadban AbanoubGhadban force-pushed the abanoubghadban/pro403-use-render-to-readable-stream-to-render-async branch 3 times, most recently from 64ce293 to e67efaf Compare October 15, 2024 12:36
@AbanoubGhadban AbanoubGhadban force-pushed the abanoubghadban/pro403-use-render-to-readable-stream-to-render-async branch from d5d5d68 to 8937c5f Compare October 20, 2024 11:45
@AbanoubGhadban AbanoubGhadban force-pushed the abanoubghadban/pro426-add-support-for-console-replay-while-streaming-component branch from bcfddd6 to 6ad16f1 Compare October 20, 2024 13:31
node_package/src/buildConsoleReplay.ts Outdated Show resolved Hide resolved
node_package/src/serverRenderReactComponent.ts Outdated Show resolved Hide resolved
node_package/src/serverRenderReactComponent.ts Outdated Show resolved Hide resolved
Copy link
Collaborator

@alexeyr-ci alexeyr-ci left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You have a great and extensive summary... but it doesn't seem to be for this PR :) Please update it to be relevant to these changes only. The changelog may not need to be updated if it's covered in the previous PR.

Approving with that understanding.

@AbanoubGhadban AbanoubGhadban force-pushed the abanoubghadban/pro426-add-support-for-console-replay-while-streaming-component branch 4 times, most recently from d1df75f to bd57e3d Compare October 29, 2024 10:39
Base automatically changed from abanoubghadban/pro403-use-render-to-readable-stream-to-render-async to master October 29, 2024 11:20
@AbanoubGhadban AbanoubGhadban force-pushed the abanoubghadban/pro426-add-support-for-console-replay-while-streaming-component branch from bd57e3d to 49547bb Compare October 29, 2024 11:26
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 9

🧹 Outside diff range and nitpick comments (2)
node_package/src/buildConsoleReplay.ts (1)

Line range hint 12-48: Consider documenting streaming behavior.

Since this change is part of the streaming SSR implementation, consider adding JSDoc comments explaining how this function integrates with streaming, particularly how numberOfMessagesToSkip relates to chunk processing.

Example documentation:

/**
 * Generates a console replay script for server-side rendering.
 * @param customConsoleHistory - Optional custom console history to use instead of global
 * @param numberOfMessagesToSkip - Number of initial messages to skip, useful when streaming
 *                                to avoid replaying console messages from previous chunks
 * @returns A string containing JavaScript code to replay console messages
 */
CHANGELOG.md (1)

21-22: Fix indentation in changelog entry

The bullet point indentation should be consistent with other entries in the changelog.

Apply this diff to fix the indentation:

### Added
- Added support for replaying console logs that occur during server rendering of streamed React components. This enables debugging of server-side rendering issues by capturing and displaying console output on the client and on the server output. [PR #1647](https://github.com/shakacode/react_on_rails/pull/1647) by [AbanoubGhadban](https://github.com/AbanoubGhadban).
🧰 Tools
🪛 Markdownlint

22-22: Expected: 0; Actual: 1
Unordered list indentation

(MD007, ul-indent)

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Files that changed from the base of the PR and between ce37122 and 49547bb.

📒 Files selected for processing (5)
  • CHANGELOG.md (1 hunks)
  • lib/react_on_rails/helper.rb (2 hunks)
  • lib/react_on_rails/server_rendering_pool/ruby_embedded_java_script.rb (4 hunks)
  • node_package/src/buildConsoleReplay.ts (2 hunks)
  • node_package/src/serverRenderReactComponent.ts (3 hunks)
🧰 Additional context used
🪛 Markdownlint
CHANGELOG.md

22-22: Expected: 0; Actual: 1
Unordered list indentation

(MD007, ul-indent)

🔇 Additional comments (6)
node_package/src/buildConsoleReplay.ts (1)

12-12: LGTM: Function signature changes maintain backward compatibility.

The addition of the optional numberOfMessagesToSkip parameter with a default value of 0 is well-implemented, ensuring backward compatibility while adding new functionality.

Also applies to: 47-48

node_package/src/serverRenderReactComponent.ts (2)

2-2: LGTM: Transform import added for streaming functionality.

The addition of Transform to the stream imports is appropriate for implementing the new chunk processing functionality.


207-207: ⚠️ Potential issue

Consider potential race conditions in console history handling.

The current implementation might miss console messages if they are added between capturing the history and processing chunks. Consider implementing a locking mechanism or using a queue to ensure all console messages are captured accurately.

Consider this safer approach:

- const consoleHistory = console.history;
+ let currentHistory: typeof console.history = [];
+ const updateHistory = () => {
+   currentHistory = [...(console.history || [])];
+ };
+ updateHistory();

Also applies to: 225-230

CHANGELOG.md (1)

21-22: Verify changelog entry matches PR objectives

The changelog entry accurately reflects the PR objectives regarding console replay support for streamed React components. The entry provides good context about the feature's purpose and benefits.

🧰 Tools
🪛 Markdownlint

22-22: Expected: 0; Actual: 1
Unordered list indentation

(MD007, ul-indent)

lib/react_on_rails/server_rendering_pool/ruby_embedded_java_script.rb (1)

59-63: Conditional logic correctly handles streaming and non-streaming modes

The updated conditional statement appropriately selects between eval_streaming_js and eval_js based on the stream? flag in render_options. This ensures that the correct evaluation method is used for each rendering mode.

lib/react_on_rails/helper.rb (1)

438-453: Stream transformation logic is correctly implemented

The implementation correctly handles the first chunk differently from subsequent chunks, ensuring that the component specification tag and Rails context are included only once. This approach optimizes the streaming of server-rendered content.

// console.history is a global polyfill used in server rendering.
const consoleHistory = customConsoleHistory ?? console.history;

if (!(Array.isArray(consoleHistory))) {
return '';
}

const lines = consoleHistory.map(msg => {
const lines = consoleHistory.slice(numberOfMessagesToSkip).map(msg => {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Add validation for numberOfMessagesToSkip parameter.

The slice operation could behave unexpectedly with negative numbers or values larger than the array length. Consider adding validation to ensure robust behavior.

Here's a suggested improvement:

+  if (numberOfMessagesToSkip < 0) {
+    throw new Error('numberOfMessagesToSkip must be non-negative');
+  }
+  if (numberOfMessagesToSkip > consoleHistory.length) {
+    return '';  // or throw error depending on desired behavior
+  }
   const lines = consoleHistory.slice(numberOfMessagesToSkip).map(msg => {
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const lines = consoleHistory.slice(numberOfMessagesToSkip).map(msg => {
if (numberOfMessagesToSkip < 0) {
throw new Error('numberOfMessagesToSkip must be non-negative');
}
if (numberOfMessagesToSkip > consoleHistory.length) {
return ''; // or throw error depending on desired behavior
}
const lines = consoleHistory.slice(numberOfMessagesToSkip).map(msg => {

Comment on lines +232 to +235
const jsonChunk = JSON.stringify({
html: htmlChunk,
consoleReplayScript,
});
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Enhance security and performance of JSON serialization.

The current implementation might be vulnerable to XSS attacks through console messages and could have performance issues with large chunks.

Consider these improvements:

-const jsonChunk = JSON.stringify({
-  html: htmlChunk,
-  consoleReplayScript,
-});
+const jsonChunk = JSON.stringify({
+  html: htmlChunk,
+  consoleReplayScript: consoleReplayScript.replace(/<|>/g, match => 
+    match === '<' ? '\\u003c' : '\\u003e'
+  ),
+}, null, 0);
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const jsonChunk = JSON.stringify({
html: htmlChunk,
consoleReplayScript,
});
const jsonChunk = JSON.stringify({
html: htmlChunk,
consoleReplayScript: consoleReplayScript.replace(/<|>/g, match =>
match === '<' ? '\\u003c' : '\\u003e'
),
}, null, 0);

Comment on lines +242 to +244
ReactDOMServer.renderToPipeableStream(reactRenderingResult).pipe(transformStream);

// TODO: Add console replay script to the stream
renderResult = transformStream;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Add error boundary and cleanup for streaming pipeline.

The streaming pipeline should handle errors gracefully and clean up resources properly.

Consider adding error handling:

-ReactDOMServer.renderToPipeableStream(reactRenderingResult).pipe(transformStream);
-renderResult = transformStream;
+const stream = ReactDOMServer.renderToPipeableStream(reactRenderingResult);
+stream.on('error', (error) => {
+  transformStream.destroy(error);
+});
+transformStream.on('error', (error) => {
+  console.error('Error during streaming:', error);
+});
+stream.pipe(transformStream);
+renderResult = transformStream;
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
ReactDOMServer.renderToPipeableStream(reactRenderingResult).pipe(transformStream);
// TODO: Add console replay script to the stream
renderResult = transformStream;
const stream = ReactDOMServer.renderToPipeableStream(reactRenderingResult);
stream.on('error', (error) => {
transformStream.destroy(error);
});
transformStream.on('error', (error) => {
console.error('Error during streaming:', error);
});
stream.pipe(transformStream);
renderResult = transformStream;

Comment on lines +226 to +239
const transformStream = new Transform({
transform(chunk, _, callback) {
const htmlChunk = chunk.toString();
const consoleReplayScript = buildConsoleReplay(consoleHistory, previouslyReplayedConsoleMessages);
previouslyReplayedConsoleMessages = consoleHistory?.length || 0;

const jsonChunk = JSON.stringify({
html: htmlChunk,
consoleReplayScript,
});

this.push(jsonChunk);
callback();
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Add error handling and backpressure management to transform stream.

The transform function lacks error handling and proper backpressure management, which could lead to memory issues with large streams or when errors occur during transformation.

Consider this improved implementation:

 const transformStream = new Transform({
-  transform(chunk, _, callback) {
+  async transform(chunk, encoding, callback) {
     try {
-      const htmlChunk = chunk.toString();
+      const htmlChunk = chunk.toString(encoding);
       const consoleReplayScript = buildConsoleReplay(consoleHistory, previouslyReplayedConsoleMessages);
       previouslyReplayedConsoleMessages = consoleHistory?.length || 0;

-      const jsonChunk = JSON.stringify({
+      const jsonChunk = JSON.stringify({
         html: htmlChunk,
         consoleReplayScript,
-      });
+      }) + '\n';

       this.push(jsonChunk);
       callback();
+    } catch (error) {
+      callback(error);
+    }
   }
 });
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const transformStream = new Transform({
transform(chunk, _, callback) {
const htmlChunk = chunk.toString();
const consoleReplayScript = buildConsoleReplay(consoleHistory, previouslyReplayedConsoleMessages);
previouslyReplayedConsoleMessages = consoleHistory?.length || 0;
const jsonChunk = JSON.stringify({
html: htmlChunk,
consoleReplayScript,
});
this.push(jsonChunk);
callback();
}
const transformStream = new Transform({
async transform(chunk, encoding, callback) {
try {
const htmlChunk = chunk.toString(encoding);
const consoleReplayScript = buildConsoleReplay(consoleHistory, previouslyReplayedConsoleMessages);
previouslyReplayedConsoleMessages = consoleHistory?.length || 0;
const jsonChunk = JSON.stringify({
html: htmlChunk,
consoleReplayScript,
}) + '\n';
this.push(jsonChunk);
callback();
} catch (error) {
callback(error);
}
}

@@ -46,7 +46,7 @@ def reset_pool_if_server_bundle_was_modified
# Note, js_code does not have to be based on React.
# js_code MUST RETURN json stringify Object
# Calling code will probably call 'html_safe' on return value before rendering to the view.
# rubocop:disable Metrics/CyclomaticComplexity, Metrics/AbcSize, Metrics/PerceivedComplexity
# rubocop:disable Metrics/CyclomaticComplexity
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Consider refactoring to reduce cyclomatic complexity

Disabling RuboCop's Metrics/CyclomaticComplexity for the exec_server_render_js method suggests that it may have become too complex. Refactoring this method could improve maintainability and readability, allowing you to re-enable the RuboCop check.

Comment on lines +79 to +83
return parse_result_and_replay_console_messages(result, render_options) unless render_options.stream?

# TODO: merge with exec_server_render_js
def exec_server_render_streaming_js(js_code, render_options, js_evaluator = nil)
js_evaluator ||= self
js_evaluator.eval_streaming_js(js_code, render_options)
# Streamed component is returned as stream of strings.
# We need to parse each chunk and replay the console messages.
result.transform { |chunk| parse_result_and_replay_console_messages(chunk, render_options) }
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Verify that result responds to transform method

In the streaming case, the code calls result.transform { |chunk| ... }. Ensure that result is an object that responds to the transform method. If transform is not a valid method for result, consider using map or another appropriate enumerable method to process each chunk.

Apply this diff to use map instead of transform:

- result.transform { |chunk| parse_result_and_replay_console_messages(chunk, render_options) }
+ result.map { |chunk| parse_result_and_replay_console_messages(chunk, render_options) }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
return parse_result_and_replay_console_messages(result, render_options) unless render_options.stream?
# TODO: merge with exec_server_render_js
def exec_server_render_streaming_js(js_code, render_options, js_evaluator = nil)
js_evaluator ||= self
js_evaluator.eval_streaming_js(js_code, render_options)
# Streamed component is returned as stream of strings.
# We need to parse each chunk and replay the console messages.
result.transform { |chunk| parse_result_and_replay_console_messages(chunk, render_options) }
return parse_result_and_replay_console_messages(result, render_options) unless render_options.stream?
# Streamed component is returned as stream of strings.
# We need to parse each chunk and replay the console messages.
result.map { |chunk| parse_result_and_replay_console_messages(chunk, render_options) }

Comment on lines +229 to +249
def parse_result_and_replay_console_messages(result_string, render_options)
result = nil
begin
result = JSON.parse(result_string)
rescue JSON::ParserError => e
raise ReactOnRails::JsonParseError.new(parse_error: e, json: result_string)
end

if render_options.logging_on_server
console_script = result["consoleReplayScript"]
console_script_lines = console_script.split("\n")
# Regular expression to match console.log or console.error calls with SERVER prefix
re = /console\.(?:log|error)\.apply\(console, \["\[SERVER\] (?<msg>.*)"\]\);/
console_script_lines&.each do |line|
match = re.match(line)
# Log matched messages to Rails logger with react_on_rails prefix
Rails.logger.info { "[react_on_rails] #{match[:msg]}" } if match
end
end
result
end
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Add nil checks for console_script to prevent potential errors

In the parse_result_and_replay_console_messages method, if result["consoleReplayScript"] is nil, calling split on it will raise a NoMethodError. It's advisable to check if console_script is present before proceeding. Similarly, ensure that console_script_lines is not nil before iterating.

Apply this diff to add the necessary nil checks:

def parse_result_and_replay_console_messages(result_string, render_options)
  result = nil
  begin
    result = JSON.parse(result_string)
  rescue JSON::ParserError => e
    raise ReactOnRails::JsonParseError.new(parse_error: e, json: result_string)
  end

  if render_options.logging_on_server
    console_script = result["consoleReplayScript"]
+   return result unless console_script
    console_script_lines = console_script.split("\n")
+   return result unless console_script_lines
    # Regular expression to match console.log or console.error calls with SERVER prefix
    re = /console\.(?:log|error)\.apply\(console, \["\[SERVER\] (?<msg>.*)"\]\);/
    console_script_lines.each do |line|
      match = re.match(line)
      # Log matched messages to Rails logger with react_on_rails prefix
      Rails.logger.info { "[react_on_rails] #{match[:msg]}" } if match
    end
  end
  result
end
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
def parse_result_and_replay_console_messages(result_string, render_options)
result = nil
begin
result = JSON.parse(result_string)
rescue JSON::ParserError => e
raise ReactOnRails::JsonParseError.new(parse_error: e, json: result_string)
end
if render_options.logging_on_server
console_script = result["consoleReplayScript"]
console_script_lines = console_script.split("\n")
# Regular expression to match console.log or console.error calls with SERVER prefix
re = /console\.(?:log|error)\.apply\(console, \["\[SERVER\] (?<msg>.*)"\]\);/
console_script_lines&.each do |line|
match = re.match(line)
# Log matched messages to Rails logger with react_on_rails prefix
Rails.logger.info { "[react_on_rails] #{match[:msg]}" } if match
end
end
result
end
def parse_result_and_replay_console_messages(result_string, render_options)
result = nil
begin
result = JSON.parse(result_string)
rescue JSON::ParserError => e
raise ReactOnRails::JsonParseError.new(parse_error: e, json: result_string)
end
if render_options.logging_on_server
console_script = result["consoleReplayScript"]
return result unless console_script
console_script_lines = console_script.split("\n")
return result unless console_script_lines
# Regular expression to match console.log or console.error calls with SERVER prefix
re = /console\.(?:log|error)\.apply\(console, \["\[SERVER\] (?<msg>.*)"\]\);/
console_script_lines.each do |line|
match = re.match(line)
# Log matched messages to Rails logger with react_on_rails prefix
Rails.logger.info { "[react_on_rails] #{match[:msg]}" } if match
end
end
result
end

Comment on lines +494 to +499
html_content = <<~HTML
#{rendered_output}
#{component_specification_tag}
#{console_script}
HTML
html_content.strip.html_safe
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Adjust indentation in HEREDOC to prevent unintended whitespace

The inconsistent indentation inside the HEREDOC may introduce unwanted whitespace in the generated HTML output. To ensure the rendered HTML is formatted correctly, align the content within the HEREDOC.

Apply this diff to correct the indentation:

           html_content = <<~HTML
-            #{rendered_output}
-                  #{component_specification_tag}
-                  #{console_script}
+#{rendered_output}
+#{component_specification_tag}
+#{console_script}
           HTML
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
html_content = <<~HTML
#{rendered_output}
#{component_specification_tag}
#{console_script}
HTML
html_content.strip.html_safe
html_content = <<~HTML
#{rendered_output}
#{component_specification_tag}
#{console_script}
HTML
html_content.strip.html_safe

Comment on lines +433 to +435
rendered_html_stream:,
component_specification_tag:,
render_options:
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Ensure required keyword is used for parameter validation and consistency

In the build_react_component_result_for_server_streamed_content method, the parameters are declared without the required method. To ensure that these parameters are not nil and to maintain consistency with other methods in the file, consider using the required helper to validate them.

Apply this diff to enforce parameter validation:

 def build_react_component_result_for_server_streamed_content(
-          rendered_html_stream:,
-          component_specification_tag:,
-          render_options:
+          rendered_html_stream: required("rendered_html_stream"),
+          component_specification_tag: required("component_specification_tag"),
+          render_options: required("render_options")
        )
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
rendered_html_stream:,
component_specification_tag:,
render_options:
rendered_html_stream: required("rendered_html_stream"),
component_specification_tag: required("component_specification_tag"),
render_options: required("render_options")

@AbanoubGhadban AbanoubGhadban merged commit cffaed8 into master Oct 29, 2024
11 checks passed
@AbanoubGhadban AbanoubGhadban deleted the abanoubghadban/pro426-add-support-for-console-replay-while-streaming-component branch October 29, 2024 11:54
@coderabbitai coderabbitai bot mentioned this pull request Nov 11, 2024
3 tasks
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants