-
Notifications
You must be signed in to change notification settings - Fork 47.5k
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
[Fiber] Split dev methods within ReactComponentTreeHook #9096
Conversation
@@ -14,16 +14,16 @@ | |||
|
|||
import type { Fiber } from 'ReactFiber'; | |||
import type { DebugID } from 'ReactInstanceType'; | |||
import type { ComponentTreeHookType } from '../../hooks/ReactComponentTreeHook'; |
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 this should just say from 'ReactComponentTreeHook'
?
} | ||
return info; | ||
}, | ||
export type ComponentTreeHookType = { |
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.
What do you think about two separate types, one for DEV, and another for prod? Then the consumer could cast once to whichever is appropriate depending on whether we're behind DEV or not.
@@ -38,7 +38,9 @@ if (__DEV__) { | |||
if (typeof current === 'number') { | |||
// DebugID from Stack. | |||
const debugID = current; | |||
stack = getStackAddendumByID(debugID); | |||
if (getStackAddendumByID) { |
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'd prefer to not do this since we already know we're in DEV. See my comment below on potential way to accomplish this.
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.
This check seems unnecessary, other than for flow, since we're within a __DEV__
block. I liked Dan's suggestion below- to use a different type for dev/prod so we can avoid all of the unnecessary not-defined checks below.
var item = getItem(id); | ||
return item ? item.updateCount : 0; | ||
}, | ||
ReactComponentTreeHook = Object.assign({}, ReactComponentTreeHook, { |
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.
Nit: why not call Object.assign()
without using return value? Flow?
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.
Flow complains if I do it any other way :)
@@ -108,22 +118,24 @@ if (__DEV__) { | |||
var lifeCycleTimerHasWarned = false; | |||
|
|||
const clearHistory = function() { | |||
ReactComponentTreeHook.purgeUnmountedComponents(); | |||
if (purgeUnmountedComponents) { |
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.
Here, too, it seems odd to add these checks.
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 wonder if we could have an "env: 'dev'" property on the type and then use flow refinements. I think Flow might understand this pattern too:
// in ReactComponentTreeHook
type ProdType = {env: 'prod', foo: F};
type DevType = {env: 'dev', foo: F, bar: B};
var ReactComponentTreeHook : ProdType | DevType = ...;
module.exports = ReactComponentTreeHook;
// elsewhere
const ReactComponentTreeHook = require('ReactComponentTreeHook');
if (__DEV__ && ReactComponentTreeHook.env === 'dev') {
const ReactComponentTreeHookDev : ComponentTreeHookDevType = ReactComponentTreeHook;
...
}
Not entirely sure though. We should probably have this reflectable in some way though to prevent crashes from version mismatches.
|
||
const ReactDebugCurrentFrame = {}; | ||
|
||
if (__DEV__) { | ||
// how do a state that ReactComponentTreeHook is using the ComponentTreeHookDevType type? | ||
const ReactComponentTreeHook: ComponentTreeHookDevType = require('ReactComponentTreeHook'); | ||
const ReactComponentTreeHook: ComponentTreeHookDevType = (require('ReactComponentTreeHook'): any); |
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.
@@ -68,7 +69,7 @@ var ReactDebugTool = ((null: any): typeof ReactDebugTool); | |||
if (__DEV__) { | |||
const hooks = []; | |||
const didHookThrowForEvent = {}; | |||
const ReactComponentTreeHook = require('react/lib/ReactComponentTreeHook'); | |||
const ReactComponentTreeHook: ComponentTreeHookDevType = (require('react/lib/ReactComponentTreeHook'): any); |
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 believe this is actually not safe in the case that you use an unminified react-dom.js with react.min.js. Can you check?
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.
Wouldn't this have failed before if that was the case?
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.
Yes this fails: Uncaught TypeError: Cannot read property 'purgeUnmountedComponents' of undefined
. What is the strategy for dealing with these issues?
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.
It would not have failed before because we check if (purgeUnmountedComponents)
, etc, each time.
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.
Oh wait I'm looking at the wrong commit.
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.
That's what I thought, I'm a bit confused as to why they should have been removed if we don't want these errors to show when mixing bundles? I can re-add the safety checks but this will need more testing.
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.
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.
Oh, I didn't think about this. That's a good observation, you're right.
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.
Good point, Ben. 👍
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've updated the PR with some guards. Let me know if there's something I may have missed? I tested locally with React (min) and ReactDOM (non-min) and it's working, although that's not going to test all the code paths that dev code may go through – so please let me know if something doesn't look right.
Curious, did you see any perf changes from this? |
@spicyj It's really hard to say, the +/- % variance against a ms difference wouldn't be too accurate to report. I'll see if there is any more dev related code that can be stripped tomorrow. |
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.
If we're going to check function existence on every use (seems like a good idea to me), can we make the flow type reflect that by making the properties optional? Otherwise you can forget and won't get a flow error.
Files in isomorphic can actually get away without the checks since I believe they will always be in the same env. It might be nice to keep the checks there too though. In that case you might even only need one type for both dev and prod.
Not big fan of checking for existence everywhere. Seems like we can at least wrap it in something like Since we've changed strategies I expect us to break this coupling between isomorphic and renderers soon so that this can be statically resolved safely. |
@sebmarkbage I didn't check everything, I just checked a few known methods that would apply to all other DEV omitted methods. The issue with what you've said is that it still won't work if we mix prod and dev React and ReactDOM packages. That's what I already did, unless I'm missing something you're saying? |
The prod version would be exporting the |
Here's an example of a pattern we've used in a few places:
|
This would bloat the production bundle with quite a few long method names. |
@sebmarkbage But this use case is a bit different right? |
Normally DCE locally or with Rollup without exclude these empty functions. The only difference is if they're exposed between two packages. So we should only export the names that are actually shared between packages. That way only those names are bloating the package. But I don't see why this is needed at all though. The only thing that needs the stack addendum in production is in Fiber. Fiber is only in the renderer. It doesn't rely on any global state. So why doesn't the relevant code just live in the renderer or in the shared folder that gets copied to both isomorphic and renderer? |
src/umd/ReactUMDEntry.js
Outdated
@@ -17,17 +17,8 @@ var React = require('React'); | |||
var ReactUMDEntry = Object.assign({ | |||
__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED: { | |||
ReactCurrentOwner: require('react/lib/ReactCurrentOwner'), | |||
ReactComponentTreeHook: require('react/lib/ReactComponentTreeHook'), |
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.
This part is the piece I'm skeptical about.
We only needed this export because the debug ids are shared. We didn't need to export this in prod before Fiber.
Stacks through getStackAddendumByWorkInProgressFiber
doesn't use the debug ids. I.e. it doesn't rely on global mutable state and therefore can be completely decoupled.
We only need prod stacks in Fiber through getStackAddendumByWorkInProgressFiber
if I understand it correctly.
Therefore, why don't we just put getStackAddendumByWorkInProgressFiber
in the renderer so we don't need this connection between the packages in prod?
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.
It seems to me that if you just split up the ReactComponentTreeHook module into smaller pieces in the shared folder that both ReactComponentTreeHook and the Fiber prod stack thing can depend on the rest will fall naturally from there. ReactComponentTreeHook gets disabled in prod and so the dependencies fall off. Only the small dependencies that you split out (getStackAddendumByWorkInProgressFiber) and its dependencies get embedded in the renderer production package.
…k.js This moves the non DEV methods to a new file called ReactFiberComponentTreeHook, updates existing references to DEV functions to remain inside DEV flags so they do not pull in ReactComponentTreeHook
@@ -1736,7 +1736,7 @@ describe('ReactComponentTreeHook', () => { | |||
expectDev(ReactComponentTreeTestUtils.getRegisteredDisplayNames()).toEqual([]); | |||
}); | |||
|
|||
describe('stack addenda', () => { | |||
describe.only('stack addenda', () => { |
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.
.only
@@ -122,7 +124,7 @@ function validateExplicitKey(element, parentType) { | |||
'%s%s See https://fb.me/react-warning-keys for more information.%s', | |||
currentComponentErrorInfo, | |||
childOwner, | |||
ReactComponentTreeHook.getCurrentStackAddendum(element) | |||
getCurrentStackAddendum && getCurrentStackAddendum(element) |
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.
Why do we need this check, here and in files below? This method is defined unconditionally, and AFAIK all the callers are also DEV-only files.
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.
This looks fine to me, but let's remove the &&
checks because they don't seem to be necessary.
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.
Awesome! So clean.
🚢 |
ReactComponentTreeHook
was originally intended to be a DEV module but has since had production usage with Fiber. This PR aims on splitting the methods out so that we don't ship extra code in production. Flow annotations were added to places that confused Flow into believing methods were missing onReactComponentTreeHook
.Note: I'm a bit unsure about the require path remaining as
react/lib/ReactComponentTreeHook
though @sebmarkbage, do we still need to keep it this way?