Skip to content

Commit a1c26b1

Browse files
committed
Model Float on Hoistables semantics (#26106)
## Hoistables In the original implementation of Float, all hoisted elements were treated like Resources. They had deduplication semantics and hydrated based on a key. This made certain kinds of hoists very challenging such as sequences of meta tags for `og:image:...` metadata. The reason is each tag along is not dedupable based on only it's intrinsic properties. two identical tags may need to be included and hoisted together with preceding meta tags that describe a semantic object with a linear set of html nodes. It was clear that the concept of Browser Resources (stylesheets / scripts / preloads) did not extend universally to all hositable tags (title, meta, other links, etc...) Additionally while Resources benefit from deduping they suffer an inability to update because while we may have multiple rendered elements that refer to a single Resource it isn't unambiguous which element owns the props on the underlying resource. We could try merging props, but that is still really hard to reason about for authors. Instead we restrict Resource semantics to freezing the props at the time the Resource is first constructed and warn if you attempt to render the same Resource with different props via another rendered element or by updating an existing element for that Resource. This lack of updating restriction is however way more extreme than necessary for instances that get hoisted but otherwise do not dedupe; where there is a well defined DOM instance for each rendered element. We should be able to update props on these instances. Hoistable is a generalization of what Float tries to model for hoisting. Instead of assuming every hoistable element is a Resource we now have two distinct categories, hoistable elements and hoistable resources. As one might guess the former has semantics that match regular Host Components except the placement of the node is usually in the <head>. The latter continues to behave how the original implementation of HostResource behaved with the first iteration of Float ### Hoistable Element On the server hoistable elements render just like regular tags except the output is stored in special queues that can be emitted in the stream earlier than they otherwise would be if rendered in place. This also allow for instance the ability to render a hoistable before even rendering the <html> tag because the queues for hoistable elements won't flush until after we have flushed the preamble (`<DOCTYPE html><html><head>`). On the client, hoistable elements largely operate like HostComponents. The most notable difference is in the hydration strategy. If we are hydrating and encounter a hoistable element we will look for all tags in the document that could potentially be a match and we check whether the attributes match the props for this particular instance. We also do this in the commit phase rather than the render phase. The reason hydration can be done for HostComponents in render is the instance will be removed from the document if hydration fails so mutating it in render is safe. For hoistables the nodes are not in a hydration boundary (Root or SuspenseBoundary at time of writing) and thus if hydration fails and we may have an instance marked as bound to some Fiber when that Fiber never commits. Moving the hydration matching to commit ensures we will always succeed in pairing the hoisted DOM instance with a Fiber that has committed. ### Hoistable Resource On the server and client the semantics of Resources are largely the same they just don't apply to title, meta, and most link tags anymore. Resources hoist and dedupe via an `href` key and are ref counted. In a future update we will add a garbage collector so we can clean up Resources that no longer have any references ## `<style>` support In earlier implementations there was no support for <style> tags. This PR adds support for treating `<style href="..." precedence="...">...</style>` as a Resource analagous to `<link rel="stylesheet" href="..." precedence="..." />` It may seem odd at first to require an href to get Resource semantics for a style tag. The rationale is that these are for inlining of actual external stylesheets as an optimization and for URI like scoping of inline styles for css-in-js libraries. The href indicates that the key space for `<style>` and `<link rel="stylesheet" />` Resources is shared. and the precedence is there to allow for interleaving of both kinds of Style resources. This is an advanced feature that we do not expect most app developers to use directly but will be quite handy for various styling libraries and for folks who want to inline as much as possible once Fizz supports this feature. ## refactor notes * HostResource Fiber type is renamed HostHoistable to reflect the generalization of the concept * The Resource object representation is modified to reduce hidden class checks and to use less memory overall * The thing that distinguishes a resource from an element is whether the Fiber has a memoizedState. If it does, it will use resource semantics, otherwise element semantics * The time complexity of matching hositable elements for hydration should be improved DiffTrain build for [6396b66](6396b66) [View git log for this commit](https://github.com/facebook/react/commits/6396b664118442f3c2eae7bf13732fcb27bda98f)
1 parent 6c6f5d0 commit a1c26b1

34 files changed

+18420
-21194
lines changed

compiled/facebook-www/REVISION

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
ef9f6e77b8ef968eee659ae797da4bdc07bbbde3
1+
6396b664118442f3c2eae7bf13732fcb27bda98f
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
ef9f6e77b8ef968eee659ae797da4bdc07bbbde3
1+
6396b664118442f3c2eae7bf13732fcb27bda98f

compiled/facebook-www/React-dev.classic.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ if (
2727
}
2828
"use strict";
2929

30-
var ReactVersion = "18.3.0-www-classic-ef9f6e77b-20230209";
30+
var ReactVersion = "18.3.0-www-classic-6396b6641-20230209";
3131

3232
// ATTENTION
3333
// When adding new symbols to this file,

compiled/facebook-www/React-dev.modern.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ if (
2727
}
2828
"use strict";
2929

30-
var ReactVersion = "18.3.0-www-modern-ef9f6e77b-20230209";
30+
var ReactVersion = "18.3.0-www-modern-6396b6641-20230209";
3131

3232
// ATTENTION
3333
// When adding new symbols to this file,

compiled/facebook-www/React-prod.classic.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -646,4 +646,4 @@ exports.useSyncExternalStore = function (
646646
);
647647
};
648648
exports.useTransition = useTransition;
649-
exports.version = "18.3.0-www-classic-ef9f6e77b-20230209";
649+
exports.version = "18.3.0-www-classic-6396b6641-20230209";

compiled/facebook-www/React-prod.modern.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -638,4 +638,4 @@ exports.useSyncExternalStore = function (
638638
);
639639
};
640640
exports.useTransition = useTransition;
641-
exports.version = "18.3.0-www-modern-ef9f6e77b-20230209";
641+
exports.version = "18.3.0-www-modern-6396b6641-20230209";

compiled/facebook-www/React-profiling.classic.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -657,7 +657,7 @@ exports.useSyncExternalStore = function (
657657
);
658658
};
659659
exports.useTransition = useTransition;
660-
exports.version = "18.3.0-www-classic-ef9f6e77b-20230209";
660+
exports.version = "18.3.0-www-classic-6396b6641-20230209";
661661

662662
/* global __REACT_DEVTOOLS_GLOBAL_HOOK__ */
663663
if (

compiled/facebook-www/React-profiling.modern.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -649,7 +649,7 @@ exports.useSyncExternalStore = function (
649649
);
650650
};
651651
exports.useTransition = useTransition;
652-
exports.version = "18.3.0-www-modern-ef9f6e77b-20230209";
652+
exports.version = "18.3.0-www-modern-6396b6641-20230209";
653653

654654
/* global __REACT_DEVTOOLS_GLOBAL_HOOK__ */
655655
if (

compiled/facebook-www/ReactART-dev.classic.js

Lines changed: 28 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,7 @@ function _assertThisInitialized(self) {
6969
return self;
7070
}
7171

72-
var ReactVersion = "18.3.0-www-classic-ef9f6e77b-20230209";
72+
var ReactVersion = "18.3.0-www-classic-6396b6641-20230209";
7373

7474
var LegacyRoot = 0;
7575
var ConcurrentRoot = 1;
@@ -229,7 +229,7 @@ var OffscreenComponent = 22;
229229
var LegacyHiddenComponent = 23;
230230
var CacheComponent = 24;
231231
var TracingMarkerComponent = 25;
232-
var HostResource = 26;
232+
var HostHoistable = 26;
233233
var HostSingleton = 27;
234234

235235
// ATTENTION
@@ -431,7 +431,7 @@ function getComponentNameFromFiber(fiber) {
431431
case Fragment:
432432
return "Fragment";
433433

434-
case HostResource:
434+
case HostHoistable:
435435
case HostSingleton:
436436
case HostComponent:
437437
// Host component type is the display name (e.g. "div", "View")
@@ -877,7 +877,7 @@ function findCurrentHostFiberImpl(node) {
877877

878878
if (
879879
tag === HostComponent ||
880-
tag === HostResource ||
880+
tag === HostHoistable ||
881881
tag === HostSingleton ||
882882
tag === HostText
883883
) {
@@ -4962,7 +4962,7 @@ function describeFiber(fiber) {
49624962
var source = fiber._debugSource;
49634963

49644964
switch (fiber.tag) {
4965-
case HostResource:
4965+
case HostHoistable:
49664966
case HostSingleton:
49674967
case HostComponent:
49684968
return describeBuiltInComponentFrame(fiber.type);
@@ -15856,7 +15856,6 @@ function attemptEarlyBailoutIfNoScheduledUpdate(
1585615856
}
1585715857
break;
1585815858

15859-
case HostResource:
1586015859
case HostSingleton:
1586115860
case HostComponent:
1586215861
pushHostContext(workInProgress);
@@ -16202,7 +16201,7 @@ function beginWork(current, workInProgress, renderLanes) {
1620216201
case HostRoot:
1620316202
return updateHostRoot(current, workInProgress, renderLanes);
1620416203

16205-
case HostResource:
16204+
case HostHoistable:
1620616205

1620716206
// eslint-disable-next-line no-fallthrough
1620816207

@@ -17911,7 +17910,7 @@ function completeWork(current, workInProgress, renderLanes) {
1791117910
return null;
1791217911
}
1791317912

17914-
case HostResource:
17913+
case HostHoistable:
1791517914
// eslint-disable-next-line-no-fallthrough
1791617915

1791717916
case HostSingleton:
@@ -18601,7 +18600,7 @@ function unwindWork(current, workInProgress, renderLanes) {
1860118600
return null;
1860218601
}
1860318602

18604-
case HostResource:
18603+
case HostHoistable:
1860518604
case HostSingleton:
1860618605
case HostComponent: {
1860718606
// TODO: popHydrationState
@@ -18732,7 +18731,7 @@ function unwindInterruptedWork(current, interruptedWork, renderLanes) {
1873218731
break;
1873318732
}
1873418733

18735-
case HostResource:
18734+
case HostHoistable:
1873618735
case HostSingleton:
1873718736
case HostComponent: {
1873818737
popHostContext(interruptedWork);
@@ -19178,7 +19177,7 @@ function commitBeforeMutationEffectsOnFiber(finishedWork) {
1917819177
}
1917919178

1918019179
case HostComponent:
19181-
case HostResource:
19180+
case HostHoistable:
1918219181
case HostSingleton:
1918319182
case HostText:
1918419183
case HostPortal:
@@ -19774,7 +19773,7 @@ function commitLayoutEffectOnFiber(
1977419773
break;
1977519774
}
1977619775

19777-
case HostResource:
19776+
case HostHoistable:
1977819777
// eslint-disable-next-line-no-fallthrough
1977919778

1978019779
case HostSingleton:
@@ -20224,7 +20223,7 @@ function commitAttachRef(finishedWork) {
2022420223
var instanceToUse;
2022520224

2022620225
switch (finishedWork.tag) {
20227-
case HostResource:
20226+
case HostHoistable:
2022820227
case HostSingleton:
2022920228
case HostComponent:
2023020229
instanceToUse = getPublicInstance(instance);
@@ -20600,7 +20599,7 @@ function commitDeletionEffectsOnFiber(
2060020599
// that don't modify the stack.
2060120600

2060220601
switch (deletedFiber.tag) {
20603-
case HostResource:
20602+
case HostHoistable:
2060420603
// eslint-disable-next-line no-fallthrough
2060520604

2060620605
case HostSingleton:
@@ -21106,7 +21105,7 @@ function commitMutationEffectsOnFiber(finishedWork, root, lanes) {
2110621105
return;
2110721106
}
2110821107

21109-
case HostResource:
21108+
case HostHoistable:
2111021109
// eslint-disable-next-line-no-fallthrough
2111121110

2111221111
case HostSingleton:
@@ -21151,14 +21150,14 @@ function commitMutationEffectsOnFiber(finishedWork, root, lanes) {
2115121150
var oldProps = current !== null ? current.memoizedProps : newProps;
2115221151
var type = finishedWork.type; // TODO: Type the updateQueue to be specific to host components.
2115321152

21154-
var updatePayload = finishedWork.updateQueue;
21153+
var _updatePayload = finishedWork.updateQueue;
2115521154
finishedWork.updateQueue = null;
2115621155

21157-
if (updatePayload !== null) {
21156+
if (_updatePayload !== null) {
2115821157
try {
2115921158
commitUpdate(
2116021159
_instance2,
21161-
updatePayload,
21160+
_updatePayload,
2116221161
type,
2116321162
oldProps,
2116421163
newProps,
@@ -21211,15 +21210,19 @@ function commitMutationEffectsOnFiber(finishedWork, root, lanes) {
2121121210
}
2121221211

2121321212
case HostRoot: {
21214-
recursivelyTraverseMutationEffects(root, finishedWork);
21215-
commitReconciliationEffects(finishedWork);
21213+
{
21214+
recursivelyTraverseMutationEffects(root, finishedWork);
21215+
commitReconciliationEffects(finishedWork);
21216+
}
2121621217

2121721218
return;
2121821219
}
2121921220

2122021221
case HostPortal: {
21221-
recursivelyTraverseMutationEffects(root, finishedWork);
21222-
commitReconciliationEffects(finishedWork);
21222+
{
21223+
recursivelyTraverseMutationEffects(root, finishedWork);
21224+
commitReconciliationEffects(finishedWork);
21225+
}
2122321226

2122421227
return;
2122521228
}
@@ -21489,7 +21492,7 @@ function disappearLayoutEffects(finishedWork) {
2148921492
break;
2149021493
}
2149121494

21492-
case HostResource:
21495+
case HostHoistable:
2149321496
case HostSingleton:
2149421497
case HostComponent: {
2149521498
// TODO (Offscreen) Check: flags & RefStatic
@@ -21591,7 +21594,7 @@ function reappearLayoutEffects(
2159121594
// ...
2159221595
// }
2159321596

21594-
case HostResource:
21597+
case HostHoistable:
2159521598
case HostSingleton:
2159621599
case HostComponent: {
2159721600
recursivelyTraverseReappearLayoutEffects(
@@ -26748,7 +26751,7 @@ function findChildHostInstancesForFiberShallowly(fiber, hostInstances) {
2674826751
var foundHostInstances = false;
2674926752

2675026753
while (true) {
26751-
if (node.tag === HostComponent || node.tag === HostResource || false) {
26754+
if (node.tag === HostComponent || node.tag === HostHoistable || false) {
2675226755
// We got a match.
2675326756
foundHostInstances = true;
2675426757
hostInstances.add(node.stateNode); // There may still be more, so keep searching.

0 commit comments

Comments
 (0)