
Security News
Attackers Are Hunting High-Impact Node.js Maintainers in a Coordinated Social Engineering Campaign
Multiple high-impact npm maintainers confirm they have been targeted in the same social engineering campaign that compromised Axios.
react-chunk
Advanced tools
A higher order component for dynamically importing components, forked from react-loadable.
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:
This enables both component and route code splitting
npm install --save react-chunk
yarn add react-chunk
For more detailed examples, take a look at the examples
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 <MyComponentChunk />;
}
}
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 (
<div>
<MyComponent {...restProps} />
<MyOtherComponent {...restProps} />
</div>
);
}
return <div>Loading...</div>;
}
const MyComponentsChunk = chunks({
MyComponent: () => import('./my-component'),
MyOtherComponent: () => import('./my-other-component'),
})(MutilImportRenderer);
export default class App extends React.Component {
render() {
return <MyComponentsChunk />;
}
}
It's recommended you configure your development environment with the following plugins.
Configure your client build.
Add these plugins to your babel configuration.
npm install --save-dev babel-plugin-syntax-dynamic-import
The order of plugins is important.
.babelrc
{
"presets": {...},
"plugins": [
"react-chunk/babel",
"syntax-dynamic-import"
]
}
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
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
})
]
If your application performs SSR, configure your server build.
Add these plugins to your babel configuration.
npm install --save-dev babel-plugin-dynamic-import-node
The order of plugins is important.
.babelrc
{
"presets": {...},
"plugins": [
"react-chunk/babel",
"dynamic-import-node"
]
}
import()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.
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.
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);
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.
function ChunkRenderer(props) {
const {
chunk: {
isLoading,
hasLoaded,
pastDelay,
timedOut,
error,
retry,
loaded,
Imported
},
...restProps
} = prop;
if (hasLoaded) {
return <Imported {...restProps } />;
}
if (error) {
return <div>An error occured</div>;
}
if (timedOut) {
return (
<div>
This is taking a while..
<a onClick={() => retry()}>retry?</a>
</div>
);
}
if (isLoading && pastDelay) {
return <div>Loading...</div>;
}
return null;
}
chunk(() => import('./someComponent'))(ChunkRenderer);
To make this all nice, your chunk component receives a couple different props.
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 rendering component will also get a 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.
chunk(() => import('./components/Bar'), {
delay: 300, // 0.3 seconds
});
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.
The rendering component will receive a
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.
chunk(() => import('./components/Bar'), {
timeout: 10000, // 10 seconds
});
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.
// 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.
chunks requires a rendering component be provided when invoking the HOC, an error will be thrown if this requirement is not met.
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.
chunks for multiple importsconst MultiComponentChunk = chunks({
Bar: () => import('./Bar'),
i18n: () => fetch('./i18n/bar.json').then(res => res.json())
}, {
delay: 300,
// other options here...
})(RequiredRendererComponent);
As an optimization, you can also decide to preload one or more components before being 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 components created by chunk and chunks expose a
static preload method which does exactly this.
const BarChunk = chunk(() => import('./Bar'))();
class MyComponent extends React.Component {
state = { showBar: false };
onClick = () => {
this.setState({ showBar: true });
};
onMouseOver = () => {
BarChunk.preloadChunk();
};
render() {
return (
<div>
<button
onClick={this.onClick}
onMouseOver={this.onMouseOver}>
Show Bar
</button>
{this.state.showBar && <BarChunk />}
</div>
)
}
}
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.
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
})
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.
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.
To do this, you can use the preloadAll
method. It returns a promise that will resolve when all your chunk
components are ready.
import { preloadAll } from 'react-chunk';
preloadAll().then(() => {
app.listen(3000, () => {
console.log('Running on http://localhost:3000/');
});
});
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.
Next we need to find out which chunks were used to perform the server render.
For this, there is the Recorder component which can
be used to record all the chunks used for rendering.
import ChunkRecorder from 'react-chunk/Recorder';
app.get('/', (req, res) => {
let renderedChunks = [];
let html = ReactDOMServer.renderToString(
<ChunkRecorder addChunk={chunkName => renderedChunks.push(chunkName)}>
<App/>
</ChunkRecorder>
);
console.log(renderedChunks);
res.send(`...${html}...`);
});
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.
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(
<ChunkRecorder addChunk={chunkName => renderedChunks.push(chunkName)}>
<App/>
</ChunkRecorder>
);
let resources = resolveChunks(chunkData, renderedChunks);
// ...
});
We can then render these resources using <script> and <link> tags in our HTML.
It is important that the script files are included before the main entry point, so that they can be loaded by the browser prior to the app rendering.
However, as the Webpack manifest (including the logic for parsing chunks) lives in the main chunk, it will need to be extracted into its own chunk.
This is easy to do with the CommonsChunkPlugin, add it to your webpack plugins configuration.
// webpack.config.js
export default {
plugins: [
//...other webpack plugins
new webpack.optimize.CommonsChunkPlugin({
name: 'manifest',
minChunks: Infinity
})
]
}
manifest.js is loaded before all other webpack scripts.main.js) is loaded after all other webpack scripts.let resources = getBundles(chunkData, renderedChunks);
let styles = resources.filter(bundle => bundle.file.endsWith('.css'));
let scripts = resources.filter(bundle => bundle.file.endsWith('.js'));
res.send(`
<!doctype html>
<html lang="en">
<head>
${styles.map(bundle => {
return `<link rel="stylesheet" href ="/dist/${bundle.file}"></script>`
}).join('\n')}
</head>
<body>
<div id="app">${html}</div>
<!-- Load the manifest FIRST -->
<script src="/dist/manifest.js"></script>
<!-- Then, load all resolved scripts -->
${scripts.map(bundle => {
return `<script src="/dist/${bundle.file}"></script>`
}).join('\n')}
<!-- Load the main entry point LAST -->
<script src="/dist/main.js"></script>
</body>
</html>
`);
We can use the preloadReady() method on the
client to preload the chunk components that were included on the page.
Like preloadAll(), it returns a promise,
which on resolution means that we can hydrate our app.
// src/entry.js
import React from 'react';
import ReactDOM from 'react-dom';
import { preloadReady } from 'react-chunk';
import App from './components/App';
preloadReady().then(() => {
ReactDOM.hydrate(<App/>, document.getElementById('app'));
}).catch(err => {
// errors can occur if imports timeout or fail
// render an error page
});
chunkA higher-order component for dynamically importing a single resource.
chunk(import: function[, options: Object]): ChunkComponent
import { chunk } from 'react-chunk';
const ChunkComponent = chunk(() => import('./Bar'), {
delay: 200,
timeout: 10000,
})([WrappedComponent]);
This returns a ChunkComponent. The WrappedComponent for a chunk is optional, but recommended for complete control of the rendering. The WrappedComponent will be passed an additional single prop chunk, that provides all state required to render the imported resource.
chunksA higher-order component that allows you to load multiple resources in parallel.
chunks(importMap: {[string]: function}[, options: Object]): ChunksComponent
import { chunks } from 'react-chunk';
const ChunksComponent = chunks({
Foo: () => import('./Foo'),
Bar: () => import('./Bar')
}, {
// define options here...
delay: 200,
timeout: 10000,
})(WrappedComponent);
This returns a ChunksComponent. The WrappedComponent for a chunks is required to control rendering of all imported resources. The WrappedComponent will be passed an additional single prop chunk, that provides all state required to render the imported resource.
chunk and chunks Optionsopts.displayName: stringThe react display name to assign when creating the HOC.
opts.hoistStatics: booleantrue to hoist non-react static methods of the imported component to the HOC. Defaults to false.
Note that the static methods are only hoisted after the component is loaded (obviously) - if you're using hoistStatics: true on a component its recommended that you preload (or preloadChunks) the component to avoid invoking static methods that have not yet been assigned to the HOC.
Using this option with chunks is not supported and will result in an error.
opts.resolveDefaultImport: (imported, importKey) => mixedBy default, the .default export of the imported resource is returned to the Imported property (for chunk) or the imported property (for chunks).
The importKey is only passed for chunks.
opts.retryBackOff: Array<number>Allows automatic retry for failed imports using the assigned backOff.
When used in conjuntion with timeout, retry attempts will be invoked after the configured timeout value has expired.
For example: [250, 500] will result in the first retry attempt starting 250ms after the first timeout or error. The second retry will start 500ms after the second timeout or error.
opts.delay: numberTime to wait (in milliseconds) before passing
props.pastDelay to your loading
component. This defaults to 200.
opts.timeout: numberTime to wait (in milliseconds) before passing
props.timedOut to your loading component.
This is turned off by default.
opts.webpack: functionAn optional function which returns an array of Webpack module ids which you can
get with require.resolveWeak.
chunk(() => import('./component'), {
webpack: () => [require.resolveWeak('./Foo')],
});
This option can be automated with the Babel Plugin.
opts.modules: Array<string>An optional array with module paths for your imports.
chunk(() => import('./component'), {
modules: ['./my-component']
});
This option can be automated with the Babel Plugin.
ChunkComponentThis is the component returned by chunk.
const ChunkComponent = chunk({
// ...
});
Props passed to this component will be passed straight through to the
wrapped component, in additional to a chunk prop that includes all data required for rendering the imported resource.
ChunksComponentThis is the component returned by chunks.
const ChunksComponent = chunks({
// ...
});
Props passed to this component will be passed straight through to the
wrapped component, in additional to a chunk prop that includes all data required for rendering the imported resources.
chunk and chunks static methodspreloadChunk()This is a static method that can be used to load the component ahead of time.
const ChunkComponent = chunk({...});
ChunkComponent.preloadChunk();
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.
getChunkLoader()This is a static method that can be used to obtain a reference to the components loader. It should be used in conjuntion with preloadChunks()
const ChunkComponent = chunk({...});
ChunkComponent.getChunkLoader();
onImported(subscriber: (ImportedComponent) => void): () => voidThis is a static method that can be used to subscribe for notifications when component has been imported.
Returns an unsubscribe function.
const ChunkComponent = chunk({...});
ChunkComponent.onImported((ImportedComponent) => { /* use ImportedComponent */ });
onImportedWithHoist(subscriber: (ImportedComponent) => void): () => voidThis is a static method that can be used to subscribe for notifications when component has been imported where hoistStatics: true.
Note: this requires hoistStatics: true
Returns an unsubscribe function.
const ChunkComponent = chunk({...});
ChunkComponent.onImportedWithHoist((ImportedComponent) => { /* use ImportedComponent */ });
WrappedComponentThis is the component you pass to the chunk() or chunks() HOC.
function WrappedComponent(props) {
const {
chunk: {
isLoading,
hasLoaded,
pastDelay,
timedOut,
error,
retry,
loaded,
importKeys,
Imported // - only for 'chunk()'
// imported - only for 'chunks()'
},
...restProps
} = prop;
if (hasLoaded) {
return <Imported {...restProps } />;
}
if (error) {
return <div>An error occured</div>;
}
if (timedOut) {
return (
<div>
This is taking a while..
<a onClick={() => retry()}>retry?</a>
</div>
);
}
if (isLoading && pastDelay) {
return <div>Loading...</div>;
}
return null;
}
Read more about loading components
chunk.Imported: mixedNote the UPPER CASE 'i'
This prop is only passed to
chunkcomponents.
It provides access to the default export of the imported resource.
It is only populated when chunk.hasLoaded is true.
chunk.imported: ObjectNote the LOWER CASE 'i'
This prop is only passed to
chunkscomponents.
It provides access to the default export of the all imported resource, by key.
It is only populated when chunk.hasLoaded is true.
chunk.importKeys: Array<string>For chunks(), an array of the key names used for imports.
For chunk(), it will always be an empty array
This can be used to create a generic rendering component that can be used to render both
chunk()andchunks()components.
chunk.isLoading: booleantrue if the import(s) are currently being loaded, otherwise false.
chunk.hasLoaded: booleantrue if the import(s) have been successfully loaded, otherwise false.
chunk.error: booleanA boolean prop passed to WrappedComponent when the loading resource(s) has failed.
function WrappedComponent({chunk}) {
if (chunk.error) {
return <div>Error!</div>;
} else {
return <div>Loading...</div>;
}
}
chunk.timedOut: booleanA boolean prop passed to WrappedComponent after a set
timeout.
function WrappedComponent({chunk}) {
if (chunk.timedOut) {
return <div>Taking a long time...</div>;
} else {
return <div>Loading...</div>;
}
}
chunk.pastDelay: booleanA boolean prop passed to WrappedComponent after a set
delay.
function WrappedComponent({chunk}) {
if (chunk.pastDelay) {
return <div>Loading...</div>;
} else {
return null;
}
}
chunk.loaded: mixedThis is considered a "low-level" API property, the loaded prop provides raw access to all imported resources. This can be used in scenarios where an imported resource includes multiple exports that you need to access.
preloadAll()This will call all of the
WrappedComponent.preload methods recursively
until they are all resolved. Allowing you to preload all of your dynamic
modules in environments like the server.
import { preloadAll } from 'react-chunk';
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 chunk components when modules are initialized rather than when your app is being rendered.
Good:
// During module initialization...
const ChunkComponent = chunk(...);
class MyComponent extends React.Component {
componentDidMount() {
// ...
}
}
Bad:
// ...
class MyComponent extends React.Component {
componentDidMount() {
// During app render...
const ChunkComponent = chunk(...);
}
}
Note:
preloadAll()will not work if you have more than one copy ofreact-chunkin your app.
Read more about preloading on the server.
preloadReady()Check for modules that are already loaded in the browser and call the matching
WrappedComponent.preload methods.
import { preloadReady } from 'react-chunk';
preloadReady().then(() => {
ReactDOM.hydrate(<App/>, document.getElementById('app'));
});
Read more about preloading on the client.
RecorderA component for reporting which chunks were used for rendering.
Accepts an addChunk prop which is called for every chunkName that is
rendered via React Chunk.
import ChunkRecorder from 'react-chunk/Recorder';
let renderedChunks = [];
let html = ReactDOMServer.renderToString(
<ChunkRecorder addChunk={chunkName => renderedChunks.push(chunkName)}>
<App/>
</ChunkRecorder>
);
console.log(renderedChunks);
Read more about capturing rendered modules.
Providing opts.webpack and opts.modules for
every chunk 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:
{
"plugins": ["react-chunk/babel"]
}
Input
import { chunk, chunks } from 'react-chunk';
const ChunkMyComponent = chunk(() => import('./MyComponent'));
const ChunkComponents = chunks({
One: () => import('./One'),
Two: () => import('./Two'),
});
Output
import { chunk, chunks } from 'react-chunk';
const ChunkMyComponent = chunk(
() => import('./MyComponent'),
{},
{
webpack: () => [require.resolveWeak('./MyComponent')],
modules: ['./MyComponent']
}
});
const ChunkComponents = chunks({
One: () => import('./One'),
Two: () => import('./Two'),
},
{},
{
webpack: () => [require.resolveWeak('./One'), require.resolveWeak('./Two')],
modules: ['./One', './Two']
}
});
Read more about declaring modules.
In order to send the right bundles down when rendering server-side, you'll need the React Chunk Webpack plugin to provide you with a mapping of modules to bundles.
// webpack.config.js
import { ReactChunkPlugin } from 'react-chunk/webpack';
export default {
plugins: [
new ReactChunkPlugin({
filename: './dist/react-chunk.json',
}),
],
};
This will create a file (opts.filename) which you can import to map modules
to bundles.
opts.filenameRequired, the destination file for writing react-chunk module data
opts.ignoreChunkNamesOptional, an array of webpack chunk names to exclude from the module data
By ignoring the main entry point (ie: main or index) only required module data is included in the output.
Read more about mapping modules to bundles.
resolveChunksA method exported by react-chunk/webpack for converting chunks to
resources.
import { resolveChunks } from 'react-chunk/webpack';
let resources = resolveChunks(chunkData, renderedChunks);
Read more about mapping modules to bundles.
Specifying the same loading component or delay every time you use
chunk() or chunks() gets repetitive fast. Instead you can wrap chunk and chunks with your
own Higher-Order Component (HOC) to set default options.
// chunkOptions.js
const defaultChunkOpts = {
delay: 200,
timeout: 10,
};
export default defaultChunkOpts;
import { chunk chunks } from 'react-chunk';
import Loading from './my-loading-component';
import defaultChunkOpts form './chunkOptions';
export default function MyComponentChunk(opts = {}) {
return chunk(
() => import('./my-component'),
Object.assign({}, defaultChunkOpts, opts)
);
};
Then you can specify additional options and a WrappedComponent when you go to use it.
import MyComponentChunk from './MyComponentChunk';
import ChunkRenderer from './ChunkRenderer';
const MyAutoRetryComponentChunk = MyComponentChunk({
retryBackOff: [200, 300]
})(ChunkRenderer);
export default class App extends React.Component {
render() {
return <MyAutoRetryComponentChunk />;
}
}
.css or sourcemaps .map with server-side rendering?When you call resolveChunks, 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:
let resources = resolveChunks(stats, modules);
let styles = resources.filter(bundle => bundle.file.endsWith('.css'));
let scripts = resources.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/manifest.js"></script>
${scripts.map(script => {
return `<script src="/dist/${script.file}"></script>`
}).join('\n')}
<script src="/dist/main.js"></script>
</body>
</html>
`);
FAQs
A higher order component for dynamically importing components, forked from react-loadable.
We found that react-chunk demonstrated a not healthy version release cadence and project activity because the last version was released a year ago. It has 1 open source maintainer collaborating on the project.
Did you know?

Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.

Security News
Multiple high-impact npm maintainers confirm they have been targeted in the same social engineering campaign that compromised Axios.

Security News
Axios compromise traced to social engineering, showing how attacks on maintainers can bypass controls and expose the broader software supply chain.

Security News
Node.js has paused its bug bounty program after funding ended, removing payouts for vulnerability reports but keeping its security process unchanged.