-
-
Notifications
You must be signed in to change notification settings - Fork 630
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
Refactor serverRenderReactComponent function #1653
Refactor serverRenderReactComponent function #1653
Conversation
@AbanoubGhadban test failure. |
WalkthroughThe changes in this pull request involve significant updates to the server rendering logic for React components. The Changes
Possibly related PRs
Suggested reviewers
Poem
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? 🪧 TipsChatThere are 3 ways to chat with CodeRabbit:
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)
Other keywords and placeholders
CodeRabbit Configuration File (
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 2
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
📒 Files selected for processing (3)
- node_package/src/isServerRenderResult.ts (1 hunks)
- node_package/src/serverRenderReactComponent.ts (2 hunks)
- node_package/src/types/index.ts (1 hunks)
🧰 Additional context used
🔇 Additional comments (1)
node_package/src/types/index.ts (1)
70-79
: LGTM! Consider adding type guards for improved type safety.The additions to the
RegisteredComponent
interface are well-documented and provide more granular control over component behavior. This aligns well with the PR objective of refactoring theserverRenderReactComponent
function.To further improve type safety, consider adding type guard functions for these new properties. This would allow for more robust type checking when working with
RegisteredComponent
objects.Here's an example of how you could implement type guards:
export function isRenderFunction(component: RegisteredComponent): component is RegisteredComponent & { renderFunction: true } { return component.renderFunction === true; } export function isRendererFunction(component: RegisteredComponent): component is RegisteredComponent & { isRenderer: true } { return component.isRenderer === true; }Let's check how these changes might affect the rest of the codebase:
✅ Verification successful
Verified! The additions to the
RegisteredComponent
interface are well-integrated and do not negatively impact the existing codebase. All usages ofRegisteredComponent
in.ts
files are consistent with the new properties, and no issues were identified related to theserverRenderReactComponent
function.🏁 Scripts executed
The following scripts were executed for the analysis:
Script:
#!/bin/bash # Description: Check for potential impacts of the RegisteredComponent interface changes # Test: Search for usages of RegisteredComponent echo "Searching for RegisteredComponent usages:" rg --type typescript "RegisteredComponent" # Test: Search for places where components are registered echo "\nSearching for component registration:" rg --type typescript "register\(\s*\{" -A 5 # Test: Search for serverRenderReactComponent function echo "\nSearching for serverRenderReactComponent function:" rg --type typescript "serverRenderReactComponent"Length of output: 540
Script:
#!/bin/bash # Description: Check for potential impacts of the RegisteredComponent interface changes # Test: Search for usages of RegisteredComponent echo "Searching for RegisteredComponent usages in .ts and .tsx files:" rg --type ts "RegisteredComponent" || rg --type tsx "RegisteredComponent" # Test: Search for places where components are registered echo -e "\nSearching for component registration in .ts and .tsx files:" rg --type ts "register\(\s*\{" -A 5 || rg --type tsx "register\(\s*\{" -A 5 # Test: Search for serverRenderReactComponent function echo -e "\nSearching for serverRenderReactComponent function in .ts and .tsx files:" rg --type ts "serverRenderReactComponent" || rg --type tsx "serverRenderReactComponent"Length of output: 2958
export function isPromise<T>(testValue: CreateReactOutputResult | Promise<T> | string | null): | ||
testValue is Promise<T> { | ||
return !!((testValue as Promise<T> | null)?.then); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
Consider renaming the function and improving promise detection.
The changes to the isPromise
function improve its flexibility and type safety. However, there are a couple of points to consider:
-
The function name
isPromise
might be misleading as it now accepts non-promise types. Consider renaming it to something likeisPromiseLike
orcouldBePromise
to better reflect its broader input types. -
The current implementation might not correctly identify all promise-like objects. The
then
property check is a good start, but it's not foolproof. Consider using a more robust check.
Here's a suggested improvement:
export function isPromiseLike<T>(testValue: CreateReactOutputResult | Promise<T> | string | null): testValue is Promise<T> {
return testValue != null && typeof testValue === 'object' && typeof (testValue as any).then === 'function';
}
This implementation:
- Renames the function to
isPromiseLike
. - Checks if
testValue
is not null and is an object. - Verifies that the
then
property is a function.
These changes make the function more accurate in identifying promise-like objects while maintaining its flexibility.
function processPromise(result: Promise<unknown>, renderingReturnsPromises: boolean): Promise<string> | string { | ||
if (!renderingReturnsPromises) { | ||
console.error('Your render function returned a Promise, which is only supported by a node renderer, not ExecJS.'); | ||
// If the app is using server rendering with ExecJS, then the promise will not be awaited. | ||
// And when a promise is passed to JSON.stringify, it will be converted to '{}'. | ||
return '{}'; | ||
} | ||
return result as Promise<string>; | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ensure that Promise resolves to a string in processPromise
to maintain type safety
In the processPromise
function, the input parameter result
is of type Promise<unknown>
. When renderingReturnsPromises
is true, the function returns result as Promise<string>
, but there's no guarantee that result
actually resolves to a string
. This may lead to type inconsistencies or runtime errors if result
resolves to a different type.
Consider updating the function to enforce that the promise resolves to a string, either by refining the input type or by adding runtime checks.
You might update the function signature to reflect the expected resolved type:
-function processPromise(result: Promise<unknown>, renderingReturnsPromises: boolean): Promise<string> | string {
+function processPromise(result: Promise<string>, renderingReturnsPromises: boolean): Promise<string> | string {
If the result
can resolve to different types, you may need to handle those cases appropriately to ensure type safety.
Committable suggestion was skipped due to low confidence.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Let's merge if @alexeyr-ci approves
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 1
🧹 Outside diff range and nitpick comments (3)
node_package/src/types/index.ts (2)
46-69
: LGTM! Clear and well-documented interface for RenderFunction.The new
RenderFunction
interface and its accompanying documentation provide clear guidelines for implementing and identifying render functions. The distinction between render functions and React Function Components is well explained, and the examples are helpful.Consider adding a link to the React documentation for Function Components in the comments to provide additional context for developers who might be less familiar with the differences:
* @remarks * To distinguish a render function from a React Function Component: +* (See: https://reactjs.org/docs/components-and-props.html#function-and-class-components) * 1. Ensure it accepts two parameters (props and railsContext), even if railsContext is unused, or * 2. Set the `renderFunction` property to `true` on the function object.
Also applies to: 70-75
94-102
: LGTM! Clear additions to RegisteredComponent interface.The new
renderFunction
andisRenderer
properties provide clear flags for identifying the type of the registered component. The comments explain their purpose and relationship well.For consistency in the documentation, consider standardizing the capitalization of "render function":
/** - * Indicates if the registered component is a RenderFunction + * Indicates if the registered component is a render function * * @see RenderFunction for more details on its behavior and usage. * */ renderFunction: boolean; // Indicates if the registered component is a Renderer function. // Renderer function handles DOM rendering or hydration with 3 args: (props, railsContext, domNodeId) // Supported on the client side only. - // All renderer functions are render functions, but not all render functions are renderer functions. + // All renderer functions are render functions, but not all render functions are renderer functions. isRenderer: boolean;node_package/src/serverRenderReactComponent.ts (1)
64-73
: LGTM with suggestion: Effective React element processingThe
processReactElement
function correctly renders React elements to strings and includes helpful error handling.Consider improving the error message formatting:
- console.error(`Invalid call to renderToString. Possibly you have a renderFunction, a function that already -calls renderToString, that takes one parameter. You need to add an extra unused parameter to identify this function -as a renderFunction and not a simple React Function Component.`); + console.error( + 'Invalid call to renderToString. Possibly you have a renderFunction, a function that already ' + + 'calls renderToString, that takes one parameter. You need to add an extra unused parameter to ' + + 'identify this function as a renderFunction and not a simple React Function Component.' + );This change improves readability and maintains consistent string formatting.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
📒 Files selected for processing (2)
- node_package/src/serverRenderReactComponent.ts (2 hunks)
- node_package/src/types/index.ts (2 hunks)
🧰 Additional context used
🔇 Additional comments (12)
node_package/src/types/index.ts (1)
Line range hint
46-102
: Address previous review comments.There are two relevant past review comments that haven't been fully addressed:
- Adding links to
RenderFunction
in the documentation.- Consistency in capitalization of "render function" vs "Render-function".
To address these:
- Consider adding a link to the
RenderFunction
interface in theRegisteredComponent
interface documentation:/** * Indicates if the registered component is a render function - * @see RenderFunction for more details on its behavior and usage. + * @see {@link RenderFunction} for more details on its behavior and usage. */
- Ensure consistent capitalization of "render function" throughout the documentation, as mentioned in the previous review comment.
These changes will improve the overall clarity and consistency of the documentation.
node_package/src/serverRenderReactComponent.ts (11)
11-15
: LGTM: Well-structuredRenderState
typeThe
RenderState
type is well-defined and covers the necessary states for rendering, including different result types and error handling.
17-22
: LGTM: ComprehensiveRenderOptions
typeThe
RenderOptions
type is well-defined and covers all necessary options for rendering, including optional properties where appropriate.
24-28
: LGTM: Clear component validationThe
validateComponent
function provides early validation with a clear error message and documentation link, which is a good practice.
54-62
: LGTM: Robust promise handlingThe
processPromise
function correctly handles promise results and provides an appropriate fallback for non-promise-supporting renderers. The error message is clear and informative.
75-83
: LGTM: Comprehensive rendering result processingThe
processRenderingResult
function effectively handles various types of rendering results, delegating to appropriate processing functions in a logical order.
85-95
: LGTM: Robust rendering error handlingThe
handleRenderingError
function provides consistent and flexible error handling, supporting both throwing and returning error states. It properly handles different error types and maintains consistency with theRenderState
type.
97-104
: LGTM: Well-structured result object creationThe
createResultObject
function correctly creates aRenderResult
object, handling all required fields and the optional error case appropriately.
106-119
: LGTM: Comprehensive async result handlingThe
createPromiseResult
function effectively handles asynchronous rendering results, including proper error handling and consistent result creation usingcreateResultObject
andhandleRenderingError
.
121-141
: LGTM: Robust final result creationThe
createFinalResult
function effectively handles both synchronous and asynchronous rendering results. The approach to building the console replay script before awaiting promises is a good solution to prevent cross-request console log leakage. The explanatory comment is very helpful for understanding the rationale behind this approach.
143-182
: LGTM: Excellent refactoring of main rendering functionThe refactoring of
serverRenderReactComponentInternal
has significantly improved its readability and maintainability. The use of helper functions makes the main logic clearer and easier to understand. The comments explaining each step of the process are very helpful for developers maintaining this code in the future.This refactoring adheres to the Single Responsibility Principle, making the code more modular and easier to test and maintain.
Line range hint
1-194
: Excellent refactoring: Improved modularity and maintainabilityThe extensive refactoring of this file has significantly improved its overall structure and maintainability. Key improvements include:
- Introduction of clear, well-defined types (
RenderState
andRenderOptions
).- Breaking down the complex rendering process into smaller, focused helper functions.
- Improved error handling and consistency throughout the rendering process.
- Better separation of concerns, adhering to the Single Responsibility Principle.
- Comprehensive comments explaining the rationale behind certain design decisions.
These changes should make the code easier to understand, test, and maintain in the future. The refactoring maintains existing functionality while substantially improving code quality.
function processServerRenderHash(result: ServerRenderResult, options: RenderOptions): RenderState { | ||
const { redirectLocation, routeError } = result; | ||
const hasErrors = !!routeError; | ||
|
||
if (hasErrors) { | ||
console.error(`React Router ERROR: ${JSON.stringify(routeError)}`); | ||
} | ||
|
||
let htmlResult: string; | ||
if (redirectLocation) { | ||
if (options.trace) { | ||
const redirectPath = redirectLocation.pathname + redirectLocation.search; | ||
console.log(`ROUTER REDIRECT: ${options.componentName} to dom node with id: ${options.domNodeId}, redirect to ${redirectPath}`); | ||
} | ||
// For redirects on server rendering, we can't stop Rails from returning the same result. | ||
// Possibly, someday, we could have the Rails server redirect. | ||
htmlResult = ''; | ||
} else { | ||
htmlResult = result.renderedHtml as string; | ||
} | ||
|
||
return { result: htmlResult, hasErrors }; | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
LGTM with suggestion: Comprehensive server render hash processing
The processServerRenderHash
function handles various scenarios well, including redirects and errors. The trace logging is helpful for debugging.
Consider enhancing the error handling:
if (hasErrors) {
- console.error(`React Router ERROR: ${JSON.stringify(routeError)}`);
+ console.error('React Router ERROR:', routeError);
}
This change avoids potential circular reference issues in JSON.stringify
and provides a more readable error output.
📝 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.
function processServerRenderHash(result: ServerRenderResult, options: RenderOptions): RenderState { | |
const { redirectLocation, routeError } = result; | |
const hasErrors = !!routeError; | |
if (hasErrors) { | |
console.error(`React Router ERROR: ${JSON.stringify(routeError)}`); | |
} | |
let htmlResult: string; | |
if (redirectLocation) { | |
if (options.trace) { | |
const redirectPath = redirectLocation.pathname + redirectLocation.search; | |
console.log(`ROUTER REDIRECT: ${options.componentName} to dom node with id: ${options.domNodeId}, redirect to ${redirectPath}`); | |
} | |
// For redirects on server rendering, we can't stop Rails from returning the same result. | |
// Possibly, someday, we could have the Rails server redirect. | |
htmlResult = ''; | |
} else { | |
htmlResult = result.renderedHtml as string; | |
} | |
return { result: htmlResult, hasErrors }; | |
} | |
function processServerRenderHash(result: ServerRenderResult, options: RenderOptions): RenderState { | |
const { redirectLocation, routeError } = result; | |
const hasErrors = !!routeError; | |
if (hasErrors) { | |
console.error('React Router ERROR:', routeError); | |
} | |
let htmlResult: string; | |
if (redirectLocation) { | |
if (options.trace) { | |
const redirectPath = redirectLocation.pathname + redirectLocation.search; | |
console.log(`ROUTER REDIRECT: ${options.componentName} to dom node with id: ${options.domNodeId}, redirect to ${redirectPath}`); | |
} | |
// For redirects on server rendering, we can't stop Rails from returning the same result. | |
// Possibly, someday, we could have the Rails server redirect. | |
htmlResult = ''; | |
} else { | |
htmlResult = result.renderedHtml as string; | |
} | |
return { result: htmlResult, hasErrors }; | |
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 0
🧹 Outside diff range and nitpick comments (2)
node_package/src/serverRenderReactComponent.ts (2)
30-52
: LGTM with suggestion: Comprehensive server render hash processingThe
processServerRenderHash
function handles various scenarios well, including redirects and errors. The trace logging is helpful for debugging. Consider enhancing the error logging:if (hasErrors) { - console.error(`React Router ERROR: ${JSON.stringify(routeError)}`); + console.error('React Router ERROR:', routeError); }This change avoids potential circular reference issues in
JSON.stringify
and provides a more readable error output.
54-62
: LGTM with suggestion: Clear promise handlingThe
processPromise
function provides a clear way to handle promises in different rendering scenarios. Consider improving the return type for better type safety:-function processPromise(result: Promise<string>, renderingReturnsPromises: boolean): Promise<string> | string { +function processPromise(result: Promise<string>, renderingReturnsPromises: boolean): Promise<string> | '{}' {This change makes it clear that the function either returns the original promise or the specific string '{}' when promises are not supported.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
📒 Files selected for processing (1)
- node_package/src/serverRenderReactComponent.ts (2 hunks)
🧰 Additional context used
🔇 Additional comments (10)
node_package/src/serverRenderReactComponent.ts (10)
11-15
: LGTM: Well-structuredRenderState
typeThe
RenderState
type is well-defined and covers the necessary states for rendering, including the result, error status, and optional error details. The use of union types forresult
allows for flexibility in handling different rendering outcomes (null, string, or Promise).
17-22
: LGTM: ComprehensiveRenderOptions
typeThe
RenderOptions
type is well-defined and covers the necessary options for rendering, including the component name, optional DOM node ID, tracing option, and whether rendering returns promises. The use of optional properties allows for flexibility in usage.
24-28
: LGTM: Clear component validationThe
validateComponent
function provides a clear validation step for components, ensuring that renderers are not used for server rendering. The error message includes a helpful link for more information, which is a good practice for guiding developers.
64-73
: LGTM: Robust React element processingThe
processReactElement
function provides a clear way to render React elements to strings. The error handling is comprehensive and includes a helpful message that guides developers in case of incorrect usage of render functions.
75-83
: LGTM: Comprehensive rendering result processingThe
processRenderingResult
function effectively handles different types of rendering results (server render hash, promise, and React element) by utilizing the previously defined helper functions. This approach provides a clear and modular way to process various rendering scenarios.
85-95
: LGTM: Consistent rendering error handlingThe
handleRenderingError
function provides a consistent way to handle rendering errors. It allows for flexibility in error handling based on thethrowJsErrors
option and creates a standardized error object. This approach ensures that errors are handled uniformly throughout the rendering process.
97-104
: LGTM: Standardized result object creationThe
createResultObject
function provides a consistent way to create result objects, including HTML, console replay script, and error information. This standardized approach ensures that all necessary information is included in the rendering results, improving consistency and ease of use.
106-119
: LGTM: Robust asynchronous result handlingThe
createPromiseResult
function provides a clear and robust way to handle asynchronous rendering results. It effectively uses the previously defined helper functions and includes comprehensive error handling for promise rejections. This approach ensures that asynchronous rendering scenarios are handled consistently and errors are properly managed.
143-182
: LGTM: Improved modularity and readability in main rendering functionThe
serverRenderReactComponentInternal
function has been significantly refactored, resulting in improved modularity and readability. The use of newly introduced helper functions has made the main function more concise and easier to understand. The error handling and result processing have been standardized, leading to a more robust and maintainable implementation.Key improvements:
- Clear separation of concerns with helper functions
- Improved error handling and standardized error responses
- Better handling of different rendering scenarios (synchronous, asynchronous, and error cases)
- More consistent and structured output format
These changes should make the codebase easier to maintain and extend in the future.
Line range hint
1-194
: Overall: Excellent refactoring with improved modularity and error handlingThe refactoring of
serverRenderReactComponent.ts
has significantly improved the code structure, readability, and maintainability. Key improvements include:
- Introduction of well-defined types (
RenderState
andRenderOptions
) for better type safety.- Creation of modular helper functions to handle specific rendering scenarios.
- Improved and standardized error handling throughout the rendering process.
- Enhanced main function (
serverRenderReactComponentInternal
) that leverages the new helper functions for a more concise and clear implementation.- Better separation of concerns, making the code easier to understand and maintain.
These changes should make the codebase more robust and easier to extend in the future. The modular approach allows for easier testing and modification of individual components of the rendering process.
Suggestions for further improvement:
- Consider adding unit tests for the new helper functions to ensure their correctness and maintain their functionality during future changes.
- Document the new functions and types with JSDoc comments to provide clear usage instructions for other developers.
Overall, this refactoring is a significant improvement to the codebase.
Summary
Remove this paragraph and provide a general description of the code changes in your pull
request... were there any bugs you had fixed? If so, mention them. If
these bugs have open GitHub issues, be sure to tag them here as well,
to keep the conversation linked together.
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 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
Summary by CodeRabbit
New Features
Bug Fixes
Refactor