A higher order component for loading components with promises
—
React Loadable provides build tool plugins to automate server-side rendering setup and optimize the development experience. This includes a Babel plugin for automatic module resolution and a Webpack plugin for generating bundle manifests.
Automatically adds webpack and modules options to Loadable components, eliminating the need for manual configuration in SSR setups.
/**
* Babel plugin that transforms Loadable calls to include webpack and modules options
* Plugin name: "react-loadable/babel"
*/Configuration:
{
"plugins": ["react-loadable/babel"]
}Transform Example:
// Input
import Loadable from 'react-loadable';
const LoadableComponent = Loadable({
loader: () => import('./MyComponent'),
loading: Loading,
});
const LoadableMap = Loadable.Map({
loader: {
One: () => import('./One'),
Two: () => import('./Two'),
},
loading: Loading,
render: (loaded, props) => <div>{/* ... */}</div>,
});// Output (automatically generated)
import Loadable from 'react-loadable';
import path from 'path';
const LoadableComponent = Loadable({
loader: () => import('./MyComponent'),
loading: Loading,
webpack: () => [require.resolveWeak('./MyComponent')],
modules: [path.join(__dirname, './MyComponent')],
});
const LoadableMap = Loadable.Map({
loader: {
One: () => import('./One'),
Two: () => import('./Two'),
},
loading: Loading,
render: (loaded, props) => <div>{/* ... */}</div>,
webpack: () => [require.resolveWeak('./One'), require.resolveWeak('./Two')],
modules: [path.join(__dirname, './One'), path.join(__dirname, './Two')],
});Generates a manifest file mapping modules to webpack bundles, enabling server-side rendering to determine which bundles to include for rendered components.
/**
* Webpack plugin for generating loadable component manifest
*/
class ReactLoadablePlugin {
/**
* @param options - Plugin configuration
* @param options.filename - Path where manifest JSON should be written
*/
constructor(options: { filename: string });
}Configuration:
// webpack.config.js
const { ReactLoadablePlugin } = require('react-loadable/webpack');
module.exports = {
plugins: [
new ReactLoadablePlugin({
filename: './dist/react-loadable.json',
}),
],
};Generated Manifest Structure:
{
"./src/components/Header": [
{
"id": 0,
"name": "./src/components/Header",
"file": "0.js",
"publicPath": "/dist/0.js"
}
],
"./src/components/Footer": [
{
"id": 1,
"name": "./src/components/Footer",
"file": "1.js",
"publicPath": "/dist/1.js"
}
]
}Convert module names to bundle information for including the correct script tags in server-rendered HTML.
/**
* Converts module IDs to bundle information using webpack manifest
* @param stats - Webpack stats object or manifest from ReactLoadablePlugin
* @param modules - Array of module names reported by Loadable.Capture
* @returns Array of bundle objects containing file paths and metadata
*/
function getBundles(stats: WebpackStats, modules: string[]): Bundle[];
interface Bundle {
/** Webpack module ID */
id: number | string;
/** Module name or path */
name: string;
/** Bundle filename */
file: string;
/** Full public path to bundle */
publicPath: string;
}
interface WebpackStats {
[moduleName: string]: Bundle[];
}Usage Examples:
import { getBundles } from 'react-loadable/webpack';
import stats from './dist/react-loadable.json';
// Server-side rendering
app.get('*', (req, res) => {
const modules = [];
const html = ReactDOMServer.renderToString(
<Loadable.Capture report={moduleName => modules.push(moduleName)}>
<App />
</Loadable.Capture>
);
// Convert modules to bundles
const bundles = getBundles(stats, modules);
// Generate script tags
const scripts = bundles.map(bundle =>
`<script src="${bundle.publicPath}"></script>`
).join('\n');
res.send(`
<!DOCTYPE html>
<html>
<body>
<div id="app">${html}</div>
${scripts}
<script src="/main.js"></script>
</body>
</html>
`);
});// webpack.config.js
const path = require('path');
const { ReactLoadablePlugin } = require('react-loadable/webpack');
module.exports = {
entry: './src/index.js',
output: {
path: path.resolve(__dirname, 'dist'),
filename: '[name].[chunkhash].js',
publicPath: '/dist/',
},
plugins: [
// Generate manifest for server-side rendering
new ReactLoadablePlugin({
filename: path.resolve(__dirname, 'dist/react-loadable.json'),
}),
// Extract webpack manifest (required for proper chunk loading)
new webpack.optimize.CommonsChunkPlugin({
name: 'manifest',
minChunks: Infinity,
}),
],
optimization: {
splitChunks: {
chunks: 'all',
},
},
};{
"presets": ["@babel/preset-react", "@babel/preset-env"],
"plugins": [
"react-loadable/babel",
"@babel/plugin-syntax-dynamic-import"
]
}{
"scripts": {
"build": "webpack --mode=production",
"build:dev": "webpack --mode=development",
"start": "node server.js",
"dev": "webpack-dev-server --mode=development"
}
}Handle dynamic public paths for CDN deployment:
// webpack.config.js
module.exports = {
output: {
publicPath: process.env.CDN_URL || '/dist/',
},
plugins: [
new ReactLoadablePlugin({
filename: './dist/react-loadable.json',
}),
],
};
// Server usage
const bundles = getBundles(stats, modules);
const scripts = bundles.map(bundle => {
// Use bundle.publicPath which includes the configured publicPath
return `<script src="${bundle.publicPath}"></script>`;
}).join('\n');// webpack.config.js
const isProduction = process.env.NODE_ENV === 'production';
module.exports = {
plugins: [
new ReactLoadablePlugin({
filename: isProduction
? './dist/react-loadable.json'
: './dev/react-loadable.json',
}),
].filter(Boolean),
optimization: isProduction ? {
splitChunks: {
chunks: 'all',
cacheGroups: {
vendor: {
test: /[\\/]node_modules[\\/]/,
name: 'vendors',
chunks: 'all',
},
},
},
} : {},
};Handle applications with multiple entry points:
// webpack.config.js
module.exports = {
entry: {
main: './src/main.js',
admin: './src/admin.js',
},
plugins: [
new ReactLoadablePlugin({
filename: './dist/react-loadable.json',
}),
],
};
// Server usage - filter bundles by entry point
function getBundlesForEntry(stats, modules, entryName) {
const bundles = getBundles(stats, modules);
return bundles.filter(bundle =>
bundle.file.includes(entryName) ||
!bundle.file.match(/^(main|admin)\./)
);
}// tsconfig.json
{
"compilerOptions": {
"target": "es5",
"module": "esnext",
"moduleResolution": "node",
"jsx": "react",
"allowSyntheticDefaultImports": true
}
}// webpack.config.js
module.exports = {
resolve: {
extensions: ['.js', '.jsx', '.ts', '.tsx'],
},
module: {
rules: [
{
test: /\.(js|jsx|ts|tsx)$/,
exclude: /node_modules/,
use: {
loader: 'babel-loader',
},
},
],
},
};Analyze which components are being code-split:
// Build script to analyze loadable components
const fs = require('fs');
const stats = require('./dist/react-loadable.json');
console.log('Loadable components:');
Object.keys(stats).forEach(module => {
const bundles = stats[module];
console.log(`${module}:`);
bundles.forEach(bundle => {
console.log(` - ${bundle.file} (${bundle.id})`);
});
});// webpack.config.js - Optimize chunk sizes
module.exports = {
optimization: {
splitChunks: {
chunks: 'all',
minSize: 20000,
maxSize: 200000,
cacheGroups: {
default: {
minChunks: 2,
priority: -20,
reuseExistingChunk: true,
},
vendor: {
test: /[\\/]node_modules[\\/]/,
priority: -10,
reuseExistingChunk: true,
},
},
},
},
};Generate preload hints for critical bundles:
// Server-side bundle preloading
function generatePreloadLinks(bundles) {
return bundles
.filter(bundle => bundle.file.endsWith('.js'))
.map(bundle =>
`<link rel="preload" href="${bundle.publicPath}" as="script">`
)
.join('\n');
}
// Usage in server
const bundles = getBundles(stats, modules);
const preloadLinks = generatePreloadLinks(bundles);
res.send(`
<!DOCTYPE html>
<html>
<head>
${preloadLinks}
</head>
<body>
<!-- ... -->
</body>
</html>
`);Install with Tessl CLI
npx tessl i tessl/npm-react-loadable