Repository: jamiebuilds/react-loadable
Branch: master
Commit: 6201c5837b21
Files: 26
Total size: 62.7 KB
Directory structure:
gitextract_jyw8n8b2/
├── .babelrc
├── .flowconfig
├── .gitignore
├── .travis.yml
├── LICENSE
├── README.md
├── __fixtures__/
│ ├── component.es6.js
│ └── component.js
├── __tests__/
│ ├── .babelrc
│ ├── __snapshots__/
│ │ └── test.js.snap
│ └── test.js
├── babel.js
├── example/
│ ├── .babelrc
│ ├── client.js
│ ├── components/
│ │ ├── App.js
│ │ ├── Example.js
│ │ ├── ExampleNested.js
│ │ └── Loading.js
│ ├── server.js
│ └── utils/
│ └── delay.js
├── package.json
├── src/
│ ├── babel.js
│ ├── index.js
│ └── webpack.js
├── webpack.config.js
└── webpack.js
================================================
FILE CONTENTS
================================================
================================================
FILE: .babelrc
================================================
{
"presets": [
[
"@babel/preset-env",
{
"loose": true
}
],
"@babel/preset-react"
],
"plugins": [
"@babel/plugin-proposal-class-properties",
"@babel/plugin-transform-object-assign"
]
}
================================================
FILE: .flowconfig
================================================
[ignore]
<PROJECT_ROOT>/node_modules/chalk/.*
[include]
[libs]
[options]
================================================
FILE: .gitignore
================================================
.idea
node_modules
*.log
lib
coverage
example/dist
================================================
FILE: .travis.yml
================================================
language: node_js
node_js:
- '6'
- '8'
- '10'
cache: yarn
script: yarn test -- --runInBand --coverage && yarn flow
================================================
FILE: LICENSE
================================================
Copyright (c) 2018-present Jamie Kyle <me@thejameskyle.com>
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
================================================
FILE: README.md
================================================

> A higher order component for loading components with dynamic imports.
## Install
```sh
yarn add react-loadable
```
## Example
```js
import Loadable from 'react-loadable';
import Loading from './my-loading-component';
const LoadableComponent = Loadable({
loader: () => import('./my-component'),
loading: Loading,
});
export default class App extends React.Component {
render() {
return <LoadableComponent/>;
}
}
```
## Happy Customers:
- ["I'm obsessed with this right now: CRA with React Router v4 and react-loadable. Free code splitting, this is so easy."](https://twitter.com/matzatorski/status/872059865350406144)
- ["Oh hey - using loadable component I knocked 13K off my initial load. Easy win!"](https://twitter.com/AdamRackis/status/846593080992153600)
- ["Had a look and its awesome. shaved like 50kb off our main bundle."](https://github.com/quran/quran.com-frontend/pull/701#issuecomment-287908551)
- ["I've got that server-side rendering + code splitting + PWA ServiceWorker caching setup done 😎 (thanks to react-loadable). Now our frontend is super fast."](https://twitter.com/mxstbr/status/922375575217627136)
- ["Using react-loadable went from 221.28 KB → 115.76 KB @ main bundle. Fucking awesome and very simple API."](https://twitter.com/evgenyrodionov/status/958821614644269057)
- ["We've reduced our entry chunk by a lot & reduced initial load time by ~50%!"](https://github.com/jamiebuilds/react-loadable/pull/181)
- ["React-loadable is killer! We've decreased our load size by over 50kb with only 2 files! Can't wait to see how much lower it will go."](https://github.com/jamiebuilds/react-loadable/pull/180/)
## Users
- [AdHawk / Flooring Stores](https://www.flooringstores.com)
- [Akutbolig.dk](https://www.akutbolig.dk)
- [Analog.Cafe](https://www.analog.cafe)
- [Ambrosus](https://ambrosus.com)
- [Appbase.io](https://github.com/appbaseio/reactivesearch)
- [Atlassian](https://www.atlassian.com/)
- [BBC News](https://github.com/BBC-News/simorgh)
- [Blytzpay](https://www.blytzpay.com)
- [ClearTax](https://cleartax.in)
- [Cloudflare](https://www.cloudflare.com)
- [Chibaki](https://chibaki.co)
- [Compass](https://compass.com)
- [Curio](https://www.curio.org)
- [Delivery.com](https://www.delivery.com)
- [Doctor.com](https://www.doctor.com/)
- [Dollar Shave Club](https://github.com/dollarshaveclub)
- [Dresez](https://dresez.pk/)
- [Edcast](https://www.edcast.com/)
- [Evidation Health](https://evidation.com/)
- [Flexport](https://flexport.com/)
- [Flyhomes](https://flyhomes.com)
- [Gogo](https://gogoair.com)
- [Gofore](https://gofore.com/en/home/)
- [Graana](https://www.graana.com/)
- [Localie](https://localie.co/en)
- [MediaTek MCS-Lite](https://github.com/MCS-Lite)
- [NiYO Solutions Inc.](https://www.goniyo.com/)
- [Officepulse](https://www.officepulse.in/)
- [PageSpeed Green](https://pagespeed.green/)
- [Perx](https://www.perxtech.com/)
- [Plottu](https://public.plottu.com)
- [reformma](https://reformma.com.br)
- [Render](https://render.com)
- [Shift](https://shift.com)
- [Snipit](https://snipit.io)
- [Spectrum.chat](https://spectrum.chat)
- [Superblocks](https://superblocks.com)
- [Sprint Boards](https://sprintboards.io)
- [Talentpair](https://talentpair.com)
- [Tinder](https://tinder.com/)
- [Unsplash](https://unsplash.com/)
- [Wave](https://waveapps.com/)
- [WUZZUF](https://wuzzuf.net/)
- [Wxb](https://wxb.com/wxpush)
> _If your company or project is using React Loadable, please open a PR and add
> yourself to this list (in alphabetical order please)_
## Also See:
- [`react-loadable-visibility`](https://github.com/stratiformltd/react-loadable-visibility) - Building on top of and keeping the same API as `react-loadable`, this library enables you to load content that is visible on the screen.
- [`react-loadable-ssr-addon`](https://github.com/themgoncalves/react-loadable-ssr-addon) - Server Side Render add-on for `react-loadable`. Discover & load automatically dynamically all files dependencies, e.g. splitted chunks, css, etc.
<h2>
<hr>
<hr>
<img src="http://thejameskyle.com/img/react-loadable-guide.png" alt="GUIDE">
<hr>
<hr>
<small>Guide</small>
</h2>
So you've got your React app, you're bundling it with Webpack, and things are
going smooth. But then one day you notice your app's bundle is getting so big
that it's slowing things down.
It's time to start code-splitting your app!

Code-splitting is the process of taking one large bundle containing your entire
app, and splitting them up into multiple smaller bundles which contain separate
parts of your app.
This might seem difficult to do, but tools like Webpack have this built in, and
React Loadable is designed to make it super simple.
### Route-based splitting vs. Component-based splitting
A common piece of advice you will see is to break your app into separate routes
and load each one asynchronously. This seems to work well enough for many apps–
as a user, clicking a link and waiting for a page to load is a familiar
experience on the web.
But we can do better than that.
Using most routing tools for React, a route is simply a component. There's
nothing particularly special about them (Sorry Ryan and Michael– you're what's
special). So what if we optimized for splitting around components instead of
routes? What would that get us?

As it turns out: Quite a lot. There are many more places than just routes where
you can pretty easily split apart your app. Modals, tabs, and many more UI
components hide content until the user has done something to reveal it.
> **Example:** Maybe your app has a map buried inside of a tab component. Why
> would you load a massive mapping library for the parent route every time when
> the user may never go to that tab?
Not to mention all the places where you can defer loading content until higher
priority content is finished loading. That component at the bottom of your page
which loads a bunch of libraries: Why should that be loaded at the same time as
the content at the top?
And because routes are just components, we can still easily code-split at the
route level.
Introducing new code-splitting points in your app should be so easy that you
don't think twice about it. It should be a matter of changing a few lines of
code and everything else should be automated.
### Introducing React Loadable
React Loadable is a small library that makes component-centric code splitting
incredibly easy in React.
`Loadable` is a higher-order component (a function that creates a component)
which lets you dynamically load any module before rendering it into your app.
Let's imagine two components, one that imports and renders another.
```js
import Bar from './components/Bar';
class Foo extends React.Component {
render() {
return <Bar/>;
}
}
```
Right now we're depending on `Bar` being imported synchronously via `import`,
but we don't need it until we go to render it. So why don't we just defer that?
Using a **dynamic import** ([a tc39 proposal currently at Stage 3](https://github.com/tc39/proposal-dynamic-import))
we can modify our component to load `Bar` asynchronously.
```js
class MyComponent extends React.Component {
state = {
Bar: null
};
componentWillMount() {
import('./components/Bar').then(Bar => {
this.setState({ Bar: Bar.default });
});
}
render() {
let {Bar} = this.state;
if (!Bar) {
return <div>Loading...</div>;
} else {
return <Bar/>;
};
}
}
```
But that's a whole bunch of work, and it doesn't even handle a bunch of cases.
What about when `import()` fails? What about server-side rendering?
Instead you can use `Loadable` to abstract away the problem.
```js
import Loadable from 'react-loadable';
const LoadableBar = Loadable({
loader: () => import('./components/Bar'),
loading() {
return <div>Loading...</div>
}
});
class MyComponent extends React.Component {
render() {
return <LoadableBar/>;
}
}
```
### Automatic code-splitting on `import()`
When you use `import()` with Webpack 2+, it will
[automatically code-split](https://webpack.js.org/guides/code-splitting/) for
you with no additional configuration.
This means that you can easily experiment with new code splitting points just
by switching to `import()` and using React Loadable. Figure out what performs
best for your app.
### Creating a great "Loading..." Component
Rendering a static "Loading..." doesn't communicate enough to the user. You
also need to think about error states, timeouts, and making it a nice
experience.
```js
function Loading() {
return <div>Loading...</div>;
}
Loadable({
loader: () => import('./WillFailToLoad'), // oh no!
loading: Loading,
});
```
To make this all nice, your [loading component](#loadingcomponent) receives a
couple different props.
#### Loading error states
When your [`loader`](optsloader) fails, your [loading component](#loadingcomponent)
will receive an [`error`](propserror) prop which will be an `Error` object (otherwise it
will be `null`).
```js
function Loading(props) {
if (props.error) {
return <div>Error! <button onClick={ props.retry }>Retry</button></div>;
} else {
return <div>Loading...</div>;
}
}
```
#### Avoiding _Flash Of Loading Component_
Sometimes components load really quickly (<200ms) and the loading screen only
quickly flashes on the screen.
A number of user studies have proven that this causes users to perceive things
taking longer than they really have. If you don't show anything, users perceive
it as being faster.
So your loading component will also get a [`pastDelay` prop](#propspastdelay)
which will only be true once the component has taken longer to load than a set
[delay](#optsdelay).
```js
function Loading(props) {
if (props.error) {
return <div>Error! <button onClick={ props.retry }>Retry</button></div>;
} else if (props.pastDelay) {
return <div>Loading...</div>;
} else {
return null;
}
}
```
This delay defaults to `200ms` but you can also customize the
[delay](#optsdelay) in `Loadable`.
```js
Loadable({
loader: () => import('./components/Bar'),
loading: Loading,
delay: 300, // 0.3 seconds
});
```
#### Timing out when the `loader` is taking too long
Sometimes network connections suck and never resolve or fail, they just hang
there forever. This sucks for the user because they won't know if it should
always take this long, or if they should try refreshing.
The [loading component](#loadingcomponent) will receive a
[`timedOut` prop](#propstimedout) which will be set to `true` when the
[`loader`](#optsloader) has timed out.
```js
function Loading(props) {
if (props.error) {
return <div>Error! <button onClick={ props.retry }>Retry</button></div>;
} else if (props.timedOut) {
return <div>Taking a long time... <button onClick={ props.retry }>Retry</button></div>;
} else if (props.pastDelay) {
return <div>Loading...</div>;
} else {
return null;
}
}
```
However, this feature is disabled by default. To turn it on, you can pass a
[`timeout` option](#optstimeout) to `Loadable`.
```js
Loadable({
loader: () => import('./components/Bar'),
loading: Loading,
timeout: 10000, // 10 seconds
});
```
### Customizing rendering
By default `Loadable` will render the `default` export of the returned module.
If you want to customize this behavior you can use the
[`render` option](#optsrender).
```js
Loadable({
loader: () => import('./my-component'),
render(loaded, props) {
let Component = loaded.namedExport;
return <Component {...props}/>;
}
});
```
### Loading multiple resources
Technically you can do whatever you want within `loader()` as long as it
returns a promise and [you're able to render something](#customizing-rendering).
But writing it out can be a bit annoying.
To make it easier to load multiple resources in parallel, you can use
[`Loadable.Map`](#loadablemap).
```js
Loadable.Map({
loader: {
Bar: () => import('./Bar'),
i18n: () => fetch('./i18n/bar.json').then(res => res.json()),
},
render(loaded, props) {
let Bar = loaded.Bar.default;
let i18n = loaded.i18n;
return <Bar {...props} i18n={i18n}/>;
},
});
```
When using `Loadable.Map` the [`render()` method](#optsrender) is required. It
will be passed a `loaded` param which will be an object matching the shape of
your `loader`.
### Preloading
As an optimization, you can also decide to preload a component before it gets
rendered.
For example, if you need to load a new component when a button gets pressed,
you could start preloading the component when the user hovers over the button.
The component created by `Loadable` exposes a
[static `preload` method](#loadablecomponentpreload) which does exactly this.
```js
const LoadableBar = Loadable({
loader: () => import('./Bar'),
loading: Loading,
});
class MyComponent extends React.Component {
state = { showBar: false };
onClick = () => {
this.setState({ showBar: true });
};
onMouseOver = () => {
LoadableBar.preload();
};
render() {
return (
<div>
<button
onClick={this.onClick}
onMouseOver={this.onMouseOver}>
Show Bar
</button>
{this.state.showBar && <LoadableBar/>}
</div>
)
}
}
```
<h2>
<hr>
<hr>
<img src="http://thejameskyle.com/img/react-loadable-ssr.png" alt="SERVER SIDE RENDERING">
<hr>
<hr>
<small>Server-Side Rendering</small>
</h2>
When you go to render all these dynamically loaded components, what you'll get
is a whole bunch of loading screens.
This really sucks, but the good news is that React Loadable is designed to
make server-side rendering work as if nothing is being loaded dynamically.
Here's our starting server using [Express](https://expressjs.com/).
```js
import express from 'express';
import React from 'react';
import ReactDOMServer from 'react-dom/server';
import App from './components/App';
const app = express();
app.get('/', (req, res) => {
res.send(`
<!doctype html>
<html lang="en">
<head>...</head>
<body>
<div id="app">${ReactDOMServer.renderToString(<App/>)}</div>
<script src="/dist/main.js"></script>
</body>
</html>
`);
});
app.listen(3000, () => {
console.log('Running on http://localhost:3000/');
});
```
### Preloading all your loadable components on the server
The first step to rendering the correct content from the server is to make sure
that all of your loadable components are already loaded when you go to render
them.
To do this, you can use the [`Loadable.preloadAll`](#loadablepreloadall)
method. It returns a promise that will resolve when all your loadable
components are ready.
```js
Loadable.preloadAll().then(() => {
app.listen(3000, () => {
console.log('Running on http://localhost:3000/');
});
});
```
### Picking up a server-side rendered app on the client
This is where things get a little bit tricky. So let's prepare ourselves
little bit.
In order for us to pick up what was rendered from the server we need to have
all the same code that was used to render on the server.
To do this, we first need our loadable components telling us which modules they
are rendering.
#### Declaring which modules are being loaded
There are two options in [`Loadable`](#loadable) and
[`Loadable.Map`](#loadablemap) which are used to tell us which modules our
component is trying to load: [`opts.modules`](#optsmodules) and
[`opts.webpack`](#optswebpack).
```js
Loadable({
loader: () => import('./Bar'),
modules: ['./Bar'],
webpack: () => [require.resolveWeak('./Bar')],
});
```
But don't worry too much about these options. React Loadable includes a
[Babel plugin](#babel-plugin) to add them for you.
Just add the `react-loadable/babel` plugin to your Babel config:
```json
{
"plugins": [
"react-loadable/babel"
]
}
```
Now these options will automatically be provided.
For typescript you can use [react-loadable-ts-transformer](https://github.com/stushurik/react-loadable-ts-transformer) which is a ts analog of react-loadable/babel plugin.
#### Finding out which dynamic modules were rendered
Next we need to find out which modules were actually rendered when a request
comes in.
For this, there is [`Loadable.Capture`](#loadablecapture) component which can
be used to collect all the modules that were rendered.
```js
import Loadable from 'react-loadable';
app.get('/', (req, res) => {
let modules = [];
let html = ReactDOMServer.renderToString(
<Loadable.Capture report={moduleName => modules.push(moduleName)}>
<App/>
</Loadable.Capture>
);
console.log(modules);
res.send(`...${html}...`);
});
```
#### Mapping loaded modules to bundles
In order to make sure that the client loads all the modules that were rendered
server-side, we'll need to map them to the bundles that Webpack created.
This comes in two parts.
First we need Webpack to tell us which bundles each module lives inside. For
this there is the [React Loadable Webpack plugin](#webpack-plugin).
Import the `ReactLoadablePlugin` from `react-loadable/webpack` and include it
in your webpack config. Pass it a `filename` for where to store the JSON data
about our bundles.
```js
// webpack.config.js
import { ReactLoadablePlugin } from 'react-loadable/webpack';
export default {
plugins: [
new ReactLoadablePlugin({
filename: './dist/react-loadable.json',
}),
],
};
```
Then we'll go back to our server and use this data to convert our modules to
bundles.
To convert from modules to bundles, import the [`getBundles`](#getbundles)
method from `react-loadable/webpack` and the data from Webpack.
```js
import Loadable from 'react-loadable';
import { getBundles } from 'react-loadable/webpack'
import stats from './dist/react-loadable.json';
app.get('/', (req, res) => {
let modules = [];
let html = ReactDOMServer.renderToString(
<Loadable.Capture report={moduleName => modules.push(moduleName)}>
<App/>
</Loadable.Capture>
);
let bundles = getBundles(stats, modules);
// ...
});
```
We can then render these bundles into `<script>` tags in our HTML.
It is important that the bundles are included _before_ the main bundle, so that
they can be loaded by the browser prior to the app rendering.
However, as the Webpack manifest (including the logic for parsing bundles) lives in
the main bundle, it will need to be extracted into its own chunk.
This is easy to do with the [CommonsChunkPlugin](https://webpack.js.org/plugins/commons-chunk-plugin/)
```js
// webpack.config.js
export default {
plugins: [
new webpack.optimize.CommonsChunkPlugin({
name: 'manifest',
minChunks: Infinity
})
]
}
```
_Notice: As of Webpack 4 the CommonsChunkPlugin has been removed and the manifest doesn't need to be extracted anymore._
```js
let bundles = getBundles(stats, modules);
res.send(`
<!doctype html>
<html lang="en">
<head>...</head>
<body>
<div id="app">${html}</div>
<script src="/dist/manifest.js"></script>
<script src="/dist/main.js"></script>
${bundles.map(bundle => {
return `<script src="/dist/${bundle.file}"></script>`
// alternatively if you are using publicPath option in webpack config
// you can use the publicPath value from bundle, e.g:
// return `<script src="${bundle.publicPath}"></script>`
}).join('\n')}
<script>window.main();</script>
</body>
</html>
`);
```
#### Preloading ready loadable components on the client
We can use the [`Loadable.preloadReady()`](#loadablepreloadready) method on the
client to preload the loadable components that were included on the page.
Like [`Loadable.preloadAll()`](#loadablepreloadall), it returns a promise,
which on resolution means that we can hydrate our app.
```js
// src/entry.js
import React from 'react';
import ReactDOM from 'react-dom';
import Loadable from 'react-loadable';
import App from './components/App';
window.main = () => {
Loadable.preloadReady().then(() => {
ReactDOM.hydrate(<App/>, document.getElementById('app'));
});
};
```
<h4 align="center">
Now server-side rendering should work perfectly!
</h4>
<h2>
<hr>
<hr>
<img src="http://thejameskyle.com/img/react-loadable-api-docs.png" alt="API DOCS">
<hr>
<hr>
<small>API Docs</small>
</h2>
### `Loadable`
A higher-order component for dynamically [loading](#optsloader) a module before
[rendering](#optsrender) it, a [loading](#opts.loading) component is rendered
while the module is unavailable.
```js
const LoadableComponent = Loadable({
loader: () => import('./Bar'),
loading: Loading,
delay: 200,
timeout: 10000,
});
```
This returns a [LoadableComponent](#loadablecomponent).
### `Loadable.Map`
A higher-order component that allows you to load multiple resources in parallel.
Loadable.Map's [`opts.loader`](#optsloader) accepts an object of functions, and
needs a [`opts.render`](#optsrender) method.
```js
Loadable.Map({
loader: {
Bar: () => import('./Bar'),
i18n: () => fetch('./i18n/bar.json').then(res => res.json()),
},
render(loaded, props) {
let Bar = loaded.Bar.default;
let i18n = loaded.i18n;
return <Bar {...props} i18n={i18n}/>;
}
});
```
When using `Loadable.Map` the `render()` method's `loaded` param will be an
object with the same shape as your `loader`.
### `Loadable` and `Loadable.Map` Options
#### `opts.loader`
A function returning a promise that loads your module.
```js
Loadable({
loader: () => import('./Bar'),
});
```
When using with [`Loadable.Map`](#loadablemap) this accepts an object of these
types of functions.
```js
Loadable.Map({
loader: {
Bar: () => import('./Bar'),
i18n: () => fetch('./i18n/bar.json').then(res => res.json()),
},
});
```
When using with `Loadable.Map` you'll also need to pass a
[`opts.render`](#optsrender) function.
#### `opts.loading`
A [`LoadingComponent`](#loadingcomponent) that renders while a module is
loading or when it errors.
```js
Loadable({
loading: LoadingComponent,
});
```
This option is required, if you don't want to render anything, return `null`.
```js
Loadable({
loading: () => null,
});
```
#### `opts.delay`
Time to wait (in milliseconds) before passing
[`props.pastDelay`](#propspastdelay) to your [`loading`](#optsloading)
component. This defaults to `200`.
```js
Loadable({
delay: 200
});
```
[Read more about delays](#avoiding-flash-of-loading-component).
#### `opts.timeout`
Time to wait (in milliseconds) before passing
[`props.timedOut`](#propstimedout) to your [`loading`](#optsloading) component.
This is turned off by default.
```js
Loadable({
timeout: 10000
});
```
[Read more about timeouts](#timing-out-when-the-loader-is-taking-too-long).
#### `opts.render`
A function to customize the rendering of loaded modules.
Receives `loaded` which is the resolved value of [`opts.loader`](#optsloader)
and `props` which are the props passed to the
[`LoadableComponent`](#loadablecomponent).
```js
Loadable({
render(loaded, props) {
let Component = loaded.default;
return <Component {...props}/>;
}
});
```
#### `opts.webpack`
An optional function which returns an array of Webpack module ids which you can
get with `require.resolveWeak`.
```js
Loadable({
loader: () => import('./Foo'),
webpack: () => [require.resolveWeak('./Foo')],
});
```
This option can be automated with the [Babel Plugin](#babel-plugin).
#### `opts.modules`
An optional array with module paths for your imports.
```js
Loadable({
loader: () => import('./my-component'),
modules: ['./my-component'],
});
```
This option can be automated with the [Babel Plugin](#babel-plugin).
### `LoadableComponent`
This is the component returned by `Loadable` and `Loadable.Map`.
```js
const LoadableComponent = Loadable({
// ...
});
```
Props passed to this component will be passed straight through to the
dynamically loaded component via [`opts.render`](#optsrender).
#### `LoadableComponent.preload()`
This is a static method on [`LoadableComponent`](#loadablecomponent) which can
be used to load the component ahead of time.
```js
const LoadableComponent = Loadable({...});
LoadableComponent.preload();
```
This returns a promise, but you should avoid waiting for that promise to
resolve to update your UI. In most cases it creates a bad user experience.
[Read more about preloading](#preloading).
### `LoadingComponent`
This is the component you pass to [`opts.loading`](#optsloading).
```js
function LoadingComponent(props) {
if (props.error) {
// When the loader has errored
return <div>Error! <button onClick={ props.retry }>Retry</button></div>;
} else if (props.timedOut) {
// When the loader has taken longer than the timeout
return <div>Taking a long time... <button onClick={ props.retry }>Retry</button></div>;
} else if (props.pastDelay) {
// When the loader has taken longer than the delay
return <div>Loading...</div>;
} else {
// When the loader has just started
return null;
}
}
Loadable({
loading: LoadingComponent,
});
```
[Read more about loading components](#creating-a-great-loading-component)
#### `props.error`
An `Error` object passed to [`LoadingComponent`](#loadingcomponent) when the
[`loader`](#optsloader) has failed. When there is no error, `null` is
passed.
```js
function LoadingComponent(props) {
if (props.error) {
return <div>Error!</div>;
} else {
return <div>Loading...</div>;
}
}
```
[Read more about errors](#loading-error-states).
#### `props.retry`
A function prop passed to [`LoadingComponent`](#loadingcomponent) when the
[`loader`](#optsloader) has failed, used to retry loading the component.
```js
function LoadingComponent(props) {
if (props.error) {
return <div>Error! <button onClick={ props.retry }>Retry</button></div>;
} else {
return <div>Loading...</div>;
}
}
```
[Read more about errors](#loading-error-states).
#### `props.timedOut`
A boolean prop passed to [`LoadingComponent`](#loadingcomponent) after a set
[`timeout`](#optstimeout).
```js
function LoadingComponent(props) {
if (props.timedOut) {
return <div>Taking a long time...</div>;
} else {
return <div>Loading...</div>;
}
}
```
[Read more about timeouts](#timing-out-when-the-loader-is-taking-too-long).
#### `props.pastDelay`
A boolean prop passed to [`LoadingComponent`](#loadingcomponent) after a set
[`delay`](#optsdelay).
```js
function LoadingComponent(props) {
if (props.pastDelay) {
return <div>Loading...</div>;
} else {
return null;
}
}
```
[Read more about delays](#avoiding-flash-of-loading-component).
### `Loadable.preloadAll()`
This will call all of the
[`LoadableComponent.preload`](#loadablecomponentpreload) methods recursively
until they are all resolved. Allowing you to preload all of your dynamic
modules in environments like the server.
```js
Loadable.preloadAll().then(() => {
app.listen(3000, () => {
console.log('Running on http://localhost:3000/');
});
});
```
It's important to note that this requires that you declare all of your loadable
components when modules are initialized rather than when your app is being
rendered.
**Good:**
```js
// During module initialization...
const LoadableComponent = Loadable({...});
class MyComponent extends React.Component {
componentDidMount() {
// ...
}
}
```
**Bad:**
```js
// ...
class MyComponent extends React.Component {
componentDidMount() {
// During app render...
const LoadableComponent = Loadable({...});
}
}
```
> **Note:** `Loadable.preloadAll()` will not work if you have more than one
> copy of `react-loadable` in your app.
[Read more about preloading on the server](#preloading-all-your-loadable-components-on-the-server).
### `Loadable.preloadReady()`
Check for modules that are already loaded in the browser and call the matching
[`LoadableComponent.preload`](#loadablecomponentpreload) methods.
```js
Loadable.preloadReady().then(() => {
ReactDOM.hydrate(<App/>, document.getElementById('app'));
});
```
[Read more about preloading on the client](#waiting-to-render-on-the-client-until-all-the-bundles-are-loaded).
### `Loadable.Capture`
A component for reporting which modules were rendered.
Accepts a `report` prop which is called for every `moduleName` that is
rendered via React Loadable.
```js
let modules = [];
let html = ReactDOMServer.renderToString(
<Loadable.Capture report={moduleName => modules.push(moduleName)}>
<App/>
</Loadable.Capture>
);
console.log(modules);
```
[Read more about capturing rendered modules](#finding-out-which-dynamic-modules-were-rendered).
## Babel Plugin
Providing [`opts.webpack`](#optswebpack) and [`opts.modules`](#optsmodules) for
every loadable component is a lot of manual work to remember to do.
Instead you can add the Babel plugin to your config and it will automate it for
you:
```json
{
"plugins": ["react-loadable/babel"]
}
```
**Input**
```js
import Loadable from 'react-loadable';
const LoadableMyComponent = Loadable({
loader: () => import('./MyComponent'),
});
const LoadableComponents = Loadable.Map({
loader: {
One: () => import('./One'),
Two: () => import('./Two'),
},
});
```
**Output**
```js
import Loadable from 'react-loadable';
import path from 'path';
const LoadableMyComponent = Loadable({
loader: () => import('./MyComponent'),
webpack: () => [require.resolveWeak('./MyComponent')],
modules: [path.join(__dirname, './MyComponent')],
});
const LoadableComponents = Loadable.Map({
loader: {
One: () => import('./One'),
Two: () => import('./Two'),
},
webpack: () => [require.resolveWeak('./One'), require.resolveWeak('./Two')],
modules: [path.join(__dirname, './One'), path.join(__dirname, './Two')],
});
```
[Read more about declaring modules](#declaring-which-modules-are-being-loaded).
## Webpack Plugin
In order to [send the right bundles down](#mapping-loaded-modules-to-bundles)
when rendering server-side, you'll need the React Loadable Webpack plugin
to provide you with a mapping of modules to bundles.
```js
// webpack.config.js
import { ReactLoadablePlugin } from 'react-loadable/webpack';
export default {
plugins: [
new ReactLoadablePlugin({
filename: './dist/react-loadable.json',
}),
],
};
```
This will create a file (`opts.filename`) which you can import to map modules
to bundles.
[Read more about mapping modules to bundles](#mapping-loaded-modules-to-bundles).
### `getBundles`
A method exported by `react-loadable/webpack` for converting modules to
bundles.
```js
import { getBundles } from 'react-loadable/webpack';
let bundles = getBundles(stats, modules);
```
[Read more about mapping modules to bundles](#mapping-loaded-modules-to-bundles).
<h2>
<hr>
<hr>
<img src="http://thejameskyle.com/img/react-loadable-faq.png" alt="FAQ">
<hr>
<hr>
<small>FAQ</small>
</h2>
### How do I avoid repetition?
Specifying the same `loading` component or `delay` every time you use
`Loadable()` gets repetitive fast. Instead you can wrap `Loadable` with your
own Higher-Order Component (HOC) to set default options.
```js
import Loadable from 'react-loadable';
import Loading from './my-loading-component';
export default function MyLoadable(opts) {
return Loadable(Object.assign({
loading: Loading,
delay: 200,
timeout: 10000,
}, opts));
};
```
Then you can just specify a `loader` when you go to use it.
```js
import MyLoadable from './MyLoadable';
const LoadableMyComponent = MyLoadable({
loader: () => import('./MyComponent'),
});
export default class App extends React.Component {
render() {
return <LoadableMyComponent/>;
}
}
```
Unfortunately at the moment using wrapped Loadable breaks [react-loadable/babel](#babel-plugin) so in such case you have to add required properties (`modules`, `webpack`) manually.
```js
import MyLoadable from './MyLoadable';
const LoadableMyComponent = MyLoadable({
loader: () => import('./MyComponent'),
modules: ['./MyComponent'],
webpack: () => [require.resolveWeak('./MyComponent')],
});
export default class App extends React.Component {
render() {
return <LoadableMyComponent/>;
}
}
```
### How do I handle other styles `.css` or sourcemaps `.map` with server-side rendering?
When you call [`getBundles`](#getbundles), it may return file types other than
JavaScript depending on your Webpack configuration.
To handle this, you should manually filter down to the file extensions that
you care about:
```js
let bundles = getBundles(stats, modules);
let styles = bundles.filter(bundle => bundle.file.endsWith('.css'));
let scripts = bundles.filter(bundle => bundle.file.endsWith('.js'));
res.send(`
<!doctype html>
<html lang="en">
<head>
...
${styles.map(style => {
return `<link href="/dist/${style.file}" rel="stylesheet"/>`
}).join('\n')}
</head>
<body>
<div id="app">${html}</div>
<script src="/dist/main.js"></script>
${scripts.map(script => {
return `<script src="/dist/${script.file}"></script>`
}).join('\n')}
</body>
</html>
`);
```
================================================
FILE: __fixtures__/component.es6.js
================================================
import React from "react";
export default class FixtureComponent extends React.Component {
render() {
return <div>fixture2</div>;
}
}
================================================
FILE: __fixtures__/component.js
================================================
const React = require("react");
module.exports = function Component() {
return <div>fixture1</div>;
};
================================================
FILE: __tests__/.babelrc
================================================
{
"presets": [
[
"@babel/preset-env",
{
"loose": true
}
],
"@babel/preset-react"
],
"plugins": [
"@babel/plugin-transform-async-to-generator"
]
}
================================================
FILE: __tests__/__snapshots__/test.js.snap
================================================
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`delay and timeout 1`] = `
<div>
MyLoadingComponent
{"isLoading":true,"pastDelay":false,"timedOut":false,"error":null}
</div>
`;
exports[`delay and timeout 2`] = `
<div>
MyLoadingComponent
{"isLoading":true,"pastDelay":true,"timedOut":false,"error":null}
</div>
`;
exports[`delay and timeout 3`] = `
<div>
MyLoadingComponent
{"isLoading":true,"pastDelay":true,"timedOut":true,"error":null}
</div>
`;
exports[`delay and timeout 4`] = `
<div>
MyComponent
{"prop":"foo"}
</div>
`;
exports[`loadable map error 1`] = `
<div>
MyLoadingComponent
{"isLoading":true,"pastDelay":false,"timedOut":false,"error":null}
</div>
`;
exports[`loadable map error 2`] = `
<div>
MyLoadingComponent
{"isLoading":true,"pastDelay":true,"timedOut":false,"error":null}
</div>
`;
exports[`loadable map error 3`] = `
<div>
MyLoadingComponent
{"isLoading":false,"pastDelay":true,"timedOut":false,"error":{}}
</div>
`;
exports[`loadable map success 1`] = `
<div>
MyLoadingComponent
{"isLoading":true,"pastDelay":false,"timedOut":false,"error":null}
</div>
`;
exports[`loadable map success 2`] = `
<div>
MyLoadingComponent
{"isLoading":true,"pastDelay":true,"timedOut":false,"error":null}
</div>
`;
exports[`loadable map success 3`] = `
<div>
<div>
MyComponent
{"prop":"baz"}
</div>
<div>
MyComponent
{"prop":"baz"}
</div>
</div>
`;
exports[`loading error 1`] = `
<div>
MyLoadingComponent
{"isLoading":true,"pastDelay":false,"timedOut":false,"error":null}
</div>
`;
exports[`loading error 2`] = `
<div>
MyLoadingComponent
{"isLoading":true,"pastDelay":true,"timedOut":false,"error":null}
</div>
`;
exports[`loading error 3`] = `
<div>
MyLoadingComponent
{"isLoading":false,"pastDelay":true,"timedOut":false,"error":{}}
</div>
`;
exports[`loading success 1`] = `
<div>
MyLoadingComponent
{"isLoading":true,"pastDelay":false,"timedOut":false,"error":null}
</div>
`;
exports[`loading success 2`] = `
<div>
MyLoadingComponent
{"isLoading":true,"pastDelay":true,"timedOut":false,"error":null}
</div>
`;
exports[`loading success 3`] = `
<div>
MyComponent
{"prop":"foo"}
</div>
`;
exports[`loading success 4`] = `
<div>
MyComponent
{"prop":"bar"}
</div>
`;
exports[`preload 1`] = `
<div>
MyLoadingComponent
{"isLoading":true,"pastDelay":false,"timedOut":false,"error":null}
</div>
`;
exports[`preload 2`] = `
<div>
MyComponent
{"prop":"baz"}
</div>
`;
exports[`preload 3`] = `
<div>
MyComponent
{"prop":"baz"}
</div>
`;
exports[`preloadReady delay with 0 1`] = `
<div>
MyLoadingComponent
{"isLoading":true,"pastDelay":true,"timedOut":false,"error":null}
</div>
`;
exports[`preloadReady many 1`] = `
<div>
MyComponent
{"prop":"baz"}
</div>
`;
exports[`preloadReady missing 1`] = `
<div>
MyLoadingComponent
{"isLoading":true,"pastDelay":false,"timedOut":false,"error":null}
</div>
`;
exports[`preloadReady one 1`] = `
<div>
MyComponent
{"prop":"baz"}
</div>
`;
exports[`preloadReady undefined 1`] = `
<div>
MyLoadingComponent
{"isLoading":true,"pastDelay":false,"timedOut":false,"error":null}
</div>
`;
exports[`render 1`] = `
<div>
MyLoadingComponent
{"isLoading":true,"pastDelay":false,"timedOut":false,"error":null}
</div>
`;
exports[`render 2`] = `
<div>
MyLoadingComponent
{"isLoading":true,"pastDelay":true,"timedOut":false,"error":null}
</div>
`;
exports[`render 3`] = `
<div>
MyComponent
{"prop":"baz"}
</div>
`;
exports[`server side rendering 1`] = `
<div>
fixture1
</div>
`;
exports[`server side rendering es6 1`] = `
<div>
fixture2
</div>
`;
================================================
FILE: __tests__/test.js
================================================
'use strict';
const path = require('path');
const React = require('react');
const renderer = require('react-test-renderer');
const Loadable = require('../src');
function waitFor(delay) {
return new Promise(resolve => {
setTimeout(resolve, delay);
});
}
function createLoader(delay, loader, error) {
return () => {
return waitFor(delay).then(() => {
if (loader) {
return loader();
} else {
throw error;
}
});
};
}
function MyLoadingComponent(props) {
return <div>MyLoadingComponent {JSON.stringify(props)}</div>;
}
function MyComponent(props) {
return <div>MyComponent {JSON.stringify(props)}</div>;
}
afterEach(async () => {
try {
await Loadable.preloadAll();
} catch (err) {}
});
test('loading success', async () => {
let LoadableMyComponent = Loadable({
loader: createLoader(400, () => MyComponent),
loading: MyLoadingComponent
});
let component1 = renderer.create(<LoadableMyComponent prop="foo" />);
expect(component1.toJSON()).toMatchSnapshot(); // initial
await waitFor(200);
expect(component1.toJSON()).toMatchSnapshot(); // loading
await waitFor(200);
expect(component1.toJSON()).toMatchSnapshot(); // loaded
let component2 = renderer.create(<LoadableMyComponent prop="bar" />);
expect(component2.toJSON()).toMatchSnapshot(); // reload
});
test('delay and timeout', async () => {
let LoadableMyComponent = Loadable({
loader: createLoader(300, () => MyComponent),
loading: MyLoadingComponent,
delay: 100,
timeout: 200,
});
let component1 = renderer.create(<LoadableMyComponent prop="foo" />);
expect(component1.toJSON()).toMatchSnapshot(); // initial
await waitFor(100);
expect(component1.toJSON()).toMatchSnapshot(); // loading
await waitFor(100);
expect(component1.toJSON()).toMatchSnapshot(); // timed out
await waitFor(100);
expect(component1.toJSON()).toMatchSnapshot(); // loaded
});
test('loading error', async () => {
let LoadableMyComponent = Loadable({
loader: createLoader(400, null, new Error('test error')),
loading: MyLoadingComponent
});
let component = renderer.create(<LoadableMyComponent prop="baz" />);
expect(component.toJSON()).toMatchSnapshot(); // initial
await waitFor(200);
expect(component.toJSON()).toMatchSnapshot(); // loading
await waitFor(200);
expect(component.toJSON()).toMatchSnapshot(); // errored
});
test('server side rendering', async () => {
let LoadableMyComponent = Loadable({
loader: createLoader(400, () => require('../__fixtures__/component')),
loading: MyLoadingComponent,
});
await Loadable.preloadAll();
let component = renderer.create(<LoadableMyComponent prop="baz" />);
expect(component.toJSON()).toMatchSnapshot(); // serverside
});
test('server side rendering es6', async () => {
let LoadableMyComponent = Loadable({
loader: createLoader(400, () => require('../__fixtures__/component.es6')),
loading: MyLoadingComponent,
});
await Loadable.preloadAll();
let component = renderer.create(<LoadableMyComponent prop="baz" />);
expect(component.toJSON()).toMatchSnapshot(); // serverside
});
test('preload', async () => {
let LoadableMyComponent = Loadable({
loader: createLoader(400, () => MyComponent),
loading: MyLoadingComponent
});
let promise = LoadableMyComponent.preload();
await waitFor(200);
let component1 = renderer.create(<LoadableMyComponent prop="baz" />);
expect(component1.toJSON()).toMatchSnapshot(); // still loading...
await promise;
expect(component1.toJSON()).toMatchSnapshot(); // success
let component2 = renderer.create(<LoadableMyComponent prop="baz" />);
expect(component2.toJSON()).toMatchSnapshot(); // success
});
test('render', async () => {
let LoadableMyComponent = Loadable({
loader: createLoader(400, () => ({ MyComponent })),
loading: MyLoadingComponent,
render(loaded, props) {
return <loaded.MyComponent {...props}/>;
}
});
let component = renderer.create(<LoadableMyComponent prop="baz" />);
expect(component.toJSON()).toMatchSnapshot(); // initial
await waitFor(200);
expect(component.toJSON()).toMatchSnapshot(); // loading
await waitFor(200);
expect(component.toJSON()).toMatchSnapshot(); // success
});
test('loadable map success', async () => {
let LoadableMyComponent = Loadable.Map({
loader: {
a: createLoader(200, () => ({ MyComponent })),
b: createLoader(400, () => ({ MyComponent })),
},
loading: MyLoadingComponent,
render(loaded, props) {
return (
<div>
<loaded.a.MyComponent {...props}/>
<loaded.b.MyComponent {...props}/>
</div>
);
}
});
let component = renderer.create(<LoadableMyComponent prop="baz" />);
expect(component.toJSON()).toMatchSnapshot(); // initial
await waitFor(200);
expect(component.toJSON()).toMatchSnapshot(); // loading
await waitFor(200);
expect(component.toJSON()).toMatchSnapshot(); // success
});
test('loadable map error', async () => {
let LoadableMyComponent = Loadable.Map({
loader: {
a: createLoader(200, () => ({ MyComponent })),
b: createLoader(400, null, new Error('test error')),
},
loading: MyLoadingComponent,
render(loaded, props) {
return (
<div>
<loaded.a.MyComponent {...props}/>
<loaded.b.MyComponent {...props}/>
</div>
);
}
});
let component = renderer.create(<LoadableMyComponent prop="baz" />);
expect(component.toJSON()).toMatchSnapshot(); // initial
await waitFor(200);
expect(component.toJSON()).toMatchSnapshot(); // loading
await waitFor(200);
expect(component.toJSON()).toMatchSnapshot(); // success
});
describe('preloadReady', () => {
beforeEach(() => {
global.__webpack_modules__ = { 1: true, 2: true };
});
afterEach(() => {
delete global.__webpack_modules__;
});
test('undefined', async () => {
let LoadableMyComponent = Loadable({
loader: createLoader(200, () => MyComponent),
loading: MyLoadingComponent,
});
await Loadable.preloadReady();
let component = renderer.create(<LoadableMyComponent prop="baz" />);
expect(component.toJSON()).toMatchSnapshot();
});
test('one', async () => {
let LoadableMyComponent = Loadable({
loader: createLoader(200, () => MyComponent),
loading: MyLoadingComponent,
webpack: () => [1],
});
await Loadable.preloadReady();
let component = renderer.create(<LoadableMyComponent prop="baz" />);
expect(component.toJSON()).toMatchSnapshot();
});
test('many', async () => {
let LoadableMyComponent = Loadable({
loader: createLoader(200, () => MyComponent),
loading: MyLoadingComponent,
webpack: () => [1, 2],
});
await Loadable.preloadReady();
let component = renderer.create(<LoadableMyComponent prop="baz" />);
expect(component.toJSON()).toMatchSnapshot();
});
test('missing', async () => {
let LoadableMyComponent = Loadable({
loader: createLoader(200, () => MyComponent),
loading: MyLoadingComponent,
webpack: () => [1, 42],
});
await Loadable.preloadReady();
let component = renderer.create(<LoadableMyComponent prop="baz" />);
expect(component.toJSON()).toMatchSnapshot();
});
test('delay with 0', () => {
let LoadableMyComponent = Loadable({
loader: createLoader(300, () => MyComponent),
loading: MyLoadingComponent,
delay: 0,
timeout: 200,
});
let loadingComponent = renderer.create(<LoadableMyComponent prop="foo" />);
expect(loadingComponent.toJSON()).toMatchSnapshot(); // loading
});
});
================================================
FILE: babel.js
================================================
module.exports = require('./lib/babel');
================================================
FILE: example/.babelrc
================================================
{
"presets": [
"@babel/preset-env",
"@babel/preset-react"
],
"plugins": [
"dynamic-import-node",
"@babel/plugin-proposal-class-properties",
"../babel",
[
"module-resolver",
{
"alias": {
"react-loadable": "./src/index.js",
"react-loadable-webpack": "./src/webpack.js"
}
}
]
]
}
================================================
FILE: example/client.js
================================================
import React from 'react';
import ReactDOM from 'react-dom';
import Loadable from 'react-loadable';
import App from './components/App';
window.main = () => {
Loadable.preloadReady().then(() => {
ReactDOM.hydrate(<App/>, document.getElementById('app'));
});
};
================================================
FILE: example/components/App.js
================================================
import React from 'react';
import Loadable from 'react-loadable';
import Loading from './Loading';
import delay from '../utils/delay';
import path from 'path';
const LoadableExample = Loadable({
loader: () => import('./Example'),
loading: Loading,
});
export default function App() {
return <LoadableExample/>;
}
================================================
FILE: example/components/Example.js
================================================
import React from 'react';
import Loadable from 'react-loadable';
import Loading from './Loading';
import delay from '../utils/delay';
import path from 'path';
const LoadableNested = Loadable({
loader: () => import('./ExampleNested'),
loading: Loading,
});
export default function Example() {
return (
<div>
<h1>Hello from a loadable component</h1>
<LoadableNested/>
</div>
);
}
================================================
FILE: example/components/ExampleNested.js
================================================
import React from 'react';
export default function ExampleNested() {
return <p>Hello from a nested loadable component!</p>;
}
================================================
FILE: example/components/Loading.js
================================================
import React from 'react';
export default function Loading(props) {
if (props.isLoading) {
if (props.timedOut) {
return <div>Loader timed out!</div>;
} else if (props.pastDelay) {
return <div>Loading...</div>;
} else {
return null;
}
} else if (props.error) {
return <div>Error! Component failed to load</div>;
} else {
return null;
}
}
================================================
FILE: example/server.js
================================================
import express from 'express';
import path from 'path';
import React from 'react';
import ReactDOMServer from 'react-dom/server';
import Loadable from 'react-loadable';
import { getBundles } from 'react-loadable-webpack'
import App from './components/App';
const stats = require('./dist/react-loadable.json');
const app = express();
app.get('/', (req, res) => {
let modules = [];
let html = ReactDOMServer.renderToString(
<Loadable.Capture report={moduleName => modules.push(moduleName)}>
<App/>
</Loadable.Capture>
);
let bundles = getBundles(stats, modules);
let styles = bundles.filter(bundle => bundle.file.endsWith('.css'));
let scripts = bundles.filter(bundle => bundle.file.endsWith('.js'));
res.send(`
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>My App</title>
${styles.map(style => {
return `<link href="/dist/${style.file}" rel="stylesheet"/>`;
}).join('\n')}
</head>
<body>
<div id="app">${html}</div>
<script src="/dist/main.js"></script>
${scripts.map(script => {
return `<script src="/dist/${script.file}"></script>`
}).join('\n')}
<script>window.main();</script>
</body>
</html>
`);
});
app.use('/dist', express.static(path.join(__dirname, 'dist')));
Loadable.preloadAll().then(() => {
app.listen(3000, () => {
console.log('Running on http://localhost:3000/');
});
}).catch(err => {
console.log(err);
});
================================================
FILE: example/utils/delay.js
================================================
export default function delay(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
================================================
FILE: package.json
================================================
{
"name": "react-loadable",
"version": "5.5.0",
"description": "A higher order component for loading components with promises",
"main": "lib/index.js",
"author": "James Kyle <me@thejameskyle.com>",
"license": "MIT",
"repository": "thejameskyle/react-loadable",
"files": [
"babel.js",
"webpack.js",
"lib/**"
],
"scripts": {
"test": "jest --coverage",
"build": "babel src -d lib",
"start": "yarn build && webpack && babel-node example/server.js",
"prepare": "yarn build"
},
"dependencies": {
"prop-types": "^15.6.2"
},
"devDependencies": {
"@babel/cli": "^7.1.2",
"@babel/core": "^7.1.2",
"@babel/node": "^7.0.0",
"@babel/plugin-proposal-class-properties": "^7.1.0",
"@babel/plugin-transform-async-to-generator": "^7.1.0",
"@babel/plugin-transform-object-assign": "^7.0.0",
"@babel/preset-env": "^7.1.0",
"@babel/preset-react": "^7.0.0",
"babel-core": "^7.0.0-bridge.0",
"babel-jest": "^23.6.0",
"babel-loader": "^8.0.4",
"babel-plugin-dynamic-import-node": "^2.2.0",
"babel-plugin-module-resolver": "^3.1.1",
"express": "^4.16.4",
"flow-bin": "^0.83.0",
"jest": "^23.6.0",
"react": "^16.5.2",
"react-dom": "^16.5.2",
"react-test-renderer": "^16.5.2",
"webpack": "^4.20.2"
},
"peerDependencies": {
"react": "*"
}
}
================================================
FILE: src/babel.js
================================================
export default function({ types: t, template }) {
return {
visitor: {
ImportDeclaration(path) {
let source = path.node.source.value;
if (source !== 'react-loadable') return;
let defaultSpecifier = path.get('specifiers').find(specifier => {
return specifier.isImportDefaultSpecifier();
});
if (!defaultSpecifier) return;
let bindingName = defaultSpecifier.node.local.name;
let binding = path.scope.getBinding(bindingName);
binding.referencePaths.forEach(refPath => {
let callExpression = refPath.parentPath;
if (
callExpression.isMemberExpression() &&
callExpression.node.computed === false &&
callExpression.get('property').isIdentifier({ name: 'Map' })
) {
callExpression = callExpression.parentPath;
}
if (!callExpression.isCallExpression()) return;
let args = callExpression.get('arguments');
if (args.length !== 1) throw callExpression.error;
let options = args[0];
if (!options.isObjectExpression()) return;
let properties = options.get('properties');
let propertiesMap = {};
properties.forEach(property => {
if (property.type !== 'SpreadProperty') {
let key = property.get('key');
propertiesMap[key.node.name] = property;
}
});
if (propertiesMap.webpack) {
return;
}
let loaderMethod = propertiesMap.loader.get('value');
let dynamicImports = [];
loaderMethod.traverse({
Import(path) {
dynamicImports.push(path.parentPath);
}
});
if (!dynamicImports.length) return;
propertiesMap.loader.insertAfter(
t.objectProperty(
t.identifier('webpack'),
t.arrowFunctionExpression(
[],
t.arrayExpression(
dynamicImports.map(dynamicImport => {
return t.callExpression(
t.memberExpression(
t.identifier('require'),
t.identifier('resolveWeak'),
),
[dynamicImport.get('arguments')[0].node],
)
})
)
)
)
);
propertiesMap.loader.insertAfter(
t.objectProperty(
t.identifier('modules'),
t.arrayExpression(
dynamicImports.map(dynamicImport => {
return dynamicImport.get('arguments')[0].node;
})
)
)
);
});
}
}
};
}
================================================
FILE: src/index.js
================================================
"use strict";
const React = require("react");
const PropTypes = require("prop-types");
const ALL_INITIALIZERS = [];
const READY_INITIALIZERS = [];
function isWebpackReady(getModuleIds) {
if (typeof __webpack_modules__ !== "object") {
return false;
}
return getModuleIds().every(moduleId => {
return (
typeof moduleId !== "undefined" &&
typeof __webpack_modules__[moduleId] !== "undefined"
);
});
}
function load(loader) {
let promise = loader();
let state = {
loading: true,
loaded: null,
error: null
};
state.promise = promise
.then(loaded => {
state.loading = false;
state.loaded = loaded;
return loaded;
})
.catch(err => {
state.loading = false;
state.error = err;
throw err;
});
return state;
}
function loadMap(obj) {
let state = {
loading: false,
loaded: {},
error: null
};
let promises = [];
try {
Object.keys(obj).forEach(key => {
let result = load(obj[key]);
if (!result.loading) {
state.loaded[key] = result.loaded;
state.error = result.error;
} else {
state.loading = true;
}
promises.push(result.promise);
result.promise
.then(res => {
state.loaded[key] = res;
})
.catch(err => {
state.error = err;
});
});
} catch (err) {
state.error = err;
}
state.promise = Promise.all(promises)
.then(res => {
state.loading = false;
return res;
})
.catch(err => {
state.loading = false;
throw err;
});
return state;
}
function resolve(obj) {
return obj && obj.__esModule ? obj.default : obj;
}
function render(loaded, props) {
return React.createElement(resolve(loaded), props);
}
function createLoadableComponent(loadFn, options) {
if (!options.loading) {
throw new Error("react-loadable requires a `loading` component");
}
let opts = Object.assign(
{
loader: null,
loading: null,
delay: 200,
timeout: null,
render: render,
webpack: null,
modules: null
},
options
);
let res = null;
function init() {
if (!res) {
res = loadFn(opts.loader);
}
return res.promise;
}
ALL_INITIALIZERS.push(init);
if (typeof opts.webpack === "function") {
READY_INITIALIZERS.push(() => {
if (isWebpackReady(opts.webpack)) {
return init();
}
});
}
return class LoadableComponent extends React.Component {
constructor(props) {
super(props);
init();
this.state = {
error: res.error,
pastDelay: false,
timedOut: false,
loading: res.loading,
loaded: res.loaded
};
}
static contextTypes = {
loadable: PropTypes.shape({
report: PropTypes.func.isRequired
})
};
static preload() {
return init();
}
componentWillMount() {
this._loadModule();
}
componentDidMount() {
this._mounted = true;
}
_loadModule() {
if (this.context.loadable && Array.isArray(opts.modules)) {
opts.modules.forEach(moduleName => {
this.context.loadable.report(moduleName);
});
}
if (!res.loading) {
return;
}
let setStateWithMountCheck = (newState) => {
if (!this._mounted) {
return;
}
this.setState(newState);
}
if (typeof opts.delay === 'number') {
if (opts.delay === 0) {
this.setState({ pastDelay: true });
} else {
this._delay = setTimeout(() => {
setStateWithMountCheck({ pastDelay: true });
}, opts.delay);
}
}
if (typeof opts.timeout === "number") {
this._timeout = setTimeout(() => {
setStateWithMountCheck({ timedOut: true });
}, opts.timeout);
}
let update = () => {
setStateWithMountCheck({
error: res.error,
loaded: res.loaded,
loading: res.loading
});
this._clearTimeouts();
};
res.promise
.then(() => {
update();
return null;
})
.catch(err => {
update();
return null;
});
}
componentWillUnmount() {
this._mounted = false;
this._clearTimeouts();
}
_clearTimeouts() {
clearTimeout(this._delay);
clearTimeout(this._timeout);
}
retry = () => {
this.setState({ error: null, loading: true, timedOut: false });
res = loadFn(opts.loader);
this._loadModule();
};
render() {
if (this.state.loading || this.state.error) {
return React.createElement(opts.loading, {
isLoading: this.state.loading,
pastDelay: this.state.pastDelay,
timedOut: this.state.timedOut,
error: this.state.error,
retry: this.retry
});
} else if (this.state.loaded) {
return opts.render(this.state.loaded, this.props);
} else {
return null;
}
}
};
}
function Loadable(opts) {
return createLoadableComponent(load, opts);
}
function LoadableMap(opts) {
if (typeof opts.render !== "function") {
throw new Error("LoadableMap requires a `render(loaded, props)` function");
}
return createLoadableComponent(loadMap, opts);
}
Loadable.Map = LoadableMap;
class Capture extends React.Component {
static propTypes = {
report: PropTypes.func.isRequired
};
static childContextTypes = {
loadable: PropTypes.shape({
report: PropTypes.func.isRequired
}).isRequired
};
getChildContext() {
return {
loadable: {
report: this.props.report
}
};
}
render() {
return React.Children.only(this.props.children);
}
}
Loadable.Capture = Capture;
function flushInitializers(initializers) {
let promises = [];
while (initializers.length) {
let init = initializers.pop();
promises.push(init());
}
return Promise.all(promises).then(() => {
if (initializers.length) {
return flushInitializers(initializers);
}
});
}
Loadable.preloadAll = () => {
return new Promise((resolve, reject) => {
flushInitializers(ALL_INITIALIZERS).then(resolve, reject);
});
};
Loadable.preloadReady = () => {
return new Promise((resolve, reject) => {
// We always will resolve, errors should be handled within loading UIs.
flushInitializers(READY_INITIALIZERS).then(resolve, resolve);
});
};
module.exports = Loadable;
================================================
FILE: src/webpack.js
================================================
'use strict';
const url = require('url');
function buildManifest(compiler, compilation) {
let context = compiler.options.context;
let manifest = {};
compilation.chunks.forEach(chunk => {
chunk.files.forEach(file => {
chunk.forEachModule(module => {
let id = module.id;
let name = typeof module.libIdent === 'function' ? module.libIdent({ context }) : null;
let publicPath = url.resolve(compilation.outputOptions.publicPath || '', file);
let currentModule = module;
if (module.constructor.name === 'ConcatenatedModule') {
currentModule = module.rootModule;
}
if (!manifest[currentModule.rawRequest]) {
manifest[currentModule.rawRequest] = [];
}
manifest[currentModule.rawRequest].push({ id, name, file, publicPath });
});
});
});
return manifest;
}
class ReactLoadablePlugin {
constructor(opts = {}) {
this.filename = opts.filename;
}
apply(compiler) {
compiler.plugin('emit', (compilation, callback) => {
const manifest = buildManifest(compiler, compilation);
var json = JSON.stringify(manifest, null, 2);
compilation.assets[this.filename] = {
source() {
return json;
},
size() {
return json.length
}
}
callback();
});
}
}
function getBundles(manifest, moduleIds) {
return moduleIds.reduce((bundles, moduleId) => {
return bundles.concat(manifest[moduleId]);
}, []);
}
exports.ReactLoadablePlugin = ReactLoadablePlugin;
exports.getBundles = getBundles;
================================================
FILE: webpack.config.js
================================================
const path = require('path');
const { ReactLoadablePlugin } = require('./webpack');
module.exports = {
entry: {
main: './example/client',
},
output: {
path: path.join(__dirname, 'example', 'dist'),
filename: '[name].js',
chunkFilename: '[name].js',
publicPath: '/dist/'
},
module: {
rules: [
{
test: /\.js$/,
exclude: /node_modules/,
use: {
loader: 'babel-loader',
options: {
babelrc: false,
presets: [
['@babel/preset-env', { modules: false }],
'@babel/preset-react',
],
plugins: [
'syntax-dynamic-import',
'@babel/plugin-proposal-class-properties',
'@babel/plugin-transform-object-assign',
require.resolve('./babel'),
],
}
},
},
],
},
devtool: 'inline-source-map',
resolve: {
alias: {
'react-loadable': path.resolve(__dirname, 'src'),
},
},
plugins: [
new ReactLoadablePlugin({
filename: path.resolve(__dirname, 'example', 'dist', 'react-loadable.json'),
}),
]
};
================================================
FILE: webpack.js
================================================
module.exports = require('./lib/webpack');
gitextract_jyw8n8b2/ ├── .babelrc ├── .flowconfig ├── .gitignore ├── .travis.yml ├── LICENSE ├── README.md ├── __fixtures__/ │ ├── component.es6.js │ └── component.js ├── __tests__/ │ ├── .babelrc │ ├── __snapshots__/ │ │ └── test.js.snap │ └── test.js ├── babel.js ├── example/ │ ├── .babelrc │ ├── client.js │ ├── components/ │ │ ├── App.js │ │ ├── Example.js │ │ ├── ExampleNested.js │ │ └── Loading.js │ ├── server.js │ └── utils/ │ └── delay.js ├── package.json ├── src/ │ ├── babel.js │ ├── index.js │ └── webpack.js ├── webpack.config.js └── webpack.js
SYMBOL INDEX (34 symbols across 10 files)
FILE: __fixtures__/component.es6.js
class FixtureComponent (line 3) | class FixtureComponent extends React.Component {
method render (line 4) | render() {
FILE: __tests__/test.js
function waitFor (line 8) | function waitFor(delay) {
function createLoader (line 14) | function createLoader(delay, loader, error) {
function MyLoadingComponent (line 26) | function MyLoadingComponent(props) {
function MyComponent (line 30) | function MyComponent(props) {
method render (line 142) | render(loaded, props) {
method render (line 161) | render(loaded, props) {
method render (line 186) | render(loaded, props) {
FILE: example/components/App.js
function App (line 12) | function App() {
FILE: example/components/Example.js
function Example (line 12) | function Example() {
FILE: example/components/ExampleNested.js
function ExampleNested (line 3) | function ExampleNested() {
FILE: example/components/Loading.js
function Loading (line 3) | function Loading(props) {
FILE: example/utils/delay.js
function delay (line 1) | function delay(ms) {
FILE: src/babel.js
method ImportDeclaration (line 4) | ImportDeclaration(path) {
FILE: src/index.js
constant ALL_INITIALIZERS (line 5) | const ALL_INITIALIZERS = [];
constant READY_INITIALIZERS (line 6) | const READY_INITIALIZERS = [];
function isWebpackReady (line 8) | function isWebpackReady(getModuleIds) {
function load (line 21) | function load(loader) {
function loadMap (line 45) | function loadMap(obj) {
function resolve (line 92) | function resolve(obj) {
function render (line 96) | function render(loaded, props) {
function createLoadableComponent (line 100) | function createLoadableComponent(loadFn, options) {
function Loadable (line 259) | function Loadable(opts) {
function LoadableMap (line 263) | function LoadableMap(opts) {
class Capture (line 273) | class Capture extends React.Component {
method getChildContext (line 284) | getChildContext() {
method render (line 292) | render() {
function flushInitializers (line 299) | function flushInitializers(initializers) {
FILE: src/webpack.js
function buildManifest (line 4) | function buildManifest(compiler, compilation) {
class ReactLoadablePlugin (line 31) | class ReactLoadablePlugin {
method constructor (line 32) | constructor(opts = {}) {
method apply (line 36) | apply(compiler) {
function getBundles (line 53) | function getBundles(manifest, moduleIds) {
Condensed preview — 26 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (69K chars).
[
{
"path": ".babelrc",
"chars": 238,
"preview": "{\n \"presets\": [\n [\n \"@babel/preset-env\",\n {\n \"loose\": true\n }\n ],\n \"@babel/preset-react\""
},
{
"path": ".flowconfig",
"chars": 76,
"preview": "[ignore]\n<PROJECT_ROOT>/node_modules/chalk/.*\n\n[include]\n\n[libs]\n\n[options]\n"
},
{
"path": ".gitignore",
"chars": 51,
"preview": ".idea\nnode_modules\n*.log\nlib\ncoverage\nexample/dist\n"
},
{
"path": ".travis.yml",
"chars": 121,
"preview": "language: node_js\nnode_js:\n - '6'\n - '8'\n - '10'\ncache: yarn\nscript: yarn test -- --runInBand --coverage && yarn flow"
},
{
"path": "LICENSE",
"chars": 1084,
"preview": "Copyright (c) 2018-present Jamie Kyle <me@thejameskyle.com>\n\nPermission is hereby granted, free of charge, to any person"
},
{
"path": "README.md",
"chars": 33530,
"preview": "\n\n> A higher order component for loading compone"
},
{
"path": "__fixtures__/component.es6.js",
"chars": 143,
"preview": "import React from \"react\";\n\nexport default class FixtureComponent extends React.Component {\n render() {\n return <div"
},
{
"path": "__fixtures__/component.js",
"chars": 106,
"preview": "const React = require(\"react\");\n\nmodule.exports = function Component() {\n return <div>fixture1</div>;\n};\n"
},
{
"path": "__tests__/.babelrc",
"chars": 196,
"preview": "{\n \"presets\": [\n [\n \"@babel/preset-env\",\n {\n \"loose\": true\n }\n ],\n \"@babel/preset-react\""
},
{
"path": "__tests__/__snapshots__/test.js.snap",
"chars": 3659,
"preview": "// Jest Snapshot v1, https://goo.gl/fbAQLP\n\nexports[`delay and timeout 1`] = `\n<div>\n MyLoadingComponent \n {\"isLoading"
},
{
"path": "__tests__/test.js",
"chars": 7719,
"preview": "'use strict';\n\nconst path = require('path');\nconst React = require('react');\nconst renderer = require('react-test-render"
},
{
"path": "babel.js",
"chars": 41,
"preview": "module.exports = require('./lib/babel');\n"
},
{
"path": "example/.babelrc",
"chars": 367,
"preview": "{\n \"presets\": [\n \"@babel/preset-env\",\n \"@babel/preset-react\"\n ],\n \"plugins\": [\n \"dynamic-import-node\",\n \""
},
{
"path": "example/client.js",
"chars": 269,
"preview": "import React from 'react';\nimport ReactDOM from 'react-dom';\nimport Loadable from 'react-loadable';\nimport App from './c"
},
{
"path": "example/components/App.js",
"chars": 321,
"preview": "import React from 'react';\nimport Loadable from 'react-loadable';\nimport Loading from './Loading';\nimport delay from '.."
},
{
"path": "example/components/Example.js",
"chars": 409,
"preview": "import React from 'react';\nimport Loadable from 'react-loadable';\nimport Loading from './Loading';\nimport delay from '.."
},
{
"path": "example/components/ExampleNested.js",
"chars": 129,
"preview": "import React from 'react';\n\nexport default function ExampleNested() {\n return <p>Hello from a nested loadable component"
},
{
"path": "example/components/Loading.js",
"chars": 388,
"preview": "import React from 'react';\n\nexport default function Loading(props) {\n if (props.isLoading) {\n if (props.timedOut) {\n"
},
{
"path": "example/server.js",
"chars": 1662,
"preview": "import express from 'express';\nimport path from 'path';\nimport React from 'react';\nimport ReactDOMServer from 'react-dom"
},
{
"path": "example/utils/delay.js",
"chars": 96,
"preview": "export default function delay(ms) {\n return new Promise(resolve => setTimeout(resolve, ms));\n}\n"
},
{
"path": "package.json",
"chars": 1363,
"preview": "{\n \"name\": \"react-loadable\",\n \"version\": \"5.5.0\",\n \"description\": \"A higher order component for loading components wi"
},
{
"path": "src/babel.js",
"chars": 2833,
"preview": "export default function({ types: t, template }) {\n return {\n visitor: {\n ImportDeclaration(path) {\n let "
},
{
"path": "src/index.js",
"chars": 6591,
"preview": "\"use strict\";\nconst React = require(\"react\");\nconst PropTypes = require(\"prop-types\");\n\nconst ALL_INITIALIZERS = [];\ncon"
},
{
"path": "src/webpack.js",
"chars": 1607,
"preview": "'use strict';\nconst url = require('url');\n\nfunction buildManifest(compiler, compilation) {\n let context = compiler.opti"
},
{
"path": "webpack.config.js",
"chars": 1164,
"preview": "const path = require('path');\nconst { ReactLoadablePlugin } = require('./webpack');\n\nmodule.exports = {\n entry: {\n m"
},
{
"path": "webpack.js",
"chars": 43,
"preview": "module.exports = require('./lib/webpack');\n"
}
]
About this extraction
This page contains the full source code of the jamiebuilds/react-loadable GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 26 files (62.7 KB), approximately 16.6k tokens, and a symbol index with 34 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.
Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.