-
Notifications
You must be signed in to change notification settings - Fork 47.6k
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
fix(ServerRendering): execution should be async #982
Conversation
@@ -50,7 +50,7 @@ function renderComponentToString(component, callback) { | |||
transaction.perform(function() { | |||
var markup = component.mountComponent(id, transaction, 0); | |||
markup = ReactMarkupChecksum.addChecksumToMarkup(markup); | |||
callback(markup); | |||
window.setTimeout(callback, 0, markup); |
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.
window
doesn't necessarily exist where you might call this.
Very true, sorry for that. Updated the PR accordingly. |
@bripkens FYI, I think you forgot to push it. |
@@ -50,7 +50,7 @@ function renderComponentToString(component, callback) { | |||
transaction.perform(function() { | |||
var markup = component.mountComponent(id, transaction, 0); | |||
markup = ReactMarkupChecksum.addChecksumToMarkup(markup); | |||
callback(markup); | |||
setTimeout(callback, 0, markup); |
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.
Can you change this to setTimeout(() => callback(markup), 0) ? Add FB we disallow to use the third argument for setTimeout like this.
Done. CLA is signed as well. |
I recently implemented server-side rendering in Rails here: It is a lot easier if What if |
What if we just committed to a sync API? Do we think this is likely to realistically change in the near term @sebmarkbage @jordwalke ? I know we've talked about async rendering but it seems pretty far off. |
woops, sorry, don't ask me what happened there. |
@petehunt I'd be fine with that. A short term async rendering would likely be of the continuation style or allow certain components to render a partial result and then update themselves. Longterm, however, we could do parallelized rendering with PJS. All could be opt-in. |
@@ -50,7 +50,7 @@ function renderComponentToString(component, callback) { | |||
transaction.perform(function() { | |||
var markup = component.mountComponent(id, transaction, 0); | |||
markup = ReactMarkupChecksum.addChecksumToMarkup(markup); | |||
callback(markup); | |||
setTimeout(() => callback(markup), 0); |
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.
process.nextTick would be more useful on the server...? why zero timeout?
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.
I didn't mean to restrict the server side rendering to node.js. For instance, I am currently trying to make this work on the JVM where process.nextTick is not available.
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.
I think a lot more people use node than other server side js environments, and even then, could shim this to use node conventions as default, falling back to setTimeout for other platforms. (very personal bias though!)
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.
The semantic difference between process.nextTick and setTimeout seems negligible in this case (while it might not be for edge cases). I would therefore argue that compatibility is more important than Node conventions (especially considering that renderComponentToString
is part of React's default distribution and not part of some server side add-on).
Anyhow, let's wait until the core team has made up their mind w.r.t. asynchronous rendering in general :-).
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.
true that, anything that runs on client would need to avoid nodeisms. I think I saw somewhere a comment about using setImmediate
..? there's a nice discussion of the problem with setTimeout's clamped minimum values vs real nextTick
deferral here: http://jphpsf.github.io/setImmediate-shim-demo/
Maybe I should provide some context and why I think async rendering is interesting. Over the last two weeks I have been playing with the idea of server side rendering. Rendering simple component trees is easy with the current version of I stumbled over this issue while making facebookarchive/react-page#47 work on the JVM. On the JVM I have complete control over the event loop and can identify the moment when all data has been loaded. I am hoping to make server side rendering work without any changes to an existing React application. That being said, more React changes will be necessary in order to support this "advanced" server side rendering (and then the question remains whether these changes are portable across JS environments). |
Just noticed #985. Supporting server side rendering cases with async data retrieval is surely going to require a fair amount of work. If you want to make the API synchronous and thus not focus on this issue now, I have no problem with that. |
On a related note, what about changing the callback signature to |
following node conventions seems like a good idea. |
I think that data fetching is workable even in a sync world. You can just move the asynchronicity outside of React. You can even put the code for data fetching alongside your React component, just don't call render() until all the data is fetched and pass the data in as a top-level prop. I think making this method synchronous would be better. |
Would you like me to update my PR accordingly, i.e. change the API to synchronous, or do you wish to wait with this (breaking) change? Would love to help you out here. |
@bripkens if you want to make the API synchronous that would be awesome! |
The documentation states that React.renderComponentToString 'uses a callback API to keep the API async', but the implementation is actually synchronous. In order to maintain this contract the callback should always be called asynchronously or be change to a synchronous API. As per the discussion of pull request 982, the API should be changed to a synchronous one.
@petehunt PR updated. API is no synchronous and provides some guidance for migrations. |
// We use a callback API to keep the API async in case in the future we ever | ||
// need it, but in reality this is a synchronous operation. | ||
|
||
function renderComponentToString(component) { | ||
invariant( | ||
ReactComponent.isValidComponent(component), | ||
'renderComponentToString(): You must pass a valid ReactComponent.' | ||
); | ||
|
||
invariant( |
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.
I'm not sure that this should be an invariant
... do we want to stop execution or should we let it keep running and just warn? @petehunt?
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.
I am cool with this
Sure thing, here you go. The word "internal" might imply that it is not, but is this typechecker available somewhere? You made me curious ;-). |
It's not available at this point, sorry :/ |
fix(ServerRendering): execution should be async
@bripkens Needs docs changes here? https://github.com/facebook/react/blob/master/docs/docs/ref-01-top-level-api.md |
It does, yes. I opened PR #1033 with the necessary doc changes. |
See the background in #982. This PR reimplements part of InferReactiveScopes, aligning reactive scopes to block boundaries, but against ReactiveFunction instead of the HIR.
See the background in #982. This PR reimplements part of InferReactiveScopes, merging overlapping reactive scopes, but against ReactiveFunction instead of the HIR.
The documentation states that React.renderComponentToString
'uses a callback API to keep the API async', but the
implementation is actually synchronous. In order to maintain
this contract the callback should always be called
asynchronously.