A higher order component for dynamically importing components, forked from react-loadable.
npm install react-chunk_Code splitting with minimal boiler plate_
> A higher order component for loading components with dynamic imports.






_This is a fork of react-loadable, differences and new features include:_
* _A modified API to support new features_
* _Full render control using a HOC wrapped component_
* _Improved re-use of import components_
* _Improved support for route code splitting_
* _Pre-loading all chunks required to render an entire route_
* _Option to _hoist_ static methods of imported components_
* _Option to enable retry support with backoff_
* _Manually invoking a retry after timeout or error_
* _Support for react-router-config code splitting_
> This enables both _component_ and _route_ code splitting
``sh`
npm install --save react-chunk
`sh`
yarn add react-chunk
For more detailed examples, take a look at the examples
`js
import { chunk } from 'react-chunk';
// It can be this easy!
const MyComponentChunk = chunk(() => import('./my-component'))();
export default class App extends React.Component {
render() {
return
}
}
`
js
import { chunks } from 'react-chunk';// A component for rendering mutilple imports
function MutilImportRenderer(props) {
const {
chunk: {
isLoaded,
imported: {
MyComponent,
MyOtherComponent
}
},
...restProps
}) = props;
if (isLoaded) {
return (
);
} return
Loading...;
}const MyComponentsChunk = chunks({
MyComponent: () => import('./my-component'),
MyOtherComponent: () => import('./my-other-component'),
})(MutilImportRenderer);
export default class App extends React.Component {
render() {
return ;
}
}
`Environment Configuration
It's _recommended_ you configure your development environment with the following plugins.
$3
Configure your client build.
#### Babel
Add these plugins to your babel configuration.
`sh
npm install --save-dev babel-plugin-syntax-dynamic-import
`The order of plugins is important.
.babelrc
`json
{
"presets": {...},
"plugins": [
"react-chunk/babel",
"syntax-dynamic-import"
]
}`#### Webpack
The react-chunk webpack plugin will write the chunk module data to a file required for server-side rendering.
The webpack
CommonsChunkPlugin is required to allow non entry point chunks to be pre-loaded on the client.Add the plugins to your _client_ webpack plugins
`js
import webpack from 'webpack';
import { ReactChunkPlugin } from 'react-chunk/webpack';plugins: [
new ReactChunkPlugin({
filename: path.join(__dirname, 'dist', 'react-chunk.json')
}),
new webpack.optimize.CommonsChunkPlugin({
name: 'manifest',
minChunks: Infinity
})
]
`$3
If your application performs SSR, configure your server build.
#### Babel
Add these plugins to your babel configuration.
`sh
npm install --save-dev babel-plugin-dynamic-import-node
`The order of plugins is important.
.babelrc
`json
{
"presets": {...},
"plugins": [
"react-chunk/babel",
"dynamic-import-node"
]
}`Introduction
$3
When you use
import() with Webpack 2+, it will
automatically code-split 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 Chunk. Figure out what performs
best for your app.
$3
Its often useful to assign _names_ to webpack chunks. This can be achieved easily using inline code comments.
Be aware that naming chunks impacts how webpack bundles your code. You should read about webpack code splitting.
`js
import { chunk, chunks } from 'react-chunk';const AppChunk =
chunk(() => import(/ webpackChunkName: "App" / './app'))();
const TimeChunk =
chunks({
Calendar: () => import(/ webpackChunkName: "calendar" / './calendar'),
Clock: () => import(/ webpackChunkName: "clock" / './clock'),
})(TimeRenderer);
`$3
Rendering a static "Loading..." doesn't communicate enough to the user. You
also need to think about error states, timeouts, retries, and making it a nice user experience.
As a developer, you can easiliy re-use import rendering logic when importing a single component. Renderering components for multiple components don't require much more effort.
`js
function ChunkRenderer(props) {
const {
chunk: {
isLoading,
hasLoaded,
pastDelay,
timedOut,
error,
retry,
loaded,
Imported
},
...restProps
} = prop; if (hasLoaded) {
return ;
}
if (error) {
return
An error occured;
} if (timedOut) {
return (
);
} if (isLoading && pastDelay) {
return
Loading...;
} return null;
}
chunk(() => import('./someComponent'))(ChunkRenderer);
`To make this all nice, your chunk component receives a
couple different props.
#### 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.
pastDelay prop
which will only be true once the component has taken longer to load than a set
delay.This delay defaults to
200ms but you can also customize the
delay in chunk and chunks.`js
chunk(() => import('./components/Bar'), {
delay: 300, // 0.3 seconds
});
`#### Timing out when the
loader is taking too longSometimes 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.
timedOut prop which will be set to true when the
loader has timed out.However, this feature is disabled by default. To turn it on, you can pass a
timeout option to chunk and chunks.`js
chunk(() => import('./components/Bar'), {
timeout: 10000, // 10 seconds
});
`$3
By default
chunk and chunks will render the default export of each returned import.
If you want to customize this behavior you can use the
resolveDefaultImport option.#### Chunk rendering without a rendering component
`js// Notice the HOC is invoked with no component
const MyComponentChunk = chunk(() => import('./myComponent'))();
`When no rendering component is provided,
null is rendered until the component hasLoaded.
#### Rendering multiple chunks
chunks requires a rendering component be provided when invoking the HOC, an error will be thrown if this requirement is not met.
$3
To make it easier to load multiple resources in parallel, you can use
chunks.When using
chunks a rendering component must be provided when invoking the HOC.#### Using
chunks for multiple imports`js
const MultiComponentChunk = chunks({
Bar: () => import('./Bar'),
i18n: () => fetch('./i18n/bar.json').then(res => res.json())
}, {
delay: 300,
// other options here...
})(RequiredRendererComponent);
`
$3
As an optimization, you can also decide to preload one or more components before being
rendered.
#### Preload a single chunk
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 components created by
chunk and chunks expose a
static preload method which does exactly this.`js
const BarChunk = chunk(() => import('./Bar'))();class MyComponent extends React.Component {
state = { showBar: false };
onClick = () => {
this.setState({ showBar: true });
};
onMouseOver = () => {
BarChunk.preloadChunk();
};
render() {
return (
onClick={this.onClick}
onMouseOver={this.onMouseOver}>
Show Bar
{this.state.showBar && }
)
}
}
`#### Preload multiple chunks
This approach can be used to load all the chunks required for rendering a route on the client, and ensure that all chunks are loaded before rendering the route.
This makes it easier to handle errors, instead of having to render an error for each failed component on the page (which may result in the user seeing many error messages) you can simply render an error page for the user - and allow the user to retry the previous action if desired.
`js
import { preloadChunks } from 'react-chunk';const FooChunk = chunk(() => import('./Foo'))();
const BarChunk = chunk(() => import('./Bar'))();
preloadChunks([
FooChunk.getChunkLoader(),
BarChunk.getChunkLoader(),
]).then(() => {
// use 'setState()' to render using the loaded components
}).catch(err => {
// handle timeouts, or other errors
})
`Server-Side Rendering
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 Chunk is designed to
make server-side rendering work as if nothing is being imported dynamically.
$3
The first step to rendering the correct content from the server is to make sure
that all of your chunk components are already loaded when you go to render
them.
preloadAll
method. It returns a promise that will resolve when all your chunk
components are ready.`js
import { preloadAll } from 'react-chunk';preloadAll().then(() => {
app.listen(3000, () => {
console.log('Running on http://localhost:3000/');
});
});
`#### Configure babel and webpack
Ensure you have configured babel and webpack for both _client_ and _server_ builds.
The babel plugin adds additional information to all of your
chunk and chunks.#### Tracking which dynamic modules were rendered
Next we need to find out which chunks were used to perform the server render.
Recorder component which can
be used to record all the chunks used for rendering.`js
import ChunkRecorder from 'react-chunk/Recorder';app.get('/', (req, res) => {
let renderedChunks = [];
let html = ReactDOMServer.renderToString(
renderedChunks.push(chunkName)}>
);
console.log(renderedChunks);
res.send(
...${html}...);
});
`#### Resolving rendered chunks
In order to make sure that the client loads all the resources required by the
server-side render, we need to resolve the chunks that Webpack created.
First we need to configure Webpack to write the chunk data to a file. Use the React Chunk Webpack plugin.
Then we can use the plugin output to determine the chunks required for the client render. To determine the files required for each chunk, import the
resolveChunks
method from react-chunk/webpack and the data from Webpack.`js
import ChunkRecorder from 'react-chunk/Recorder';
import { resolveChunks } from 'react-chunk/webpack'
import chunkData from './dist/react-chunk.json';app.get('/', (req, res) => {
let renderedChunks = [];
let html = ReactDOMServer.renderToString(
renderedChunks.push(chunkName)}>
);
let resources = resolveChunks(chunkData, renderedChunks);
// ...
});
`We can then render these resources using
${scripts.map(bundle => {
return
}).join('\n')}