Skip to content

Commit

Permalink
feat: add new loadable.lib, change API
Browse files Browse the repository at this point in the history
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
  • Loading branch information
gregberge committed Oct 30, 2018
1 parent cdbbbeb commit 94b2e87
Show file tree
Hide file tree
Showing 27 changed files with 8,767 additions and 236 deletions.
3 changes: 2 additions & 1 deletion .eslintrc.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
"react/jsx-wrap-multilines": "off",
"react/no-unused-state": "off",
"react/destructuring-assignment": "off",
"react/prop-types": "off"
"react/prop-types": "off",
"import/prefer-default-export": "off"
}
}
246 changes: 190 additions & 56 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,18 @@ Loadable leverage the limit of Code Splitting and give you access to all feature

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.

## Differences with React.lazy & react-loadable

[`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`.

| Library | Suspense | SSR | Library loading | import(`./${x}`) |
| --------------------- | -------- | --- | --------------- | ---------------- |
| `React.lazy` |||||
| `react-loadable` || 🔶 |||
| `@loadable/component` |||||

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.

## Getting started

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

### Loading library

`loadable.lib` lets you defer the loading of a library. It takes a render props called when the library is loaded.

```js
import loadable from '@loadable/component'

const Moment = loadable.lib(() => import('moment'))

function MyComponent() {
return (
<div>
<Moment fallback="xx:xx">
{({ default: moment }) => moment().format('HH:mm')}
</Moment>
</div>
)
}
```

You can also use a `ref` that will be populated when the library will be loaded.

```js
import loadable from '@loadable/component'

const Moment = loadable.lib(() => import('moment'))

class MyComponent {
moment = React.createRef()

handleClick = () => {
if (this.moment.current) {
return console.log(this.moment.current.default.format('HH:mm'))
}
}

render() {
return (
<div>
<button onClick={this.handleClick}>What time is it?</button>
<Moment ref={this.moment} />
</div>
)
}
}
```
### Full dynamic import
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.
Expand All @@ -62,12 +121,12 @@ function MyComponent() {
### Suspense
`@loadable/component` exposes a `lazy` method that acts similarly as `React.lazy` one.
`@loadable/component` exposes a `loadable.lazy` method that acts similarly as `React.lazy` one.
```js
import { lazy } from '@loadable/component'
import loadable from '@loadable/component'

const OtherComponent = lazy(() => import('./OtherComponent'))
const OtherComponent = loadable.lazy(() => import('./OtherComponent'))

function MyComponent() {
return (
Expand All @@ -80,60 +139,63 @@ function MyComponent() {
}
```
> Suspense is not yet available for server-side rendering.
> Use `loadable.lib.lazy` for libraries.
### Custom loading
> Suspense is not yet available for server-side rendering.
It is possible to add a custom loading component, by default it will render nothing.
### Fallback without Suspense
**Using a component**
You can specify a `fallback` in `loadable` options.
```js
const Loading = () => <div>Loading...</div>

const Home = loadable(() => import('./Home'), {
LoadingComponent: Loading,
const OtherComponent = loadable(() => import('./OtherComponent'), {
fallback: <div>Loading...</div>,
})
```

**Using render props**

```js
import React from 'react'

const Home = loadable(() => import('./Home'), {
render: ({ Component, loading, ownProps }) => {
if (loading) return <div>Loading...</div>
return <Component {...ownProps} />
},
})
function MyComponent() {
return (
<div>
<OtherComponent />
</div>
)
}
```
### Error handling

You can configure the component rendered when an error occurs during loading, by default it will display the error.

**Using a component**
You can also specify a `fallback` in props:
```js
const ErrorDisplay = ({ error }) => <div>Oups! {error.message}</div>
const OtherComponent = loadable(() => import('./OtherComponent'))

const Home = loadable(() => import('./Home'), {
ErrorComponent: ErrorDisplay,
})
function MyComponent() {
return (
<div>
<OtherComponent fallback={<div>Loading...</div>} />
</div>
)
}
```
**Using render props**
### Error boundaries
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.
```js
import React from 'react'
import MyErrorBoundary from '/MyErrorBoundary'
const OtherComponent = loadable.lazy(() => import('./OtherComponent'))
const AnotherComponent = loadable.lazy(() => import('./AnotherComponent'))

const Home = loadable(() => import('./Home'), {
render: ({ Component, error, ownProps }) => {
if (error) return <div>Oups! {error.message}</div>
return <Component {...ownProps} />
},
})
const MyComponent = () => (
<div>
<MyErrorBoundary>
<Suspense fallback={<div>Loading...</div>}>
<section>
<OtherComponent />
<AnotherComponent />
</section>
</Suspense>
</MyErrorBoundary>
</div>
)
```
### Delay
Expand All @@ -145,21 +207,9 @@ import loadable from '@loadable/component'
import pMinDelay from 'p-min-delay'

// Wait a minimum of 200ms before loading home.
export const Home = loadable(() => pMinDelay(import('./Home'), 200))
```

If you want to avoid these delay server-side:

```js
import loadable from '@loadable/component'
import pMinDelay from 'p-min-delay'

const delay = promise => {
if (typeof window === 'undefined') return promise
return pMinDelay(promise, 200)
}

export const Home = loadable(() => delay(import('./Home')))
export const OtherComponent = loadable(() =>
pMinDelay(import('./OtherComponent'), 200),
)
```
### Timeout
Expand All @@ -171,7 +221,9 @@ import loadable from '@loadable/component'
import { timeout } from 'promise-timeout'

// Wait a maximum of 2s before sending an error.
export const Home = loadable(() => timeout(import('./Home'), 2000))
export const OtherComponent = loadable(() =>
timeout(import('./OtherComponent'), 2000),
)
```
### Prefetching
Expand Down Expand Up @@ -213,8 +265,90 @@ function MyComponent() {
}
```
> `prefetch` and `Prefetch` are also available for components created with `loadable.lazy`, `loadable.lib` and `loadable.lib.lazy`.
> Only component based prefetching is compatible with Server Side Rendering.
## API
### loadable
Create a loadable component.
| Arguments | Description |
| ------------------ | ---------------------------------------- |
| `loadFn` | The function call to load the component. |
| `options` | Optional options. |
| `options.fallback` | Fallback displayed during the loading. |
```js
import loadable from '@loadable/component'

const OtherComponent = loadable(() => import('./OtherComponent'))
```
### loadableState.lazy
Create a loadable component "Suspense" ready.
| Arguments | Description |
| --------- | ---------------------------------------- |
| `loadFn` | The function call to load the component. |
```js
import loadable from '@loadable/component'

const OtherComponent = loadable.lazy(() => import('./OtherComponent'))
```
### LoadableComponent
A component created using `loadable` or `loadable.lazy`.
| Props | Description |
| ----- | ------------------------------------------------- |
| `...` | Props are forwarded as first argument of `loadFn` |
### loadable.lib
Create a loadable library.
| Arguments | Description |
| ------------------ | ---------------------------------------- |
| `loadFn` | The function call to load the component. |
| `options` | Optional options. |
| `options.fallback` | Fallback displayed during the loading. |
```js
import loadable from '@loadable/component'

const Moment = loadable.lib(() => import('moment'))
```
### loadable.lib.lazy
Create a loadable library "Suspense" ready.
| Arguments | Description |
| --------- | ---------------------------------------- |
| `loadFn` | The function call to load the component. |
```js
import loadable from '@loadable/component'

const Moment = loadable.lib.lazy(() => import('moment'))
```
### LoadableLibrary
A component created using `loadable.lib` or `loadable.lib.lazy`.
| Props | Description |
| ---------- | ---------------------------------------------------- |
| `children` | Function called when the library is loaded. |
| `ref` | Accepts a ref, populated when the library is loaded. |
| `...` | Props are forwarded as first argument of `loadFn` |
## [Server side rendering](https://github.com/smooth-code/loadable-components/tree/master/packages/server)
👉 [See `@loadable/server` documentation](https://github.com/smooth-code/loadable-components/tree/master/packages/server).
Expand Down
8 changes: 8 additions & 0 deletions examples/client-side/.eslintrc.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"env": {
"browser": true
},
"rules": {
"import/no-unresolved": "off"
}
}
4 changes: 4 additions & 0 deletions examples/client-side/babel.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
module.exports = {
presets: ['@babel/preset-react'],
plugins: ['@babel/plugin-syntax-dynamic-import'],
}
21 changes: 21 additions & 0 deletions examples/client-side/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
{
"name": "client-side",
"version": "1.0.0",
"main": "index.js",
"license": "MIT",
"scripts": {
"start": "webpack-dev-server"
},
"devDependencies": {
"babel-loader": "^8.0.4",
"html-webpack-plugin": "^3.2.0",
"webpack": "^4.23.1",
"webpack-cli": "^3.1.2",
"webpack-dev-server": "^3.1.10"
},
"dependencies": {
"moment": "^2.22.2",
"react": "^16.6.0",
"react-dom": "^16.6.0"
}
}
1 change: 1 addition & 0 deletions examples/client-side/src/Hello.js
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export default () => 'Hello'
18 changes: 18 additions & 0 deletions examples/client-side/src/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import React from 'react'
import { render } from 'react-dom'
import loadable from '../../../packages/component'

const Hello = loadable(() => import('./Hello'))
const Moment = loadable.lib(() => import('moment'))

const App = () => (
<div>
<Hello />
<Moment>{({ default: moment }) => moment().format('HH:mm')}</Moment>
</div>
)

const root = document.createElement('div')
document.body.append(root)

render(<App />, root)
15 changes: 15 additions & 0 deletions examples/client-side/webpack.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
const HtmlWebpackPlugin = require('html-webpack-plugin')

module.exports = {
mode: 'development',
module: {
rules: [
{
test: /\.js?$/,
exclude: /node_modules/,
use: 'babel-loader',
},
],
},
plugins: [new HtmlWebpackPlugin()],
}
Loading

0 comments on commit 94b2e87

Please sign in to comment.