Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 5663635

Browse files
leebyronsebmarkbage
authored andcommitted
Partial support for React.lazy() in server renderer. (facebook#16383)
Provides partial support for React.lazy() components from the existing PartialRenderer server-side renderer. Lazy components which are already resolved (or rejected), perhaps with something like `react-ssr-prepass`, can be continued into synchronously. If they have not yet been initialized, they'll be initialized before checking, opening the possibility to exploit this capability with a babel transform. If they're pending (which will typically be the case for a just initialized async ctor) then the existing invariant continues to be thrown.
1 parent 6fbe630 commit 5663635

File tree

4 files changed

+120
-63
lines changed

4 files changed

+120
-63
lines changed

packages/react-dom/src/__tests__/ReactServerRendering-test.js

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -554,6 +554,34 @@ describe('ReactDOMServer', () => {
554554
),
555555
).not.toThrow();
556556
});
557+
558+
it('renders synchronously resolved lazy component', () => {
559+
const LazyFoo = React.lazy(() => ({
560+
then(resolve) {
561+
resolve({
562+
default: function Foo({id}) {
563+
return <div id={id}>lazy</div>;
564+
},
565+
});
566+
},
567+
}));
568+
569+
expect(ReactDOMServer.renderToString(<LazyFoo id="foo" />)).toEqual(
570+
'<div id="foo">lazy</div>',
571+
);
572+
});
573+
574+
it('throws error from synchronously rejected lazy component', () => {
575+
const LazyFoo = React.lazy(() => ({
576+
then(resolve, reject) {
577+
reject(new Error('Bad lazy'));
578+
},
579+
}));
580+
581+
expect(() => ReactDOMServer.renderToString(<LazyFoo />)).toThrow(
582+
'Bad lazy',
583+
);
584+
});
557585
});
558586

559587
describe('renderToNodeStream', () => {

packages/react-dom/src/server/ReactPartialRenderer.js

Lines changed: 46 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99

1010
import type {ThreadID} from './ReactThreadIDAllocator';
1111
import type {ReactElement} from 'shared/ReactElementType';
12+
import type {LazyComponent} from 'shared/ReactLazyComponent';
1213
import type {ReactProvider, ReactContext} from 'shared/ReactTypes';
1314

1415
import React from 'react';
@@ -19,6 +20,12 @@ import warning from 'shared/warning';
1920
import warningWithoutStack from 'shared/warningWithoutStack';
2021
import describeComponentFrame from 'shared/describeComponentFrame';
2122
import ReactSharedInternals from 'shared/ReactSharedInternals';
23+
import {
24+
Resolved,
25+
Rejected,
26+
Pending,
27+
initializeLazyComponentType,
28+
} from 'shared/ReactLazyComponent';
2229
import {
2330
warnAboutDeprecatedLifecycles,
2431
disableLegacyContext,
@@ -1226,11 +1233,45 @@ class ReactDOMServerRenderer {
12261233
);
12271234
}
12281235
// eslint-disable-next-line-no-fallthrough
1229-
case REACT_LAZY_TYPE:
1230-
invariant(
1231-
false,
1232-
'ReactDOMServer does not yet support lazy-loaded components.',
1233-
);
1236+
case REACT_LAZY_TYPE: {
1237+
const element: ReactElement = (nextChild: any);
1238+
const lazyComponent: LazyComponent<any> = (nextChild: any).type;
1239+
// Attempt to initialize lazy component regardless of whether the
1240+
// suspense server-side renderer is enabled so synchronously
1241+
// resolved constructors are supported.
1242+
initializeLazyComponentType(lazyComponent);
1243+
switch (lazyComponent._status) {
1244+
case Resolved: {
1245+
const nextChildren = [
1246+
React.createElement(
1247+
lazyComponent._result,
1248+
Object.assign({ref: element.ref}, element.props),
1249+
),
1250+
];
1251+
const frame: Frame = {
1252+
type: null,
1253+
domNamespace: parentNamespace,
1254+
children: nextChildren,
1255+
childIndex: 0,
1256+
context: context,
1257+
footer: '',
1258+
};
1259+
if (__DEV__) {
1260+
((frame: any): FrameDev).debugElementStack = [];
1261+
}
1262+
this.stack.push(frame);
1263+
return '';
1264+
}
1265+
case Rejected:
1266+
throw lazyComponent._result;
1267+
case Pending:
1268+
default:
1269+
invariant(
1270+
false,
1271+
'ReactDOMServer does not yet support lazy-loaded components.',
1272+
);
1273+
}
1274+
}
12341275
}
12351276
}
12361277

packages/react-reconciler/src/ReactFiberLazyComponent.js

Lines changed: 6 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,9 @@
77
* @flow
88
*/
99

10-
import type {LazyComponent, Thenable} from 'shared/ReactLazyComponent';
10+
import type {LazyComponent} from 'shared/ReactLazyComponent';
1111

12-
import {Resolved, Rejected, Pending} from 'shared/ReactLazyComponent';
13-
import warning from 'shared/warning';
12+
import {Resolved, initializeLazyComponentType} from 'shared/ReactLazyComponent';
1413

1514
export function resolveDefaultProps(Component: any, baseProps: Object): Object {
1615
if (Component && Component.defaultProps) {
@@ -28,60 +27,9 @@ export function resolveDefaultProps(Component: any, baseProps: Object): Object {
2827
}
2928

3029
export function readLazyComponentType<T>(lazyComponent: LazyComponent<T>): T {
31-
const status = lazyComponent._status;
32-
const result = lazyComponent._result;
33-
switch (status) {
34-
case Resolved: {
35-
const Component: T = result;
36-
return Component;
37-
}
38-
case Rejected: {
39-
const error: mixed = result;
40-
throw error;
41-
}
42-
case Pending: {
43-
const thenable: Thenable<T, mixed> = result;
44-
throw thenable;
45-
}
46-
default: {
47-
lazyComponent._status = Pending;
48-
const ctor = lazyComponent._ctor;
49-
const thenable = ctor();
50-
thenable.then(
51-
moduleObject => {
52-
if (lazyComponent._status === Pending) {
53-
const defaultExport = moduleObject.default;
54-
if (__DEV__) {
55-
if (defaultExport === undefined) {
56-
warning(
57-
false,
58-
'lazy: Expected the result of a dynamic import() call. ' +
59-
'Instead received: %s\n\nYour code should look like: \n ' +
60-
"const MyComponent = lazy(() => import('./MyComponent'))",
61-
moduleObject,
62-
);
63-
}
64-
}
65-
lazyComponent._status = Resolved;
66-
lazyComponent._result = defaultExport;
67-
}
68-
},
69-
error => {
70-
if (lazyComponent._status === Pending) {
71-
lazyComponent._status = Rejected;
72-
lazyComponent._result = error;
73-
}
74-
},
75-
);
76-
// Handle synchronous thenables.
77-
switch (lazyComponent._status) {
78-
case Resolved:
79-
return lazyComponent._result;
80-
case Rejected:
81-
throw lazyComponent._result;
82-
}
83-
lazyComponent._result = thenable;
84-
throw thenable;
85-
}
30+
initializeLazyComponentType(lazyComponent);
31+
if (lazyComponent._status !== Resolved) {
32+
throw lazyComponent._result;
8633
}
34+
return lazyComponent._result;
8735
}

packages/shared/ReactLazyComponent.js

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@
77
* @flow
88
*/
99

10+
import warning from 'shared/warning';
11+
1012
export type Thenable<T, R> = {
1113
then(resolve: (T) => mixed, reject: (mixed) => mixed): R,
1214
};
@@ -25,6 +27,7 @@ type ResolvedLazyComponent<T> = {
2527
_result: any,
2628
};
2729

30+
export const Uninitialized = -1;
2831
export const Pending = 0;
2932
export const Resolved = 1;
3033
export const Rejected = 2;
@@ -34,3 +37,40 @@ export function refineResolvedLazyComponent<T>(
3437
): ResolvedLazyComponent<T> | null {
3538
return lazyComponent._status === Resolved ? lazyComponent._result : null;
3639
}
40+
41+
export function initializeLazyComponentType(
42+
lazyComponent: LazyComponent<any>,
43+
): void {
44+
if (lazyComponent._status === Uninitialized) {
45+
lazyComponent._status = Pending;
46+
const ctor = lazyComponent._ctor;
47+
const thenable = ctor();
48+
lazyComponent._result = thenable;
49+
thenable.then(
50+
moduleObject => {
51+
if (lazyComponent._status === Pending) {
52+
const defaultExport = moduleObject.default;
53+
if (__DEV__) {
54+
if (defaultExport === undefined) {
55+
warning(
56+
false,
57+
'lazy: Expected the result of a dynamic import() call. ' +
58+
'Instead received: %s\n\nYour code should look like: \n ' +
59+
"const MyComponent = lazy(() => import('./MyComponent'))",
60+
moduleObject,
61+
);
62+
}
63+
}
64+
lazyComponent._status = Resolved;
65+
lazyComponent._result = defaultExport;
66+
}
67+
},
68+
error => {
69+
if (lazyComponent._status === Pending) {
70+
lazyComponent._status = Rejected;
71+
lazyComponent._result = error;
72+
}
73+
},
74+
);
75+
}
76+
}

0 commit comments

Comments
 (0)