react icon indicating copy to clipboard operation
react copied to clipboard

Implement Persistent Host Instances

Open gnoff opened this issue 1 year ago • 2 comments

Background

For a variety of reasons html, head, and body nodes are special in the browser and historically there has not been much heed given to these elements in particular within react-dom which has led to some limitations of interoperability with externals systems such as 3rd party scripts and browser extensions. Additionally new features in React will require some special handling of these instances to be possible.

The main issue has to do with instance lifecycles and React's total ownership of the nodes within it's tree. For the head for instance, if there are stylesheet links inserted by 3rd parties, React might unmount those nodes, or reinsert them somewhere else causing a new fetch and temporary style unloading. Additionally useInsertionEffect runs in the mutation phase but if we are going to be replacing the head it will be unmounted during the time these effects are run so you can't always inject styles when you expected to be able to.

The historical advice has been to render into an element in the body that isn't like to be targeted by any external systems however this conflicts with the guidance for using streaming rendering available in React 18 where it is going to be common that React owns the entire document.

General Approach

To solve these issues, this PR introduces a Psuedo sub-type of HostComponent for react-dom only called a Persistent HostComponent. When we detect that a HostComponent is of the persistent variety different insertion and update semantics are enforced.

  1. All Persistent HostComponents need to be placed individually, they will not be appended to a parent fiber that has a Placement.
  2. During completeWork Persistent HostComponents accumulate HostComponent children into a temporary host.
  3. During commitWork Persistent HostComponents defer insertion to an ad-hoc placement function which is responsible for guaranteeing this desired persistent semantics

react-dom Approach

In the case of react-dom there are three persistent instances, document.documentElement, document.head, document.body. If you render a <html /> | <head /> | <body /> in your tree they each will bind to the already existing Element and not recreate a new one.

Key Constraints:

  • none of the 3 persistent nodes will be unmounted at any time
  • none of the 3 persistent nodes will be ever change referential identity
  • Head and Body nodes will never reposition, reorder, or otherwise alter the placement of style-related nodes outside of React

insertion stability (Body and Head only)

In Body and Head, we expect there to be Nodes that were created by systems other than React. Most items can safely be removed once loaded because they are fire-and-forget, such as scripts. However <style> and <link rel="stylesheet"> nodes need to be retained for proper functioning of a site and as such we expect that these two instances will have a number of Nodes outside the purview of React's runtime which change insertion semantics.

The main challenge here is when React thinks you are placing an instance in tail position it will use appendChild however it may be intentional that 3rd party style nodes actually exist after the React tree. To get around this issue both Body and Head manage a special Comment Node which acts as an insertion anchor. When you might otherwise append, if this insertion anchor exists it will insertBefore instead. This anchor persists even through unmounting so if you completely replace your head or body the new contents will be emitted into the same slot as the old one was deleted out of. This means we can not only support trailing extra nodes but preceding nodes as well. (This will be more important when SSR starts to support first class stylesheet management)

Open Questions

  1. What do we do about props on persistent instances when react unmounts them? We leave the 3rdparty children in place and we could eliminate the props but we don't know how to restore the attributes to what they were before React mounted, nor is that necessarily correct. The current implementation leaves the props in place
  2. Is there any special consideration for event handlers that needs to take place?
  3. Have I avoided the hot paths enough?
  4. Is it worth the code size?
  5. Will this feature ever make sense for any other renderer?

gnoff avatar Jul 29 '22 23:07 gnoff

Comparing: 6ef466c681c987493432608be56f2e01f94268d7...ec38036b13694a8bb6d14c4409ad3890eefd532c

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.min.js +0.16% 134.28 kB 134.50 kB +0.10% 42.94 kB 42.98 kB
oss-experimental/react-dom/cjs/react-dom.production.min.js +1.59% 140.35 kB 142.57 kB +1.71% 44.74 kB 45.50 kB
facebook-www/ReactDOM-prod.classic.js +0.10% 474.44 kB 474.89 kB +0.11% 84.87 kB 84.97 kB
facebook-www/ReactDOM-prod.modern.js +0.09% 459.68 kB 460.11 kB +0.10% 82.63 kB 82.71 kB
facebook-www/ReactDOMForked-prod.classic.js +0.10% 474.44 kB 474.89 kB +0.11% 84.88 kB 84.97 kB

Significant size changes

Includes any change greater than 0.2%:

Expand to show
Name +/- Base Current +/- gzip Base gzip Current gzip
oss-experimental/react-dom/umd/react-dom.production.min.js +1.60% 140.38 kB 142.63 kB +1.57% 45.40 kB 46.12 kB
oss-experimental/react-dom/cjs/react-dom.production.min.js +1.59% 140.35 kB 142.57 kB +1.71% 44.74 kB 45.50 kB
oss-experimental/react-dom/umd/react-dom.profiling.min.js +1.50% 149.24 kB 151.48 kB +1.41% 47.66 kB 48.34 kB
oss-experimental/react-dom/cjs/react-dom.profiling.min.js +1.48% 149.87 kB 152.09 kB +1.72% 47.08 kB 47.90 kB
oss-stable-semver/react-reconciler/cjs/react-reconciler-reflection.development.js +1.45% 17.97 kB 18.23 kB +1.37% 5.19 kB 5.26 kB
oss-stable/react-reconciler/cjs/react-reconciler-reflection.development.js +1.45% 17.97 kB 18.23 kB +1.37% 5.19 kB 5.26 kB
oss-experimental/react-reconciler/cjs/react-reconciler.production.min.js +1.28% 100.87 kB 102.17 kB +0.92% 30.81 kB 31.09 kB
oss-experimental/react-reconciler/cjs/react-reconciler.profiling.min.js +1.18% 109.77 kB 111.06 kB +0.94% 32.96 kB 33.27 kB
oss-experimental/react-dom/cjs/react-dom.development.js +1.01% 1,089.28 kB 1,100.32 kB +0.83% 242.94 kB 244.95 kB
oss-experimental/react-dom/umd/react-dom.development.js +1.01% 1,142.60 kB 1,154.17 kB +0.85% 245.62 kB 247.71 kB
oss-experimental/react-reconciler/cjs/react-reconciler-reflection.production.min.js +0.90% 2.67 kB 2.69 kB +0.35% 1.14 kB 1.14 kB
oss-experimental/react-reconciler/cjs/react-reconciler.development.js +0.89% 805.91 kB 813.11 kB +0.58% 170.76 kB 171.75 kB
oss-experimental/react-reconciler/cjs/react-reconciler-reflection.development.js +0.60% 18.33 kB 18.44 kB +0.32% 5.27 kB 5.28 kB
oss-experimental/react-art/cjs/react-art.production.min.js +0.43% 89.32 kB 89.71 kB +0.29% 27.61 kB 27.69 kB
oss-experimental/react-art/umd/react-art.production.min.js +0.43% 125.16 kB 125.69 kB +0.38% 38.80 kB 38.95 kB
oss-experimental/react-art/cjs/react-art.development.js +0.42% 738.25 kB 741.38 kB +0.36% 158.48 kB 159.05 kB
oss-stable-semver/react-dom/cjs/react-dom-test-utils.development.js +0.39% 55.07 kB 55.29 kB +0.41% 15.99 kB 16.06 kB
oss-stable/react-dom/cjs/react-dom-test-utils.development.js +0.39% 55.07 kB 55.29 kB +0.41% 15.99 kB 16.06 kB
oss-experimental/react-art/umd/react-art.development.js +0.39% 844.28 kB 847.57 kB +0.33% 176.65 kB 177.24 kB
oss-stable-semver/react-dom/umd/react-dom-test-utils.development.js +0.38% 58.15 kB 58.37 kB +0.40% 16.25 kB 16.31 kB
oss-stable/react-dom/umd/react-dom-test-utils.development.js +0.38% 58.15 kB 58.37 kB +0.40% 16.25 kB 16.31 kB
oss-stable-semver/react-reconciler/cjs/react-reconciler.production.min.js +0.33% 95.45 kB 95.76 kB +0.24% 29.28 kB 29.35 kB
oss-stable/react-reconciler/cjs/react-reconciler.production.min.js +0.33% 95.47 kB 95.78 kB +0.25% 29.30 kB 29.37 kB
oss-stable-semver/react-reconciler/cjs/react-reconciler.development.js +0.33% 774.24 kB 776.76 kB +0.30% 164.35 kB 164.85 kB
oss-stable/react-reconciler/cjs/react-reconciler.development.js +0.33% 774.26 kB 776.78 kB +0.30% 164.37 kB 164.87 kB
oss-stable-semver/react-reconciler/cjs/react-reconciler.profiling.min.js +0.30% 104.33 kB 104.64 kB +0.26% 31.51 kB 31.59 kB
oss-stable/react-reconciler/cjs/react-reconciler.profiling.min.js +0.30% 104.35 kB 104.67 kB +0.26% 31.53 kB 31.61 kB
facebook-www/ReactTestUtils-dev.modern.js +0.30% 49.53 kB 49.68 kB +0.46% 13.85 kB 13.92 kB
facebook-www/ReactTestUtils-dev.classic.js +0.30% 49.53 kB 49.68 kB +0.46% 13.85 kB 13.91 kB
oss-stable-semver/react-test-renderer/cjs/react-test-renderer.production.min.js +0.23% 91.49 kB 91.71 kB +0.12% 28.26 kB 28.29 kB
oss-stable/react-test-renderer/cjs/react-test-renderer.production.min.js +0.23% 91.52 kB 91.73 kB +0.12% 28.26 kB 28.29 kB
oss-stable-semver/react-test-renderer/umd/react-test-renderer.production.min.js +0.23% 91.74 kB 91.96 kB +0.11% 28.69 kB 28.72 kB
oss-stable/react-test-renderer/umd/react-test-renderer.production.min.js +0.23% 91.76 kB 91.98 kB +0.11% 28.69 kB 28.72 kB
facebook-www/ReactDOMTesting-dev.modern.js +0.23% 1,078.29 kB 1,080.72 kB +0.24% 240.40 kB 240.96 kB
oss-experimental/react-test-renderer/cjs/react-test-renderer.production.min.js +0.22% 96.24 kB 96.45 kB +0.16% 29.59 kB 29.63 kB
oss-experimental/react-test-renderer/umd/react-test-renderer.production.min.js +0.22% 96.48 kB 96.70 kB +0.23% 29.95 kB 30.02 kB
oss-experimental/react-dom/cjs/react-dom-unstable_testing.production.min.js +0.22% 145.10 kB 145.43 kB +0.15% 46.59 kB 46.66 kB
facebook-www/ReactDOMTesting-dev.classic.js +0.22% 1,106.91 kB 1,109.37 kB +0.22% 246.22 kB 246.76 kB
oss-stable-semver/react-test-renderer/umd/react-test-renderer.development.js +0.22% 712.82 kB 714.36 kB +0.24% 148.96 kB 149.31 kB
oss-stable/react-test-renderer/umd/react-test-renderer.development.js +0.22% 712.84 kB 714.39 kB +0.24% 148.98 kB 149.34 kB
oss-experimental/react-dom/cjs/react-dom-unstable_testing.development.js +0.21% 1,079.32 kB 1,081.61 kB +0.23% 240.90 kB 241.45 kB
oss-stable-semver/react-test-renderer/cjs/react-test-renderer.development.js +0.21% 680.45 kB 681.89 kB +0.25% 147.36 kB 147.73 kB
oss-stable/react-test-renderer/cjs/react-test-renderer.development.js +0.21% 680.47 kB 681.92 kB +0.25% 147.38 kB 147.75 kB
oss-stable-semver/react-art/cjs/react-art.production.min.js +0.21% 84.22 kB 84.40 kB +0.09% 26.19 kB 26.22 kB
oss-stable/react-art/cjs/react-art.production.min.js +0.21% 84.25 kB 84.42 kB +0.09% 26.19 kB 26.22 kB
oss-experimental/react-test-renderer/umd/react-test-renderer.development.js +0.21% 743.10 kB 744.65 kB +0.24% 154.68 kB 155.05 kB
oss-experimental/react-test-renderer/cjs/react-test-renderer.development.js +0.20% 709.29 kB 710.74 kB +0.24% 153.02 kB 153.38 kB
facebook-react-native/react-test-renderer/cjs/ReactTestRenderer-dev.js +0.20% 723.31 kB 724.77 kB +0.22% 154.57 kB 154.92 kB

Generated by :no_entry_sign: dangerJS against ec38036b13694a8bb6d14c4409ad3890eefd532c

sizebot avatar Jul 29 '22 23:07 sizebot

Hear

Binaryfxv avatar Jul 31 '22 19:07 Binaryfxv

In terms of naming, I don't think we can use Persistent for this since we have a whole Persistent Mode that means other things and the term comes up a lot of other places. So this would have to be called something else.

sebmarkbage avatar Aug 02 '22 01:08 sebmarkbage