Skip to content

Conversation

sebmarkbage
Copy link
Collaborator

Normally these are gated by the whole commitGestureOnRoot path but in the case of an early commit these phases may need to be invoked. Earlier. Those paths weren't gated which I noticed when I started adding code to them.

@react-sizebot
Copy link

Comparing: 6733870...672ecb8

Critical size changes

Includes critical production bundles, as well as any change greater than 2%:

Name +/- Base Current +/- gzip Base gzip Current gzip
oss-stable/react-dom/cjs/react-dom.production.js = 6.68 kB 6.68 kB = 1.83 kB 1.83 kB
oss-stable/react-dom/cjs/react-dom-client.production.js = 518.75 kB 516.17 kB = 92.47 kB 92.15 kB
oss-experimental/react-dom/cjs/react-dom.production.js = 6.69 kB 6.69 kB = 1.83 kB 1.83 kB
oss-experimental/react-dom/cjs/react-dom-client.production.js = 591.76 kB 591.76 kB = 105.36 kB 105.36 kB
facebook-www/ReactDOM-prod.classic.js = 652.39 kB 650.46 kB = 114.72 kB 114.44 kB
facebook-www/ReactDOM-prod.modern.js = 642.71 kB 640.78 kB = 113.15 kB 112.86 kB

Significant size changes

Includes any change greater than 0.2%:

Expand to show
Name +/- Base Current +/- gzip Base gzip Current gzip
facebook-www/ReactDOMTesting-dev.classic.js = 1,194.21 kB 1,191.69 kB = 198.99 kB 198.63 kB
facebook-www/ReactDOMTesting-dev.modern.js = 1,185.07 kB 1,182.55 kB = 197.20 kB 196.85 kB
facebook-www/ReactDOM-dev.classic.js = 1,177.67 kB 1,175.16 kB = 195.13 kB 194.78 kB
facebook-www/ReactDOM-dev.modern.js = 1,168.53 kB 1,166.02 kB = 193.41 kB 193.08 kB
facebook-www/ReactReconciler-dev.classic.js = 820.07 kB 818.08 kB = 127.37 kB 127.17 kB
facebook-www/ReactART-dev.classic.js = 710.89 kB 709.15 kB = 111.38 kB 111.20 kB
facebook-www/ReactReconciler-dev.modern.js = 810.86 kB 808.88 kB = 125.69 kB 125.49 kB
facebook-www/ReactART-dev.modern.js = 701.40 kB 699.65 kB = 109.54 kB 109.35 kB
react-native/implementations/ReactNativeRenderer-dev.fb.js = 677.47 kB 675.72 kB = 110.21 kB 109.99 kB
react-native/implementations/ReactNativeRenderer-dev.js = 650.62 kB 648.88 kB = 106.09 kB 105.88 kB
facebook-www/ReactDOM-profiling.classic.js = 715.32 kB 713.39 kB = 123.87 kB 123.60 kB
facebook-www/ReactDOM-profiling.modern.js = 707.28 kB 705.34 kB = 122.54 kB 122.28 kB
facebook-react-native/react-test-renderer/cjs/ReactTestRenderer-dev.js = 600.52 kB 598.82 kB = 96.71 kB 96.51 kB
oss-experimental/eslint-plugin-react-hooks/cjs/eslint-plugin-react-hooks.production.js = 2,267.61 kB 2,261.18 kB = 358.95 kB 358.11 kB
facebook-www/ReactDOMTesting-prod.classic.js = 666.80 kB 664.86 kB = 118.38 kB 118.10 kB
facebook-www/ReactTestRenderer-dev.classic.js = 577.52 kB 575.83 kB = 93.87 kB 93.72 kB
facebook-www/ReactTestRenderer-dev.modern.js = 577.52 kB 575.83 kB = 93.87 kB 93.72 kB
facebook-www/ReactDOMTesting-prod.modern.js = 657.11 kB 655.18 kB = 116.77 kB 116.48 kB
facebook-www/ReactDOM-prod.classic.js = 652.39 kB 650.46 kB = 114.72 kB 114.44 kB
react-native/implementations/ReactFabric-dev.fb.js = 672.95 kB 670.95 kB = 109.33 kB 109.14 kB
facebook-www/ReactDOM-prod.modern.js = 642.71 kB 640.78 kB = 113.15 kB 112.86 kB
oss-stable/react-test-renderer/cjs/react-test-renderer.development.js = 559.66 kB 557.95 kB = 90.84 kB 90.65 kB
oss-experimental/react-test-renderer/cjs/react-test-renderer.development.js = 559.61 kB 557.90 kB = 90.83 kB 90.64 kB
oss-stable-semver/react-test-renderer/cjs/react-test-renderer.development.js = 559.58 kB 557.87 kB = 90.82 kB 90.63 kB
oss-stable/react-art/cjs/react-art.development.js = 560.65 kB 558.91 kB = 90.16 kB 89.96 kB
oss-stable-semver/react-art/cjs/react-art.development.js = 560.58 kB 558.83 kB = 90.13 kB 89.93 kB
react-native/implementations/ReactFabric-dev.js = 642.51 kB 640.51 kB = 104.73 kB 104.54 kB
oss-stable/react-reconciler/cjs/react-reconciler.development.js = 648.14 kB 646.11 kB = 103.30 kB 103.08 kB
oss-stable-semver/react-reconciler/cjs/react-reconciler.development.js = 648.11 kB 646.09 kB = 103.27 kB 103.05 kB
facebook-react-native/react-dom/cjs/ReactDOMProfiling-dev.js = 1,014.23 kB 1,011.04 kB = 170.44 kB 170.07 kB
react-native/implementations/ReactNativeRenderer-profiling.fb.js = 411.53 kB 410.21 kB = 70.85 kB 70.71 kB
facebook-react-native/react-dom/cjs/ReactDOMClient-dev.js = 997.91 kB 994.71 kB = 167.57 kB 167.21 kB
oss-stable-semver/eslint-plugin-react-hooks/cjs/eslint-plugin-react-hooks.production.js = 2,268.31 kB 2,260.98 kB = 358.98 kB 358.08 kB
oss-stable/eslint-plugin-react-hooks/cjs/eslint-plugin-react-hooks.production.js = 2,268.31 kB 2,260.98 kB = 358.98 kB 358.08 kB
oss-stable-semver/eslint-plugin-react-hooks/cjs/eslint-plugin-react-hooks.development.js = 2,595.58 kB 2,587.19 kB = 370.60 kB 369.62 kB
oss-stable/eslint-plugin-react-hooks/cjs/eslint-plugin-react-hooks.development.js = 2,595.58 kB 2,587.19 kB = 370.60 kB 369.62 kB
oss-stable/react-dom/cjs/react-dom-profiling.development.js = 970.19 kB 966.99 kB = 163.81 kB 163.41 kB
oss-stable-semver/react-dom/cjs/react-dom-profiling.development.js = 970.06 kB 966.86 kB = 163.78 kB 163.38 kB
facebook-www/ReactReconciler-prod.classic.js = 502.84 kB 501.17 kB = 80.27 kB 80.05 kB
react-native/implementations/ReactNativeRenderer-profiling.js = 393.06 kB 391.75 kB = 67.55 kB 67.41 kB
oss-stable/react-dom/cjs/react-dom-client.development.js = 953.75 kB 950.55 kB = 160.98 kB 160.58 kB
oss-stable-semver/react-dom/cjs/react-dom-client.development.js = 953.62 kB 950.42 kB = 160.95 kB 160.55 kB
facebook-www/ReactReconciler-prod.modern.js = 492.57 kB 490.91 kB = 78.67 kB 78.47 kB
react-native/implementations/ReactNativeRenderer-prod.fb.js = 386.05 kB 384.73 kB = 67.03 kB 66.87 kB
facebook-www/ReactART-prod.classic.js = 386.51 kB 385.19 kB = 64.83 kB 64.69 kB
facebook-www/ReactART-prod.modern.js = 376.57 kB 375.24 kB = 63.21 kB 63.08 kB
facebook-react-native/react-test-renderer/cjs/ReactTestRenderer-profiling.js = 357.74 kB 356.47 kB = 61.64 kB 61.52 kB
react-native/implementations/ReactNativeRenderer-prod.js = 367.71 kB 366.40 kB = 63.74 kB 63.62 kB
oss-experimental/eslint-plugin-react-hooks/cjs/eslint-plugin-react-hooks.development.js = 2,595.77 kB 2,586.42 kB = 370.62 kB 369.59 kB
facebook-react-native/react-test-renderer/cjs/ReactTestRenderer-prod.js = 335.56 kB 334.29 kB = 58.45 kB 58.34 kB
react-native/implementations/ReactFabric-profiling.fb.js = 408.29 kB 406.73 kB = 70.26 kB 70.13 kB
react-native/implementations/ReactFabric-profiling.js = 386.00 kB 384.44 kB = 66.35 kB 66.22 kB
oss-stable/react-reconciler/cjs/react-reconciler.profiling.js = 419.72 kB 418.02 kB = 67.47 kB 67.29 kB
oss-stable-semver/react-reconciler/cjs/react-reconciler.profiling.js = 419.70 kB 418.00 kB = 67.44 kB 67.27 kB
react-native/implementations/ReactFabric-prod.fb.js = 382.78 kB 381.22 kB = 66.40 kB 66.25 kB
oss-experimental/react-test-renderer/cjs/react-test-renderer.production.js = 315.08 kB 313.79 kB = 55.24 kB 55.09 kB
oss-stable/react-test-renderer/cjs/react-test-renderer.production.js = 314.90 kB 313.61 kB = 55.21 kB 55.06 kB
oss-stable-semver/react-test-renderer/cjs/react-test-renderer.production.js = 314.83 kB 313.54 kB = 55.19 kB 55.04 kB
react-native/implementations/ReactFabric-prod.js = 360.76 kB 359.20 kB = 62.63 kB 62.51 kB
oss-stable/react-reconciler/cjs/react-reconciler.production.js = 393.54 kB 391.83 kB = 63.80 kB 63.64 kB
oss-stable-semver/react-reconciler/cjs/react-reconciler.production.js = 393.51 kB 391.81 kB = 63.77 kB 63.62 kB
oss-stable/react-art/cjs/react-art.production.js = 302.82 kB 301.49 kB = 51.46 kB 51.34 kB
oss-stable-semver/react-art/cjs/react-art.production.js = 302.75 kB 301.42 kB = 51.44 kB 51.32 kB
facebook-react-native/react-dom/cjs/ReactDOMProfiling-profiling.js = 577.46 kB 574.88 kB = 101.90 kB 101.57 kB
facebook-react-native/react-dom/cjs/ReactDOMClient-profiling.js = 571.52 kB 568.94 kB = 100.74 kB 100.41 kB
facebook-react-native/react-dom/cjs/ReactDOMProfiling-prod.js = 552.17 kB 549.58 kB = 98.07 kB 97.75 kB
oss-stable/react-dom/cjs/react-dom-profiling.profiling.js = 549.16 kB 546.57 kB = 97.17 kB 96.85 kB
oss-stable-semver/react-dom/cjs/react-dom-profiling.profiling.js = 549.03 kB 546.45 kB = 97.14 kB 96.82 kB
facebook-react-native/react-dom/cjs/ReactDOMClient-prod.js = 546.66 kB 544.08 kB = 96.99 kB 96.68 kB
oss-stable/react-dom/cjs/react-dom-client.production.js = 518.75 kB 516.17 kB = 92.47 kB 92.15 kB
oss-stable-semver/react-dom/cjs/react-dom-client.production.js = 518.63 kB 516.04 kB = 92.44 kB 92.12 kB

Generated by 🚫 dangerJS against 05616d2

@sebmarkbage sebmarkbage merged commit 3e95680 into facebook:main Mar 14, 2025
196 checks passed
sebmarkbage added a commit that referenced this pull request Mar 14, 2025
Stacked on #32585 and #32605.

This adds more loops for the phases of "Apply Gesture". It doesn't
implement the interesting bit yet like adding view-transition-names and
measurements. I'll do that in a separate PR to keep reviewing easier.

The three phases of this approach is roughly:

- Clone and apply names to the "old" state.
- Inside startViewTransition: Apply names to the "new" state. Measure
both the "old" and "new" state to know whether to cancel some of them.
Delete the clones which will include all the "old" names.
- After startViewTransition: Restore "new" names back to no
view-transition-name.

Since we don't have any other Effects in these phases we have a bit more
flexibility and we can avoid extra phases that traverse the tree. I've
tried to avoid any additional passes.

An interesting consequence of this approach is that we could measure
both the "old" and "new" state before `startViewTransition`. This would
be more efficient because we wouldn't need to take View Transition
snapshots of parts of the tree that won't actually animate. However,
that would require an extra pass and force layout earlier. It would also
have different semantics from the fire-and-forget View Transitions
because we could optimize better which can be visible. It would also not
account for any late mutations. So I decided to instead let the layout
be computed by painting as usual and then measure both "old" and "new"
inside the startViewTransition instead. Then canceling anything that
doesn't animate to keep it consistent.

Unfortunately, though there's not a lot of code sharing possible in
these phases because the strategy is so different with the cloning and
because the animation is performed in reverse. The "finishedWork" Fiber
represents the "old" state and the "current" Fiber represents the "new"
state.

The most complicated phase is the cloning. I actually ended up having to
make a very different pattern from the other phases and CommitWork in
general. Because we have to clone as we go and also do other things like
apply names and finding pairs, it has more phases. I ended up with an
approach that uses three different loops. The outer one for updated
trees, one for inserted trees that don't need cloning (doesn't include
reappearing offscreen) and one for not updated trees that still need
cloning. Inside each loop it can also be in different phases which I
track with the `visitPhase` enum - this pattern is kind of new.

Additionally, we need to measure the cloned nodes after we've applied
mutations to them and we have to wait until the whole tree is inserted.
We don't have a reference to these DOM elements in the Fiber tree since
that still refers to the original ones. We need to store the cloned
elements somewhere. So I added a temporary field on the
ViewTransitionState to keep track of any clones owned by that
ViewTransition.

When we deep clone an unchanged subtree we don't have DOM element
instances. It wouldn't be quite safe to try to find them from the tree
structure. So we need to avoid the deep clones if we might need DOM
elements. Therefore we keep traversing in the case where we need to find
nested ViewTransition boundaries that are either potentially affected by
layout or a "pair".

For the other two phases the pattern there's a lot of code duplication
since it's slightly different from the commit ones but they at least
follow the same pattern. For the restore phase I was actually able to
reuse most of the code.

I don't love how much code this is.
github-actions bot pushed a commit that referenced this pull request Mar 14, 2025
Normally these are gated by the whole commitGestureOnRoot path but in
the case of an early commit these phases may need to be invoked.
Earlier. Those paths weren't gated which I noticed when I started adding
code to them.

DiffTrain build for [3e95680](3e95680)
github-actions bot pushed a commit that referenced this pull request Mar 14, 2025
Normally these are gated by the whole commitGestureOnRoot path but in
the case of an early commit these phases may need to be invoked.
Earlier. Those paths weren't gated which I noticed when I started adding
code to them.

DiffTrain build for [3e95680](3e95680)
github-actions bot pushed a commit that referenced this pull request Mar 14, 2025
Stacked on #32585 and #32605.

This adds more loops for the phases of "Apply Gesture". It doesn't
implement the interesting bit yet like adding view-transition-names and
measurements. I'll do that in a separate PR to keep reviewing easier.

The three phases of this approach is roughly:

- Clone and apply names to the "old" state.
- Inside startViewTransition: Apply names to the "new" state. Measure
both the "old" and "new" state to know whether to cancel some of them.
Delete the clones which will include all the "old" names.
- After startViewTransition: Restore "new" names back to no
view-transition-name.

Since we don't have any other Effects in these phases we have a bit more
flexibility and we can avoid extra phases that traverse the tree. I've
tried to avoid any additional passes.

An interesting consequence of this approach is that we could measure
both the "old" and "new" state before `startViewTransition`. This would
be more efficient because we wouldn't need to take View Transition
snapshots of parts of the tree that won't actually animate. However,
that would require an extra pass and force layout earlier. It would also
have different semantics from the fire-and-forget View Transitions
because we could optimize better which can be visible. It would also not
account for any late mutations. So I decided to instead let the layout
be computed by painting as usual and then measure both "old" and "new"
inside the startViewTransition instead. Then canceling anything that
doesn't animate to keep it consistent.

Unfortunately, though there's not a lot of code sharing possible in
these phases because the strategy is so different with the cloning and
because the animation is performed in reverse. The "finishedWork" Fiber
represents the "old" state and the "current" Fiber represents the "new"
state.

The most complicated phase is the cloning. I actually ended up having to
make a very different pattern from the other phases and CommitWork in
general. Because we have to clone as we go and also do other things like
apply names and finding pairs, it has more phases. I ended up with an
approach that uses three different loops. The outer one for updated
trees, one for inserted trees that don't need cloning (doesn't include
reappearing offscreen) and one for not updated trees that still need
cloning. Inside each loop it can also be in different phases which I
track with the `visitPhase` enum - this pattern is kind of new.

Additionally, we need to measure the cloned nodes after we've applied
mutations to them and we have to wait until the whole tree is inserted.
We don't have a reference to these DOM elements in the Fiber tree since
that still refers to the original ones. We need to store the cloned
elements somewhere. So I added a temporary field on the
ViewTransitionState to keep track of any clones owned by that
ViewTransition.

When we deep clone an unchanged subtree we don't have DOM element
instances. It wouldn't be quite safe to try to find them from the tree
structure. So we need to avoid the deep clones if we might need DOM
elements. Therefore we keep traversing in the case where we need to find
nested ViewTransition boundaries that are either potentially affected by
layout or a "pair".

For the other two phases the pattern there's a lot of code duplication
since it's slightly different from the commit ones but they at least
follow the same pattern. For the restore phase I was actually able to
reuse most of the code.

I don't love how much code this is.

DiffTrain build for [c4a3b92](c4a3b92)
github-actions bot pushed a commit that referenced this pull request Mar 14, 2025
Stacked on #32585 and #32605.

This adds more loops for the phases of "Apply Gesture". It doesn't
implement the interesting bit yet like adding view-transition-names and
measurements. I'll do that in a separate PR to keep reviewing easier.

The three phases of this approach is roughly:

- Clone and apply names to the "old" state.
- Inside startViewTransition: Apply names to the "new" state. Measure
both the "old" and "new" state to know whether to cancel some of them.
Delete the clones which will include all the "old" names.
- After startViewTransition: Restore "new" names back to no
view-transition-name.

Since we don't have any other Effects in these phases we have a bit more
flexibility and we can avoid extra phases that traverse the tree. I've
tried to avoid any additional passes.

An interesting consequence of this approach is that we could measure
both the "old" and "new" state before `startViewTransition`. This would
be more efficient because we wouldn't need to take View Transition
snapshots of parts of the tree that won't actually animate. However,
that would require an extra pass and force layout earlier. It would also
have different semantics from the fire-and-forget View Transitions
because we could optimize better which can be visible. It would also not
account for any late mutations. So I decided to instead let the layout
be computed by painting as usual and then measure both "old" and "new"
inside the startViewTransition instead. Then canceling anything that
doesn't animate to keep it consistent.

Unfortunately, though there's not a lot of code sharing possible in
these phases because the strategy is so different with the cloning and
because the animation is performed in reverse. The "finishedWork" Fiber
represents the "old" state and the "current" Fiber represents the "new"
state.

The most complicated phase is the cloning. I actually ended up having to
make a very different pattern from the other phases and CommitWork in
general. Because we have to clone as we go and also do other things like
apply names and finding pairs, it has more phases. I ended up with an
approach that uses three different loops. The outer one for updated
trees, one for inserted trees that don't need cloning (doesn't include
reappearing offscreen) and one for not updated trees that still need
cloning. Inside each loop it can also be in different phases which I
track with the `visitPhase` enum - this pattern is kind of new.

Additionally, we need to measure the cloned nodes after we've applied
mutations to them and we have to wait until the whole tree is inserted.
We don't have a reference to these DOM elements in the Fiber tree since
that still refers to the original ones. We need to store the cloned
elements somewhere. So I added a temporary field on the
ViewTransitionState to keep track of any clones owned by that
ViewTransition.

When we deep clone an unchanged subtree we don't have DOM element
instances. It wouldn't be quite safe to try to find them from the tree
structure. So we need to avoid the deep clones if we might need DOM
elements. Therefore we keep traversing in the case where we need to find
nested ViewTransition boundaries that are either potentially affected by
layout or a "pair".

For the other two phases the pattern there's a lot of code duplication
since it's slightly different from the commit ones but they at least
follow the same pattern. For the restore phase I was actually able to
reuse most of the code.

I don't love how much code this is.

DiffTrain build for [c4a3b92](c4a3b92)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
CLA Signed React Core Team Opened by a member of the React Core Team
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants