@samitouri / QOS-React-2 / commits / 343710c923

[Fizz] Fragments and Iterable support (#21228)

Sebastian Markbåge committed Apr 10, 2021 at 15:50 UTC 343710c9230d643ec442acef4634e380c1f0bce9
2 files changed +102 -11
packages/react-dom/src/__tests__/ReactDOMFizzServer-test.js
+9 -7
@@ -351,10 +351,12 @@ describe('ReactDOMFizzServer', () => {
351 await act(async () => {
352 const {startWriting} = ReactDOMFizzServer.pipeToNodeWritable(
353 <Suspense fallback={<Text text="Loading A..." />}>
354 - <Text text="This will show A: " />
355 - <div>
356 - <AsyncText text="A" />
357 - </div>
354 + <>
355 + <Text text="This will show A: " />
356 + <div>
357 + <AsyncText text="A" />
358 + </div>
359 + </>
360 </Suspense>,
361 writableA,
362 {
@@ -432,11 +434,11 @@ describe('ReactDOMFizzServer', () => {
434 }
435
436 function AsyncPath({id}) {
435 - return <path id={readText(id)}>{[]}</path>;
437 + return <path id={readText(id)} />;
438 }
439
440 function AsyncMi({id}) {
439 - return <mi id={readText(id)}>{[]}</mi>;
441 + return <mi id={readText(id)} />;
442 }
443
444 function App() {
@@ -601,7 +603,7 @@ describe('ReactDOMFizzServer', () => {
603 // @gate experimental
604 it('can stream into an SVG container', async () => {
605 function AsyncPath({id}) {
604 - return <path id={readText(id)}>{[]}</path>;
606 + return <path id={readText(id)} />;
607 }
608
609 function App() {
packages/react-server/src/ReactFizzServer.js
+93 -4
@@ -62,6 +62,12 @@ import {
62 REACT_PORTAL_TYPE,
63 REACT_LAZY_TYPE,
64 REACT_SUSPENSE_TYPE,
65 + REACT_LEGACY_HIDDEN_TYPE,
66 + REACT_DEBUG_TRACING_MODE_TYPE,
67 + REACT_STRICT_MODE_TYPE,
68 + REACT_PROFILER_TYPE,
69 + REACT_SUSPENSE_LIST_TYPE,
70 + REACT_FRAGMENT_TYPE,
71 } from 'shared/ReactSymbols';
72 import ReactSharedInternals from 'shared/ReactSharedInternals';
73 import {
@@ -521,6 +527,8 @@ const didWarnAboutContextTypeOnFunctionComponent = {};
527 const didWarnAboutGetDerivedStateOnFunctionComponent = {};
528 let didWarnAboutReassigningProps = false;
529 const didWarnAboutDefaultPropsOnFunctionComponent = {};
530 +let didWarnAboutGenerators = false;
531 +let didWarnAboutMaps = false;
532
533 // This would typically be a function component but we still support module pattern
534 // components for some reason.
@@ -701,10 +709,67 @@ function renderElement(
709 }
710 } else if (typeof type === 'string') {
711 renderHostElement(request, task, type, props);
704 - } else if (type === REACT_SUSPENSE_TYPE) {
705 - renderSuspenseBoundary(request, task, props);
712 } else {
707 - throw new Error('Not yet implemented element type.');
713 + switch (type) {
714 + // TODO: LegacyHidden acts the same as a fragment. This only works
715 + // because we currently assume that every instance of LegacyHidden is
716 + // accompanied by a host component wrapper. In the hidden mode, the host
717 + // component is given a `hidden` attribute, which ensures that the
718 + // initial HTML is not visible. To support the use of LegacyHidden as a
719 + // true fragment, without an extra DOM node, we would have to hide the
720 + // initial HTML in some other way.
721 + // TODO: Add REACT_OFFSCREEN_TYPE here too with the same capability.
722 + case REACT_LEGACY_HIDDEN_TYPE:
723 + case REACT_DEBUG_TRACING_MODE_TYPE:
724 + case REACT_STRICT_MODE_TYPE:
725 + case REACT_PROFILER_TYPE:
726 + case REACT_SUSPENSE_LIST_TYPE: // TODO: SuspenseList should control the boundaries.
727 + case REACT_FRAGMENT_TYPE: {
728 + renderNodeDestructive(request, task, props.children);
729 + break;
730 + }
731 + case REACT_SUSPENSE_TYPE: {
732 + renderSuspenseBoundary(request, task, props);
733 + break;
734 + }
735 + default: {
736 + throw new Error('Not yet implemented element type.');
737 + }
738 + }
739 + }
740 +}
741 +
742 +function validateIterable(iterable, iteratorFn: Function): void {
743 + if (__DEV__) {
744 + // We don't support rendering Generators because it's a mutation.
745 + // See https://github.com/facebook/react/issues/12995
746 + if (
747 + typeof Symbol === 'function' &&
748 + // $FlowFixMe Flow doesn't know about toStringTag
749 + iterable[Symbol.toStringTag] === 'Generator'
750 + ) {
751 + if (!didWarnAboutGenerators) {
752 + console.error(
753 + 'Using Generators as children is unsupported and will likely yield ' +
754 + 'unexpected results because enumerating a generator mutates it. ' +
755 + 'You may convert it to an array with `Array.from()` or the ' +
756 + '`[...spread]` operator before rendering. Keep in mind ' +
757 + 'you might need to polyfill these features for older browsers.',
758 + );
759 + }
760 + didWarnAboutGenerators = true;
761 + }
762 +
763 + // Warn about using Maps as children
764 + if ((iterable: any).entries === iteratorFn) {
765 + if (!didWarnAboutMaps) {
766 + console.error(
767 + 'Using Maps as children is not supported. ' +
768 + 'Use an array of keyed ReactElements instead.',
769 + );
770 + }
771 + didWarnAboutMaps = true;
772 + }
773 }
774 }
775
@@ -756,7 +821,30 @@ function renderNodeDestructive(
821
822 const iteratorFn = getIteratorFn(node);
823 if (iteratorFn) {
759 - throw new Error('Not yet implemented node type.');
824 + if (__DEV__) {
825 + validateIterable(node, iteratorFn());
826 + }
827 + const iterator = iteratorFn.call(node);
828 + if (iterator) {
829 + let step = iterator.next();
830 + // If there are not entries, we need to push an empty so we start by checking that.
831 + if (!step.done) {
832 + do {
833 + // Recursively render the rest. We need to use the non-destructive form
834 + // so that we can safely pop back up and render the sibling if something
835 + // suspends.
836 + renderNode(request, task, step.value);
837 + step = iterator.next();
838 + } while (!step.done);
839 + return;
840 + }
841 + }
842 + pushEmpty(
843 + task.blockedSegment.chunks,
844 + request.responseState,
845 + task.assignID,
846 + );
847 + task.assignID = null;
848 }
849
850 const childString = Object.prototype.toString.call(node);
@@ -805,6 +893,7 @@ function renderNodeDestructive(
893
894 // Any other type is assumed to be empty.
895 pushEmpty(task.blockedSegment.chunks, request.responseState, task.assignID);
896 + task.assignID = null;
897 }
898
899 function spawnNewSuspendedTask(