Skip to content

Commit ac93347

Browse files
committed
Introduce Loadable.preloadAll
1 parent 1846782 commit ac93347

15 files changed

Lines changed: 665 additions & 212 deletions

File tree

.babelrc

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
{
22
"presets": [
33
["es2015", {
4-
"loose": true
4+
"loose": true,
55
}],
6-
"react"
7-
]
6+
"react",
7+
],
88
}

README.md

Lines changed: 81 additions & 94 deletions
Original file line numberDiff line numberDiff line change
@@ -222,131 +222,118 @@ Loadable.Map({
222222
When using `Loadable.Map` the `render()` method's `loaded` param will be an
223223
object with the same shape as your `loader`.
224224

225-
### How do I avoid repetition?
225+
### `Loadable.preloadAll()`
226226

227-
Specifying the same `loading` component or `delay` every time you use
228-
`Loadable()` gets repetitive fast. Instead you can wrap `Loadable` with your
229-
own Higher-Order Component (HOC) to set default options.
227+
In order to avoid rendering loading states server-side, we need to preload all
228+
of our loadable components before we start responding to requests. For this
229+
there is the `Loadable.preloadAll()` method.
230230

231-
```js
232-
import Loadable from 'react-loadable';
233-
import Loading from './my-loading-component';
231+
When you declare your loadable components React Loadable stores references to
232+
each of them. So when you call `Loadable.preloadAll()` it will go through each
233+
of these references and call their `loader()` methods.
234234

235-
export default function MyLoadable(opts) {
236-
return Loadable(Object.assign({
237-
loading: Loading,
238-
delay: 200,
239-
timeout: 10,
240-
}, opts));
241-
};
242-
```
235+
`Loadable.preloadAll` returns a promise that resolves when every `loader()`
236+
method is done loading, you can wait for your app to be loaded before starting
237+
your app.
243238

244-
Then you can just specify a `loader` when you go to use it.
239+
**Example:**
245240

246241
```js
247-
import MyLoadable from './MyLoadable';
248-
249-
const LoadableMyComponent = MyLoadable({
250-
loader: () => import('./MyComponent'),
242+
import express from 'express';
243+
import React from 'react';
244+
import ReactDOMServer from 'react-dom/server';
245+
import Loadable from 'react-loadable';
246+
import App from './components/App';
247+
248+
const app = express();
249+
250+
app.get('/', (req, res) => {
251+
res.send(`
252+
<!doctype html>
253+
<html>
254+
<head>
255+
<title>My App</title>
256+
</head>
257+
<body>
258+
<div id="app">
259+
${ReactDOMServer.renderToString(React.createElement(App))}
260+
</div>
261+
</body>
262+
</html>
263+
`);
251264
});
252265

253-
export default class App extends React.Component {
254-
render() {
255-
return <LoadableMyComponent/>;
256-
}
257-
}
266+
Loadable.preloadAll().then(() => {
267+
app.listen(3000, () => {
268+
console.log('Running on http://localhost:3000/');
269+
});
270+
});
258271
```
259272

260-
### `babel-plugin-import-inspector`
273+
It's important to note that this requires that you declare all of your loadable
274+
components when modules are initialized rather than when your app is being
275+
rendered.
261276

262-
To allow for some more complicated features like server-side rendering and
263-
synchronous rendering in webpack, you'll need to use the
264-
[`import-inspector`](https://github.com/thejameskyle/babel-plugin-import-inspector)
265-
[Babel](https://babeljs.io) plugin.
277+
**Good:**
266278

267279
```js
268-
yarn add --dev babel-plugin-import-inspector
269-
```
280+
// During module initialization...
281+
const LoadableComponent = Loadable({...});
270282

271-
```js
272-
{
273-
"plugins": [
274-
["import-inspector", {
275-
"serverSideRequirePath": true,
276-
"webpackRequireWeakId": true,
277-
}]
278-
]
283+
class MyComponent extends React.Component {
284+
componentDidMount() {
285+
// ...
286+
}
279287
}
280288
```
281289

282-
### Server-side rendering
283-
284-
See [`babel-plugin-import-inspector`](#babel-plugin-import-inspector) and make
285-
sure to set `serverSideRequirePath` to `true`.
290+
**Bad:**
286291

287292
```js
288-
{
289-
"plugins": [
290-
["import-inspector", {
291-
"serverSideRequirePath": true,
292-
}]
293-
]
294-
}
295-
```
296-
297-
Rendering server-side should then just work.
293+
// ...
298294

299-
### Sync rendering preloaded imports in Webpack
300-
301-
See [`babel-plugin-import-inspector`](#babel-plugin-import-inspector) and make
302-
sure to set `webpackRequireWeakId` to `true`.
303-
304-
```js
305-
{
306-
"plugins": [
307-
["import-inspector", {
308-
"webpackRequireWeakId": true,
309-
}]
310-
]
295+
class MyComponent extends React.Component {
296+
componentDidMount() {
297+
// During app render...
298+
const LoadableComponent = Loadable({...});
299+
}
311300
}
312301
```
313302

314-
Synchronously rendering preloaded imports in Webpack should then just work.
315-
303+
> **Note:** `Loadable.preloadAll()` will not work if you have more than one
304+
> copy of `react-loadable` in your app.
316305
317-
### Server-side rendering
318-
319-
This requires using a special [Babel](https://babeljs.io) plugin,
320-
[`babel-plugin-import-inspector`](https://github.com/thejameskyle/babel-plugin-import-inspector),
321-
which will wrap every dynamic `import()` in your app with metadata which will
322-
allow React Loadable to render your component server-side.
306+
### How do I avoid repetition?
323307

324-
To install:
308+
Specifying the same `loading` component or `delay` every time you use
309+
`Loadable()` gets repetitive fast. Instead you can wrap `Loadable` with your
310+
own Higher-Order Component (HOC) to set default options.
325311

326312
```js
327-
yarn add --dev babel-plugin-import-inspector
328-
```
329-
330-
Then add this to your `.babelrc`:
313+
import Loadable from 'react-loadable';
314+
import Loading from './my-loading-component';
331315

332-
```js
333-
{
334-
"plugins": [
335-
["import-inspector", {
336-
"serverSideRequirePath": true,
337-
}]
338-
]
339-
}
316+
export default function MyLoadable(opts) {
317+
return Loadable(Object.assign({
318+
loading: Loading,
319+
delay: 200,
320+
timeout: 10,
321+
}, opts));
322+
};
340323
```
341324

342-
Your imports will then look like this:
325+
Then you can just specify a `loader` when you go to use it.
343326

344327
```js
345-
report(import("./module"), {
346-
// ...
347-
serverSideRequirePath: path.join(__dirname, "./module"),
348-
webpackRequireWeakId: () => require.resolveWeak("./module"),
328+
import MyLoadable from './MyLoadable';
329+
330+
const LoadableMyComponent = MyLoadable({
331+
loader: () => import('./MyComponent'),
349332
});
350-
```
351333

352-
Rendering server-side should then just work.
334+
export default class App extends React.Component {
335+
render() {
336+
return <LoadableMyComponent/>;
337+
}
338+
}
339+
```

__tests__/.babelrc

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
{
2+
"presets": [
3+
["es2015", {
4+
"loose": true,
5+
}],
6+
"react",
7+
],
8+
"plugins": [
9+
"transform-async-to-generator",
10+
],
11+
}

test.js renamed to __tests__/test.js

Lines changed: 25 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -3,24 +3,23 @@
33
const path = require('path');
44
const React = require('react');
55
const renderer = require('react-test-renderer');
6-
const Loadable = require('./src');
7-
const {report} = require('import-inspector');
6+
const Loadable = require('../src');
87

98
function waitFor(delay) {
109
return new Promise(resolve => {
1110
setTimeout(resolve, delay);
1211
});
1312
}
1413

15-
function createLoader(delay, Component, error, metadata) {
14+
function createLoader(delay, loader, error) {
1615
return () => {
17-
return report(waitFor(delay).then(() => {
18-
if (Component) {
19-
return Component;
16+
return waitFor(delay).then(() => {
17+
if (loader) {
18+
return loader();
2019
} else {
2120
throw error;
2221
}
23-
}), metadata);
22+
});
2423
};
2524
}
2625

@@ -32,9 +31,15 @@ function MyComponent(props) {
3231
return <div>MyComponent {JSON.stringify(props)}</div>;
3332
}
3433

34+
afterEach(async () => {
35+
try {
36+
await Loadable.preloadAll();
37+
} catch (err) {}
38+
});
39+
3540
test('loading success', async () => {
3641
let LoadableMyComponent = Loadable({
37-
loader: createLoader(400, MyComponent),
42+
loader: createLoader(400, () => MyComponent),
3843
loading: MyLoadingComponent
3944
});
4045

@@ -53,7 +58,7 @@ test('loading success', async () => {
5358

5459
test('delay and timeout', async () => {
5560
let LoadableMyComponent = Loadable({
56-
loader: createLoader(300, MyComponent),
61+
loader: createLoader(300, () => MyComponent),
5762
loading: MyLoadingComponent,
5863
delay: 100,
5964
timeout: 200,
@@ -87,33 +92,33 @@ test('loading error', async () => {
8792

8893
test('server side rendering', async () => {
8994
let LoadableMyComponent = Loadable({
90-
loader: createLoader(400, null, new Error('test error'), {
91-
serverSideRequirePath: path.join(__dirname, './__fixtures__/component.js')
92-
}),
95+
loader: createLoader(400, () => require('../__fixtures__/component')),
9396
loading: MyLoadingComponent,
9497
});
9598

99+
await Loadable.preloadAll();
100+
96101
let component = renderer.create(<LoadableMyComponent prop="baz" />);
97102

98103
expect(component.toJSON()).toMatchSnapshot(); // serverside
99104
});
100105

101106
test('server side rendering es6', async () => {
102107
let LoadableMyComponent = Loadable({
103-
loader: createLoader(400, null, new Error('test error'), {
104-
serverSideRequirePath: path.join(__dirname, './__fixtures__/component.es6.js')
105-
}),
108+
loader: createLoader(400, () => require('../__fixtures__/component.es6')),
106109
loading: MyLoadingComponent,
107110
});
108111

112+
await Loadable.preloadAll();
113+
109114
let component = renderer.create(<LoadableMyComponent prop="baz" />);
110115

111116
expect(component.toJSON()).toMatchSnapshot(); // serverside
112117
});
113118

114119
test('preload', async () => {
115120
let LoadableMyComponent = Loadable({
116-
loader: createLoader(400, MyComponent),
121+
loader: createLoader(400, () => MyComponent),
117122
loading: MyLoadingComponent
118123
});
119124

@@ -132,7 +137,7 @@ test('preload', async () => {
132137

133138
test('render', async () => {
134139
let LoadableMyComponent = Loadable({
135-
loader: createLoader(400, { MyComponent }),
140+
loader: createLoader(400, () => ({ MyComponent })),
136141
loading: MyLoadingComponent,
137142
render(loaded, props) {
138143
return <loaded.MyComponent {...props}/>;
@@ -149,8 +154,8 @@ test('render', async () => {
149154
test('loadable map success', async () => {
150155
let LoadableMyComponent = Loadable.Map({
151156
loader: {
152-
a: createLoader(200, { MyComponent }),
153-
b: createLoader(400, { MyComponent }),
157+
a: createLoader(200, () => ({ MyComponent })),
158+
b: createLoader(400, () => ({ MyComponent })),
154159
},
155160
loading: MyLoadingComponent,
156161
render(loaded, props) {
@@ -174,7 +179,7 @@ test('loadable map success', async () => {
174179
test('loadable map error', async () => {
175180
let LoadableMyComponent = Loadable.Map({
176181
loader: {
177-
a: createLoader(200, { MyComponent }),
182+
a: createLoader(200, () => ({ MyComponent })),
178183
b: createLoader(400, null, new Error('test error')),
179184
},
180185
loading: MyLoadingComponent,

example/.babelrc

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
{
2+
"presets": [
3+
"es2015",
4+
"react",
5+
],
6+
"plugins": [
7+
"dynamic-import-node"
8+
],
9+
}

example/components/App.js

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
import React from 'react';
2+
import Loadable from '../../src/index';
3+
import Loading from './Loading';
4+
import delay from '../utils/delay';
5+
6+
const LoadableExample = Loadable({
7+
loader: () => delay(1000).then(() => import('./Example')),
8+
loading: Loading,
9+
});
10+
11+
export default function App() {
12+
return <LoadableExample/>;
13+
}

0 commit comments

Comments
 (0)