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

Skip to content

Commit d9a3cc0

Browse files
author
Andrew Clark
authored
React.lazy constructor must return result of a dynamic import (facebook#13886)
We may want to change the protocol later, so until then we'll be restrictive. Heuristic is to check for existence of `default`.
1 parent d9659e4 commit d9a3cc0

8 files changed

+85
-55
lines changed

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

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -571,14 +571,19 @@ describe('ReactDOMServer', () => {
571571
ReactDOMServer.renderToString(<React.unstable_Suspense />);
572572
}).toThrow('ReactDOMServer does not yet support Suspense.');
573573

574+
async function fakeImport(result) {
575+
return {default: result};
576+
}
577+
574578
expect(() => {
575-
const LazyFoo = React.lazy(
576-
() =>
579+
const LazyFoo = React.lazy(() =>
580+
fakeImport(
577581
new Promise(resolve =>
578582
resolve(function Foo() {
579583
return <div />;
580584
}),
581585
),
586+
),
582587
);
583588
ReactDOMServer.renderToString(<LazyFoo />);
584589
}).toThrow('ReactDOMServer does not yet support lazy-loaded components.');

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

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -444,14 +444,20 @@ describe('ReactDOMServerHydration', () => {
444444
});
445445

446446
it('should be able to use lazy components after hydrating', async () => {
447+
async function fakeImport(result) {
448+
return {default: result};
449+
}
450+
447451
const Lazy = React.lazy(
448452
() =>
449453
new Promise(resolve => {
450454
setTimeout(
451455
() =>
452-
resolve(function World() {
453-
return 'world';
454-
}),
456+
resolve(
457+
fakeImport(function World() {
458+
return 'world';
459+
}),
460+
),
455461
1000,
456462
);
457463
}),

packages/react-reconciler/src/ReactFiberLazyComponent.js

Lines changed: 15 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
import type {LazyComponent} from 'shared/ReactLazyComponent';
1111

1212
import {Resolved, Rejected, Pending} from 'shared/ReactLazyComponent';
13+
import warning from 'shared/warning';
1314

1415
export function readLazyComponentType<T>(lazyComponent: LazyComponent<T>): T {
1516
const status = lazyComponent._status;
@@ -26,22 +27,22 @@ export function readLazyComponentType<T>(lazyComponent: LazyComponent<T>): T {
2627
const ctor = lazyComponent._ctor;
2728
const thenable = ctor();
2829
thenable.then(
29-
resolvedValue => {
30+
moduleObject => {
3031
if (lazyComponent._status === Pending) {
31-
lazyComponent._status = Resolved;
32-
if (typeof resolvedValue === 'object' && resolvedValue !== null) {
33-
// If the `default` property is not empty, assume it's the result
34-
// of an async import() and use that. Otherwise, use the
35-
// resolved value itself.
36-
const defaultExport = (resolvedValue: any).default;
37-
resolvedValue =
38-
defaultExport !== undefined && defaultExport !== null
39-
? defaultExport
40-
: resolvedValue;
41-
} else {
42-
resolvedValue = resolvedValue;
32+
const defaultExport = moduleObject.default;
33+
if (__DEV__) {
34+
if (defaultExport === undefined) {
35+
warning(
36+
false,
37+
'lazy: Expected the result of a dynamic import() call. ' +
38+
'Instead received: %s\n\nYour code should look like: \n ' +
39+
"const MyComponent = lazy(() => import('./MyComponent'))",
40+
moduleObject,
41+
);
42+
}
4343
}
44-
lazyComponent._result = resolvedValue;
44+
lazyComponent._status = Resolved;
45+
lazyComponent._result = defaultExport;
4546
}
4647
},
4748
error => {

packages/react-reconciler/src/__tests__/ReactIncrementalPerf-test.internal.js

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -573,6 +573,10 @@ describe('ReactDebugFiberPerf', () => {
573573
return <span />;
574574
}
575575

576+
async function fakeImport(result) {
577+
return {default: result};
578+
}
579+
576580
let resolve;
577581
const LazyFoo = React.lazy(
578582
() =>
@@ -591,9 +595,11 @@ describe('ReactDebugFiberPerf', () => {
591595
ReactNoop.flush();
592596
expect(getFlameChart()).toMatchSnapshot();
593597

594-
resolve(function Foo() {
595-
return <div />;
596-
});
598+
resolve(
599+
fakeImport(function Foo() {
600+
return <div />;
601+
}),
602+
);
597603
await LazyFoo;
598604

599605
ReactNoop.render(

packages/react-reconciler/src/__tests__/ReactLazy-test.internal.js

Lines changed: 29 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -21,8 +21,12 @@ describe('ReactLazy', () => {
2121
return props.text;
2222
}
2323

24+
async function fakeImport(result) {
25+
return {default: result};
26+
}
27+
2428
it('suspends until module has loaded', async () => {
25-
const LazyText = lazy(async () => Text);
29+
const LazyText = lazy(() => fakeImport(Text));
2630

2731
const root = ReactTestRenderer.create(
2832
<Suspense fallback={<Text text="Loading..." />}>
@@ -51,8 +55,10 @@ describe('ReactLazy', () => {
5155
expect(root).toMatchRenderedOutput('Hi again');
5256
});
5357

54-
it('uses `default` property, if it exists', async () => {
55-
const LazyText = lazy(async () => ({default: Text}));
58+
it('does not support arbitrary promises, only module objects', async () => {
59+
spyOnDev(console, 'error');
60+
61+
const LazyText = lazy(async () => Text);
5662

5763
const root = ReactTestRenderer.create(
5864
<Suspense fallback={<Text text="Loading..." />}>
@@ -67,17 +73,13 @@ describe('ReactLazy', () => {
6773

6874
await LazyText;
6975

70-
expect(root).toFlushAndYield(['Hi']);
71-
expect(root).toMatchRenderedOutput('Hi');
72-
73-
// Should not suspend on update
74-
root.update(
75-
<Suspense fallback={<Text text="Loading..." />}>
76-
<LazyText text="Hi again" />
77-
</Suspense>,
78-
);
79-
expect(root).toFlushAndYield(['Hi again']);
80-
expect(root).toMatchRenderedOutput('Hi again');
76+
if (__DEV__) {
77+
expect(console.error).toHaveBeenCalledTimes(1);
78+
expect(console.error.calls.argsFor(0)[0]).toContain(
79+
'Expected the result of a dynamic import() call',
80+
);
81+
}
82+
expect(root).toFlushAndThrow('Element type is invalid');
8183
});
8284

8385
it('throws if promise rejects', async () => {
@@ -117,8 +119,8 @@ describe('ReactLazy', () => {
117119
}
118120
}
119121

120-
const LazyChildA = lazy(async () => Child);
121-
const LazyChildB = lazy(async () => Child);
122+
const LazyChildA = lazy(() => fakeImport(Child));
123+
const LazyChildB = lazy(() => fakeImport(Child));
122124

123125
function Parent({swap}) {
124126
return (
@@ -160,7 +162,7 @@ describe('ReactLazy', () => {
160162
return <Text {...props} />;
161163
}
162164
T.defaultProps = {text: 'Hi'};
163-
const LazyText = lazy(async () => T);
165+
const LazyText = lazy(() => fakeImport(T));
164166

165167
const root = ReactTestRenderer.create(
166168
<Suspense fallback={<Text text="Loading..." />}>
@@ -200,7 +202,7 @@ describe('ReactLazy', () => {
200202
);
201203
}
202204
LazyImpl.defaultProps = {siblingText: 'Sibling'};
203-
const Lazy = lazy(async () => LazyImpl);
205+
const Lazy = lazy(() => fakeImport(LazyImpl));
204206

205207
class Stateful extends React.Component {
206208
state = {text: 'A'};
@@ -239,7 +241,7 @@ describe('ReactLazy', () => {
239241
const LazyFoo = lazy(() => {
240242
ReactTestRenderer.unstable_yield('Started loading');
241243
const Foo = props => <div>{[<Text text="A" />, <Text text="B" />]}</div>;
242-
return Promise.resolve(Foo);
244+
return fakeImport(Foo);
243245
});
244246

245247
const root = ReactTestRenderer.create(
@@ -263,13 +265,13 @@ describe('ReactLazy', () => {
263265
});
264266

265267
it('supports class and forwardRef components', async () => {
266-
const LazyClass = lazy(async () => {
268+
const LazyClass = lazy(() => {
267269
class Foo extends React.Component {
268270
render() {
269271
return <Text text="Foo" />;
270272
}
271273
}
272-
return Foo;
274+
return fakeImport(Foo);
273275
});
274276

275277
const LazyForwardRef = lazy(async () => {
@@ -278,10 +280,12 @@ describe('ReactLazy', () => {
278280
return <Text text="Bar" />;
279281
}
280282
}
281-
return React.forwardRef((props, ref) => {
282-
ReactTestRenderer.unstable_yield('forwardRef');
283-
return <Bar ref={ref} />;
284-
});
283+
return fakeImport(
284+
React.forwardRef((props, ref) => {
285+
ReactTestRenderer.unstable_yield('forwardRef');
286+
return <Bar ref={ref} />;
287+
}),
288+
);
285289
});
286290

287291
const ref = React.createRef();

packages/react-reconciler/src/__tests__/ReactPure-test.internal.js

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,10 @@ describe('pure', () => {
3434
return <span prop={props.text} />;
3535
}
3636

37+
async function fakeImport(result) {
38+
return {default: result};
39+
}
40+
3741
// Tests should run against both the lazy and non-lazy versions of `pure`.
3842
// To make the tests work for both versions, we wrap the non-lazy version in
3943
// a lazy function component.
@@ -42,11 +46,11 @@ describe('pure', () => {
4246
function Indirection(props) {
4347
return <Pure {...props} />;
4448
}
45-
return React.lazy(async () => Indirection);
49+
return React.lazy(() => fakeImport(Indirection));
4650
});
4751
sharedTests('lazy', (...args) => {
4852
const Pure = React.pure(...args);
49-
return React.lazy(async () => Pure);
53+
return React.lazy(() => fakeImport(Pure));
5054
});
5155

5256
function sharedTests(label, pure) {

packages/react-reconciler/src/__tests__/ReactSuspense-test.internal.js

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -256,7 +256,11 @@ describe('ReactSuspense', () => {
256256
}
257257
}
258258

259-
const LazyClass = React.lazy(() => Promise.resolve(Class));
259+
async function fakeImport(result) {
260+
return {default: result};
261+
}
262+
263+
const LazyClass = React.lazy(() => fakeImport(Class));
260264

261265
const root = ReactTestRenderer.create(
262266
<Suspense fallback={<Text text="Loading..." />}>

packages/shared/ReactLazyComponent.js

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -13,14 +13,14 @@ export type Thenable<T, R> = {
1313

1414
export type LazyComponent<T> = {
1515
$$typeof: Symbol | number,
16-
_ctor: () => Thenable<T, mixed>,
16+
_ctor: () => Thenable<{default: T}, mixed>,
1717
_status: 0 | 1 | 2,
1818
_result: any,
1919
};
2020

21-
type ResolvedLazyComponentThenable<T> = {
21+
type ResolvedLazyComponent<T> = {
2222
$$typeof: Symbol | number,
23-
_ctor: () => Thenable<T, mixed>,
23+
_ctor: () => Thenable<{default: T}, mixed>,
2424
_status: 1,
2525
_result: any,
2626
};
@@ -30,13 +30,13 @@ export const Resolved = 1;
3030
export const Rejected = 2;
3131

3232
export function getResultFromResolvedLazyComponent<T>(
33-
lazyComponent: ResolvedLazyComponentThenable<T>,
33+
lazyComponent: ResolvedLazyComponent<T>,
3434
): T {
3535
return lazyComponent._result;
3636
}
3737

3838
export function refineResolvedLazyComponent<T>(
3939
lazyComponent: LazyComponent<T>,
40-
): ResolvedLazyComponentThenable<T> | null {
40+
): ResolvedLazyComponent<T> | null {
4141
return lazyComponent._status === Resolved ? lazyComponent._result : null;
4242
}

0 commit comments

Comments
 (0)