Skip to content

Commit 94b2e87

Browse files
committed
feat: add new loadable.lib, change API
BREAKING CHANGE: - `ErrorComponent` is ignored, please use Error Boundaries to handle errors. - `lazy` is no longer exported - `LoadingComponent` is replaced by `fallback` option - `ref` are now forwarded
1 parent cdbbbeb commit 94b2e87

27 files changed

+8767
-236
lines changed

.eslintrc.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
"react/jsx-wrap-multilines": "off",
1212
"react/no-unused-state": "off",
1313
"react/destructuring-assignment": "off",
14-
"react/prop-types": "off"
14+
"react/prop-types": "off",
15+
"import/prefer-default-export": "off"
1516
}
1617
}

README.md

Lines changed: 190 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,18 @@ Loadable leverage the limit of Code Splitting and give you access to all feature
2323

2424
Code Splitting + Server Side Rendering is something very complex. Several libraries tried to solve this problem successfully or not. The goal of this library is to follow as much as possible the philosophy of React and give a developer-experience first solution to solve this complex problem. It takes the best from all libraries and provide an elegant solution to this problem.
2525

26+
## Differences with React.lazy & react-loadable
27+
28+
[`React.lazy`](https://reactjs.org/docs/code-splitting.html#reactlazy) doesn't support full dynamic import and SSR. Loadable uses the same API with more features (SSR, full dynamic import, library import). If you don't need them, you don't need `loadable`.
29+
30+
| Library | Suspense | SSR | Library loading | import(`./${x}`) |
31+
| --------------------- | -------- | --- | --------------- | ---------------- |
32+
| `React.lazy` |||||
33+
| `react-loadable` || 🔶 |||
34+
| `@loadable/component` |||||
35+
36+
Even if [`react-loadable` is recommended by React team](https://reactjs.org/docs/code-splitting.html#reactlazy), the project does not accept any GitHub issues and is no longer maintained.
37+
2638
## Getting started
2739

2840
`loadable` lets you render a dynamic import as a regular component.
@@ -41,6 +53,53 @@ function MyComponent() {
4153
}
4254
```
4355

56+
### Loading library
57+
58+
`loadable.lib` lets you defer the loading of a library. It takes a render props called when the library is loaded.
59+
60+
```js
61+
import loadable from '@loadable/component'
62+
63+
const Moment = loadable.lib(() => import('moment'))
64+
65+
function MyComponent() {
66+
return (
67+
<div>
68+
<Moment fallback="xx:xx">
69+
{({ default: moment }) => moment().format('HH:mm')}
70+
</Moment>
71+
</div>
72+
)
73+
}
74+
```
75+
76+
You can also use a `ref` that will be populated when the library will be loaded.
77+
78+
```js
79+
import loadable from '@loadable/component'
80+
81+
const Moment = loadable.lib(() => import('moment'))
82+
83+
class MyComponent {
84+
moment = React.createRef()
85+
86+
handleClick = () => {
87+
if (this.moment.current) {
88+
return console.log(this.moment.current.default.format('HH:mm'))
89+
}
90+
}
91+
92+
render() {
93+
return (
94+
<div>
95+
<button onClick={this.handleClick}>What time is it?</button>
96+
<Moment ref={this.moment} />
97+
</div>
98+
)
99+
}
100+
}
101+
```
102+
44103
### Full dynamic import
45104
46105
Webpack accepts [full dynamic imports](https://webpack.js.org/api/module-methods/#import-) and you can also use them with `@loadable/component` to create dynamic components.
@@ -62,12 +121,12 @@ function MyComponent() {
62121
63122
### Suspense
64123
65-
`@loadable/component` exposes a `lazy` method that acts similarly as `React.lazy` one.
124+
`@loadable/component` exposes a `loadable.lazy` method that acts similarly as `React.lazy` one.
66125
67126
```js
68-
import { lazy } from '@loadable/component'
127+
import loadable from '@loadable/component'
69128

70-
const OtherComponent = lazy(() => import('./OtherComponent'))
129+
const OtherComponent = loadable.lazy(() => import('./OtherComponent'))
71130

72131
function MyComponent() {
73132
return (
@@ -80,60 +139,63 @@ function MyComponent() {
80139
}
81140
```
82141
83-
> Suspense is not yet available for server-side rendering.
142+
> Use `loadable.lib.lazy` for libraries.
84143
85-
### Custom loading
144+
> Suspense is not yet available for server-side rendering.
86145
87-
It is possible to add a custom loading component, by default it will render nothing.
146+
### Fallback without Suspense
88147
89-
**Using a component**
148+
You can specify a `fallback` in `loadable` options.
90149
91150
```js
92-
const Loading = () => <div>Loading...</div>
93-
94-
const Home = loadable(() => import('./Home'), {
95-
LoadingComponent: Loading,
151+
const OtherComponent = loadable(() => import('./OtherComponent'), {
152+
fallback: <div>Loading...</div>,
96153
})
97-
```
98-
99-
**Using render props**
100154

101-
```js
102-
import React from 'react'
103-
104-
const Home = loadable(() => import('./Home'), {
105-
render: ({ Component, loading, ownProps }) => {
106-
if (loading) return <div>Loading...</div>
107-
return <Component {...ownProps} />
108-
},
109-
})
155+
function MyComponent() {
156+
return (
157+
<div>
158+
<OtherComponent />
159+
</div>
160+
)
161+
}
110162
```
111163
112-
### Error handling
113-
114-
You can configure the component rendered when an error occurs during loading, by default it will display the error.
115-
116-
**Using a component**
164+
You can also specify a `fallback` in props:
117165
118166
```js
119-
const ErrorDisplay = ({ error }) => <div>Oups! {error.message}</div>
167+
const OtherComponent = loadable(() => import('./OtherComponent'))
120168

121-
const Home = loadable(() => import('./Home'), {
122-
ErrorComponent: ErrorDisplay,
123-
})
169+
function MyComponent() {
170+
return (
171+
<div>
172+
<OtherComponent fallback={<div>Loading...</div>} />
173+
</div>
174+
)
175+
}
124176
```
125177
126-
**Using render props**
178+
### Error boundaries
179+
180+
If the other module fails to load (for example, due to network failure), it will trigger an error. You can handle these errors to show a nice user experience and manage recovery with [Error Boundaries](https://reactjs.org/docs/error-boundaries.html). Once you’ve created your Error Boundary, you can use it anywhere above your lazy components to display an error state when there’s a network error.
127181
128182
```js
129-
import React from 'react'
183+
import MyErrorBoundary from '/MyErrorBoundary'
184+
const OtherComponent = loadable.lazy(() => import('./OtherComponent'))
185+
const AnotherComponent = loadable.lazy(() => import('./AnotherComponent'))
130186

131-
const Home = loadable(() => import('./Home'), {
132-
render: ({ Component, error, ownProps }) => {
133-
if (error) return <div>Oups! {error.message}</div>
134-
return <Component {...ownProps} />
135-
},
136-
})
187+
const MyComponent = () => (
188+
<div>
189+
<MyErrorBoundary>
190+
<Suspense fallback={<div>Loading...</div>}>
191+
<section>
192+
<OtherComponent />
193+
<AnotherComponent />
194+
</section>
195+
</Suspense>
196+
</MyErrorBoundary>
197+
</div>
198+
)
137199
```
138200
139201
### Delay
@@ -145,21 +207,9 @@ import loadable from '@loadable/component'
145207
import pMinDelay from 'p-min-delay'
146208

147209
// Wait a minimum of 200ms before loading home.
148-
export const Home = loadable(() => pMinDelay(import('./Home'), 200))
149-
```
150-
151-
If you want to avoid these delay server-side:
152-
153-
```js
154-
import loadable from '@loadable/component'
155-
import pMinDelay from 'p-min-delay'
156-
157-
const delay = promise => {
158-
if (typeof window === 'undefined') return promise
159-
return pMinDelay(promise, 200)
160-
}
161-
162-
export const Home = loadable(() => delay(import('./Home')))
210+
export const OtherComponent = loadable(() =>
211+
pMinDelay(import('./OtherComponent'), 200),
212+
)
163213
```
164214
165215
### Timeout
@@ -171,7 +221,9 @@ import loadable from '@loadable/component'
171221
import { timeout } from 'promise-timeout'
172222

173223
// Wait a maximum of 2s before sending an error.
174-
export const Home = loadable(() => timeout(import('./Home'), 2000))
224+
export const OtherComponent = loadable(() =>
225+
timeout(import('./OtherComponent'), 2000),
226+
)
175227
```
176228
177229
### Prefetching
@@ -213,8 +265,90 @@ function MyComponent() {
213265
}
214266
```
215267
268+
> `prefetch` and `Prefetch` are also available for components created with `loadable.lazy`, `loadable.lib` and `loadable.lib.lazy`.
269+
216270
> Only component based prefetching is compatible with Server Side Rendering.
217271
272+
## API
273+
274+
### loadable
275+
276+
Create a loadable component.
277+
278+
| Arguments | Description |
279+
| ------------------ | ---------------------------------------- |
280+
| `loadFn` | The function call to load the component. |
281+
| `options` | Optional options. |
282+
| `options.fallback` | Fallback displayed during the loading. |
283+
284+
```js
285+
import loadable from '@loadable/component'
286+
287+
const OtherComponent = loadable(() => import('./OtherComponent'))
288+
```
289+
290+
### loadableState.lazy
291+
292+
Create a loadable component "Suspense" ready.
293+
294+
| Arguments | Description |
295+
| --------- | ---------------------------------------- |
296+
| `loadFn` | The function call to load the component. |
297+
298+
```js
299+
import loadable from '@loadable/component'
300+
301+
const OtherComponent = loadable.lazy(() => import('./OtherComponent'))
302+
```
303+
304+
### LoadableComponent
305+
306+
A component created using `loadable` or `loadable.lazy`.
307+
308+
| Props | Description |
309+
| ----- | ------------------------------------------------- |
310+
| `...` | Props are forwarded as first argument of `loadFn` |
311+
312+
### loadable.lib
313+
314+
Create a loadable library.
315+
316+
| Arguments | Description |
317+
| ------------------ | ---------------------------------------- |
318+
| `loadFn` | The function call to load the component. |
319+
| `options` | Optional options. |
320+
| `options.fallback` | Fallback displayed during the loading. |
321+
322+
```js
323+
import loadable from '@loadable/component'
324+
325+
const Moment = loadable.lib(() => import('moment'))
326+
```
327+
328+
### loadable.lib.lazy
329+
330+
Create a loadable library "Suspense" ready.
331+
332+
| Arguments | Description |
333+
| --------- | ---------------------------------------- |
334+
| `loadFn` | The function call to load the component. |
335+
336+
```js
337+
import loadable from '@loadable/component'
338+
339+
const Moment = loadable.lib.lazy(() => import('moment'))
340+
```
341+
342+
### LoadableLibrary
343+
344+
A component created using `loadable.lib` or `loadable.lib.lazy`.
345+
346+
| Props | Description |
347+
| ---------- | ---------------------------------------------------- |
348+
| `children` | Function called when the library is loaded. |
349+
| `ref` | Accepts a ref, populated when the library is loaded. |
350+
| `...` | Props are forwarded as first argument of `loadFn` |
351+
218352
## [Server side rendering](https://github.com/smooth-code/loadable-components/tree/master/packages/server)
219353
220354
👉 [See `@loadable/server` documentation](https://github.com/smooth-code/loadable-components/tree/master/packages/server).

examples/client-side/.eslintrc.json

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
{
2+
"env": {
3+
"browser": true
4+
},
5+
"rules": {
6+
"import/no-unresolved": "off"
7+
}
8+
}

examples/client-side/babel.config.js

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
module.exports = {
2+
presets: ['@babel/preset-react'],
3+
plugins: ['@babel/plugin-syntax-dynamic-import'],
4+
}

examples/client-side/package.json

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
{
2+
"name": "client-side",
3+
"version": "1.0.0",
4+
"main": "index.js",
5+
"license": "MIT",
6+
"scripts": {
7+
"start": "webpack-dev-server"
8+
},
9+
"devDependencies": {
10+
"babel-loader": "^8.0.4",
11+
"html-webpack-plugin": "^3.2.0",
12+
"webpack": "^4.23.1",
13+
"webpack-cli": "^3.1.2",
14+
"webpack-dev-server": "^3.1.10"
15+
},
16+
"dependencies": {
17+
"moment": "^2.22.2",
18+
"react": "^16.6.0",
19+
"react-dom": "^16.6.0"
20+
}
21+
}

examples/client-side/src/Hello.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export default () => 'Hello'

examples/client-side/src/index.js

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
import React from 'react'
2+
import { render } from 'react-dom'
3+
import loadable from '../../../packages/component'
4+
5+
const Hello = loadable(() => import('./Hello'))
6+
const Moment = loadable.lib(() => import('moment'))
7+
8+
const App = () => (
9+
<div>
10+
<Hello />
11+
<Moment>{({ default: moment }) => moment().format('HH:mm')}</Moment>
12+
</div>
13+
)
14+
15+
const root = document.createElement('div')
16+
document.body.append(root)
17+
18+
render(<App />, root)
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
const HtmlWebpackPlugin = require('html-webpack-plugin')
2+
3+
module.exports = {
4+
mode: 'development',
5+
module: {
6+
rules: [
7+
{
8+
test: /\.js?$/,
9+
exclude: /node_modules/,
10+
use: 'babel-loader',
11+
},
12+
],
13+
},
14+
plugins: [new HtmlWebpackPlugin()],
15+
}

0 commit comments

Comments
 (0)