diff --git a/packages/react-dom/src/__tests__/ReactServerRendering-test.js b/packages/react-dom/src/__tests__/ReactServerRendering-test.js
index 8c68b07a5fe88..d6a86197af6fd 100644
--- a/packages/react-dom/src/__tests__/ReactServerRendering-test.js
+++ b/packages/react-dom/src/__tests__/ReactServerRendering-test.js
@@ -554,6 +554,34 @@ describe('ReactDOMServer', () => {
),
).not.toThrow();
});
+
+ it('supports synchronous resolved lazy components', () => {
+ const LazyFoo = React.lazy(() => ({
+ then(resolve) {
+ resolve({
+ default: function Foo({id}) {
+ return
lazy
;
+ },
+ });
+ },
+ }));
+
+ expect(ReactDOMServer.renderToString()).toEqual(
+ 'lazy
',
+ );
+ });
+
+ it('supports synchronous rejected lazy components', () => {
+ const LazyFoo = React.lazy(() => ({
+ then(resolve, reject) {
+ reject(new Error('Bad lazy'));
+ },
+ }));
+
+ expect(() => ReactDOMServer.renderToString()).toThrow(
+ 'Bad lazy',
+ );
+ });
});
describe('renderToNodeStream', () => {
diff --git a/packages/react-dom/src/server/ReactPartialRenderer.js b/packages/react-dom/src/server/ReactPartialRenderer.js
index 756397eec6690..a69fbbe394d1e 100644
--- a/packages/react-dom/src/server/ReactPartialRenderer.js
+++ b/packages/react-dom/src/server/ReactPartialRenderer.js
@@ -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,49 @@ 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 = (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:
+ if (enableSuspenseServerRenderer) {
+ throw lazyComponent._result;
+ }
+ // fall through
+ default:
+ invariant(
+ false,
+ 'ReactDOMServer does not yet support lazy-loaded components.',
+ );
+ }
+ }
}
}
diff --git a/packages/react-reconciler/src/ReactFiberLazyComponent.js b/packages/react-reconciler/src/ReactFiberLazyComponent.js
index 1db5ecd35cab2..81f601a3ac35f 100644
--- a/packages/react-reconciler/src/ReactFiberLazyComponent.js
+++ b/packages/react-reconciler/src/ReactFiberLazyComponent.js
@@ -7,10 +7,14 @@
* @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,
+ Rejected,
+ Pending,
+ initializeLazyComponentType,
+} from 'shared/ReactLazyComponent';
export function resolveDefaultProps(Component: any, baseProps: Object): Object {
if (Component && Component.defaultProps) {
@@ -28,60 +32,15 @@ export function resolveDefaultProps(Component: any, baseProps: Object): Object {
}
export function readLazyComponentType(lazyComponent: LazyComponent): T {
- const status = lazyComponent._status;
- const result = lazyComponent._result;
- switch (status) {
+ initializeLazyComponentType(lazyComponent);
+ switch (lazyComponent._status) {
case Resolved: {
- const Component: T = result;
+ const Component: T = lazyComponent._result;
return Component;
}
- case Rejected: {
- const error: mixed = result;
- throw error;
- }
- case Pending: {
- const thenable: Thenable = 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;
- }
+ case Rejected:
+ case Pending:
+ default:
+ throw lazyComponent._result;
}
}
diff --git a/packages/shared/ReactLazyComponent.js b/packages/shared/ReactLazyComponent.js
index f3042ab11aec4..e5bc8b4a37dff 100644
--- a/packages/shared/ReactLazyComponent.js
+++ b/packages/shared/ReactLazyComponent.js
@@ -7,6 +7,8 @@
* @flow
*/
+import warning from 'shared/warning';
+
export type Thenable = {
then(resolve: (T) => mixed, reject: (mixed) => mixed): R,
};
@@ -25,6 +27,7 @@ type ResolvedLazyComponent = {
_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(
): ResolvedLazyComponent | null {
return lazyComponent._status === Resolved ? lazyComponent._result : null;
}
+
+export function initializeLazyComponentType(
+ lazyComponent: LazyComponent,
+): 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;
+ }
+ },
+ );
+ }
+}