mirror of
https://github.com/hanzoai/react.git
synced 2026-09-24 17:02:27 +00:00
Partial support for React.lazy() in server renderer. (#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.
This commit is contained in:
committed by
Sebastian Markbåge
parent
20ef2666c2
commit
7be0344599
@@ -554,6 +554,34 @@ describe('ReactDOMServer', () => {
|
||||
),
|
||||
).not.toThrow();
|
||||
});
|
||||
|
||||
it('renders synchronously resolved lazy component', () => {
|
||||
const LazyFoo = React.lazy(() => ({
|
||||
then(resolve) {
|
||||
resolve({
|
||||
default: function Foo({id}) {
|
||||
return <div id={id}>lazy</div>;
|
||||
},
|
||||
});
|
||||
},
|
||||
}));
|
||||
|
||||
expect(ReactDOMServer.renderToString(<LazyFoo id="foo" />)).toEqual(
|
||||
'<div id="foo">lazy</div>',
|
||||
);
|
||||
});
|
||||
|
||||
it('throws error from synchronously rejected lazy component', () => {
|
||||
const LazyFoo = React.lazy(() => ({
|
||||
then(resolve, reject) {
|
||||
reject(new Error('Bad lazy'));
|
||||
},
|
||||
}));
|
||||
|
||||
expect(() => ReactDOMServer.renderToString(<LazyFoo />)).toThrow(
|
||||
'Bad lazy',
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('renderToNodeStream', () => {
|
||||
|
||||
+46
-5
@@ -9,6 +9,7 @@
|
||||
|
||||
import type {ThreadID} from './ReactThreadIDAllocator';
|
||||
import type {ReactElement} from 'shared/ReactElementType';
|
||||
import type {LazyComponent} from 'shared/ReactLazyComponent';
|
||||
import type {ReactProvider, ReactContext} from 'shared/ReactTypes';
|
||||
|
||||
import React from 'react';
|
||||
@@ -19,6 +20,12 @@ import warning from 'shared/warning';
|
||||
import warningWithoutStack from 'shared/warningWithoutStack';
|
||||
import describeComponentFrame from 'shared/describeComponentFrame';
|
||||
import ReactSharedInternals from 'shared/ReactSharedInternals';
|
||||
import {
|
||||
Resolved,
|
||||
Rejected,
|
||||
Pending,
|
||||
initializeLazyComponentType,
|
||||
} from 'shared/ReactLazyComponent';
|
||||
import {
|
||||
warnAboutDeprecatedLifecycles,
|
||||
disableLegacyContext,
|
||||
@@ -1226,11 +1233,45 @@ class ReactDOMServerRenderer {
|
||||
);
|
||||
}
|
||||
// eslint-disable-next-line-no-fallthrough
|
||||
case REACT_LAZY_TYPE:
|
||||
invariant(
|
||||
false,
|
||||
'ReactDOMServer does not yet support lazy-loaded components.',
|
||||
);
|
||||
case REACT_LAZY_TYPE: {
|
||||
const element: ReactElement = (nextChild: any);
|
||||
const lazyComponent: LazyComponent<any> = (nextChild: any).type;
|
||||
// Attempt to initialize lazy component regardless of whether the
|
||||
// suspense server-side renderer is enabled so synchronously
|
||||
// resolved constructors are supported.
|
||||
initializeLazyComponentType(lazyComponent);
|
||||
switch (lazyComponent._status) {
|
||||
case Resolved: {
|
||||
const nextChildren = [
|
||||
React.createElement(
|
||||
lazyComponent._result,
|
||||
Object.assign({ref: element.ref}, element.props),
|
||||
),
|
||||
];
|
||||
const frame: Frame = {
|
||||
type: null,
|
||||
domNamespace: parentNamespace,
|
||||
children: nextChildren,
|
||||
childIndex: 0,
|
||||
context: context,
|
||||
footer: '',
|
||||
};
|
||||
if (__DEV__) {
|
||||
((frame: any): FrameDev).debugElementStack = [];
|
||||
}
|
||||
this.stack.push(frame);
|
||||
return '';
|
||||
}
|
||||
case Rejected:
|
||||
throw lazyComponent._result;
|
||||
case Pending:
|
||||
default:
|
||||
invariant(
|
||||
false,
|
||||
'ReactDOMServer does not yet support lazy-loaded components.',
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+6
-58
@@ -7,10 +7,9 @@
|
||||
* @flow
|
||||
*/
|
||||
|
||||
import type {LazyComponent, Thenable} from 'shared/ReactLazyComponent';
|
||||
import type {LazyComponent} from 'shared/ReactLazyComponent';
|
||||
|
||||
import {Resolved, Rejected, Pending} from 'shared/ReactLazyComponent';
|
||||
import warning from 'shared/warning';
|
||||
import {Resolved, initializeLazyComponentType} from 'shared/ReactLazyComponent';
|
||||
|
||||
export function resolveDefaultProps(Component: any, baseProps: Object): Object {
|
||||
if (Component && Component.defaultProps) {
|
||||
@@ -28,60 +27,9 @@ export function resolveDefaultProps(Component: any, baseProps: Object): Object {
|
||||
}
|
||||
|
||||
export function readLazyComponentType<T>(lazyComponent: LazyComponent<T>): T {
|
||||
const status = lazyComponent._status;
|
||||
const result = lazyComponent._result;
|
||||
switch (status) {
|
||||
case Resolved: {
|
||||
const Component: T = result;
|
||||
return Component;
|
||||
}
|
||||
case Rejected: {
|
||||
const error: mixed = result;
|
||||
throw error;
|
||||
}
|
||||
case Pending: {
|
||||
const thenable: Thenable<T, mixed> = result;
|
||||
throw thenable;
|
||||
}
|
||||
default: {
|
||||
lazyComponent._status = Pending;
|
||||
const ctor = lazyComponent._ctor;
|
||||
const thenable = ctor();
|
||||
thenable.then(
|
||||
moduleObject => {
|
||||
if (lazyComponent._status === Pending) {
|
||||
const defaultExport = moduleObject.default;
|
||||
if (__DEV__) {
|
||||
if (defaultExport === undefined) {
|
||||
warning(
|
||||
false,
|
||||
'lazy: Expected the result of a dynamic import() call. ' +
|
||||
'Instead received: %s\n\nYour code should look like: \n ' +
|
||||
"const MyComponent = lazy(() => import('./MyComponent'))",
|
||||
moduleObject,
|
||||
);
|
||||
}
|
||||
}
|
||||
lazyComponent._status = Resolved;
|
||||
lazyComponent._result = defaultExport;
|
||||
}
|
||||
},
|
||||
error => {
|
||||
if (lazyComponent._status === Pending) {
|
||||
lazyComponent._status = Rejected;
|
||||
lazyComponent._result = error;
|
||||
}
|
||||
},
|
||||
);
|
||||
// Handle synchronous thenables.
|
||||
switch (lazyComponent._status) {
|
||||
case Resolved:
|
||||
return lazyComponent._result;
|
||||
case Rejected:
|
||||
throw lazyComponent._result;
|
||||
}
|
||||
lazyComponent._result = thenable;
|
||||
throw thenable;
|
||||
}
|
||||
initializeLazyComponentType(lazyComponent);
|
||||
if (lazyComponent._status !== Resolved) {
|
||||
throw lazyComponent._result;
|
||||
}
|
||||
return lazyComponent._result;
|
||||
}
|
||||
|
||||
@@ -7,6 +7,8 @@
|
||||
* @flow
|
||||
*/
|
||||
|
||||
import warning from 'shared/warning';
|
||||
|
||||
export type Thenable<T, R> = {
|
||||
then(resolve: (T) => mixed, reject: (mixed) => mixed): R,
|
||||
};
|
||||
@@ -25,6 +27,7 @@ type ResolvedLazyComponent<T> = {
|
||||
_result: any,
|
||||
};
|
||||
|
||||
export const Uninitialized = -1;
|
||||
export const Pending = 0;
|
||||
export const Resolved = 1;
|
||||
export const Rejected = 2;
|
||||
@@ -34,3 +37,40 @@ export function refineResolvedLazyComponent<T>(
|
||||
): ResolvedLazyComponent<T> | null {
|
||||
return lazyComponent._status === Resolved ? lazyComponent._result : null;
|
||||
}
|
||||
|
||||
export function initializeLazyComponentType(
|
||||
lazyComponent: LazyComponent<any>,
|
||||
): void {
|
||||
if (lazyComponent._status === Uninitialized) {
|
||||
lazyComponent._status = Pending;
|
||||
const ctor = lazyComponent._ctor;
|
||||
const thenable = ctor();
|
||||
lazyComponent._result = thenable;
|
||||
thenable.then(
|
||||
moduleObject => {
|
||||
if (lazyComponent._status === Pending) {
|
||||
const defaultExport = moduleObject.default;
|
||||
if (__DEV__) {
|
||||
if (defaultExport === undefined) {
|
||||
warning(
|
||||
false,
|
||||
'lazy: Expected the result of a dynamic import() call. ' +
|
||||
'Instead received: %s\n\nYour code should look like: \n ' +
|
||||
"const MyComponent = lazy(() => import('./MyComponent'))",
|
||||
moduleObject,
|
||||
);
|
||||
}
|
||||
}
|
||||
lazyComponent._status = Resolved;
|
||||
lazyComponent._result = defaultExport;
|
||||
}
|
||||
},
|
||||
error => {
|
||||
if (lazyComponent._status === Pending) {
|
||||
lazyComponent._status = Rejected;
|
||||
lazyComponent._result = error;
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user