Code Splitting for React Router with Webpack and HMR
In this guide we’ll create a starter application that uses routes with code splitting.
For big web apps, it’s not efficient to put all of your code into a single file. Large apps should only download the JavaScript required to boot the app. We want to be able to break a single file into many fragments. Splitting your code into multiple chunks is what we refer to as “code splitting”. Chunks will be loaded as the user navigates around.
Neutrino, will be used to see how we can achieve code splitting with React, Webpack, and React Router. Neutrino is an awesome tool that harnesses the power of Webpack to let you create and build modern JavaScript applications with zero initial configuration. It works by allowing us to inject shared presets or configurations. We will be using the React preset. The latter requires you to be on Node.js v6.9+. According to the Neutrino documentation, the React preset features:
- Modern Babel compilation supporting ES modules, last 2 major browser versions, async functions, and dynamic imports
- Support for import CSS, HTML, images, fonts, and icons
- Tree-shaking to create smaller bundles
- Hot Module Replacement, no HTML templating, and much more
The entire source code of this post is available on GitHub. Without further ado, let’s get started.
First, we need a directory to start working from. From your terminal, create a new directory and change into it. Then we are going to install neutrino
, neutrino-preset-react
and react-hot-loader
as development dependencies. We will also need react
, react-dom
and react-router-dom
for actual React development. react-hot-loader@3.0.0-beta-6
is the latest version at the time of this write-up.
❯ mkdir react-code-splitting
❯ cd react-code-splitting/
❯ yarn add --dev neutrino neutrino-preset-react
❯ yarn add --dev react-hot-loader@3.0.0-beta.6
❯ yarn add react react-dom react-router-dom
By default, the React preset looks for source code in a src
directory with the main entry point being index.js
.
❯ mkdir src && touch src/index.js
Let’s edit index.js
with a simple hello world example. We’ll be changing that file later but for now, we will keep it simple for the sake of testing the bare minimum.
import React from 'react';
import { render } from 'react-dom';
render(<h1>Hello world!</h1>, document.getElementById('root'));
Now edit your project’s package.json
to add commands for starting and building the application:
{
"scripts": {
"start": "neutrino start --use neutrino-preset-react",
"build": "neutrino build --use neutrino-preset-react"
},
"devDependencies": {
"neutrino": "^5.2.0",
"neutrino-preset-react": "^5.2.0"
},
"dependencies": {
"react": "^15.4.2",
"react-dom": "^15.4.2",
"react-hot-loader": "3.0.0-beta.6",
"react-router-dom": "^4.0.0"
}
}
Start the app then open a browser to the address in the console.
❯ yarn start
✔ Development server running on: http://localhost:5000
✔ Build completed
Great, we’re now ready to split code like real ninjas :-)
To support Hot Module Replacement (HMR), change index.js
, the previously created file to the following:
// index.jsimport React from 'react';
import { render } from 'react-dom';
import { AppContainer } from 'react-hot-loader';
import App from './App';
const renderApp = () => {
render(
<AppContainer>
<App />
</AppContainer>,
document.getElementById('root'),
);
};
// This is needed for Hot Module Replacement
if (module.hot) {
module.hot.accept('./App', () => renderApp());
}
renderApp();
We added AppContainer
to specify which part of the app we want HMR, and an if
statement to activate it in development mode. We also imported App.js
which we will create next.
// App.jsimport React from 'react';
import { BrowserRouter as Router, Route, Link } from 'react-router-dom';
import asyncComponent from './components/asyncComponent';
const Page1 = asyncComponent(() => import('./components/Page1')
.then(module => module.default), { name: 'Page 1' });
const Page2 = asyncComponent(() => import('./components/Page2')
.then(module => module.default), { name: 'Page 2' });
const Home = asyncComponent(() => import('./components/Home')
.then(module => module.default), { name: 'Home' });
const App = () => (
<Router>
<div className="App">
<ul>
<li><Link to="/">Home</Link></li>
<li><Link to="/page1">Page 1</Link></li>
<li><Link to="/page2">Page 2</Link></li>
</ul>
<Route exact path="/" component={Home} />
<Route path="/page1" component={Page1} />
<Route path="/page2" component={Page2} />
</div>
</Router>
);
export default App;
App.js
imports react-router-dom
and returns the Router component with the routes
injected.
Dynamic Routes
From your terminal, create routes to use. We will also need asyncComponent.js
to lazily load components.
❯ mkdir src/components
❯ touch src/components/Page1.js
❯ touch src/components/Page2.js
❯ touch src/components/Home.js
❯ touch src/components/asyncComponent.js
We’ll fill in the bare minimum for the routes.
// Page1.jsimport React from 'react';
const Page1 = (props) => <h1>{props.name}</h1>;
export default Page1;// Page2.jsimport React from 'react';
const Page2 = (props) => <h1>{props.name}</h1>;
export default Page2;// Home.jsimport React from 'react';
const Home = (props) => <h1>{props.name}</h1>;
export default Home;
We’re almost done. Our last task is to write logic to lazily load components. We will do that through asyncComponent.js
.
// asyncComponent.jsimport React from 'react';
export default (loader, collection) => (
class AsyncComponent extends React.Component {
constructor(props) {
super(props);
this.Component = null;
this.state = { Component: AsyncComponent.Component };
}
componentWillMount() {
if (!this.state.Component) {
loader().then((Component) => {
AsyncComponent.Component = Component;
this.setState({ Component });
});
}
}
render() {
if (this.state.Component) {
return (
<this.state.Component { ...this.props } { ...collection } />
)
}
return null;
}
}
);
And we’re done!
Quick Start
Start the app with yarn start
, then open a browser to http://localhost:5000
.
❯ yarn start
✔ Development server running on: http://localhost:5000
✔ Build completed
Open the network tab to see the individual chunks being loaded lazily.
Building
The React preset builds static assets to the build directory by default.
To conclude, we used Neutrino to roll up a React application and split our code into multiple chunks. We also incorporated HMR to inject updated modules without reloading the page. See react-router-starter for the complete code.
Hacker Noon is how hackers start their afternoons. We’re a part of the @AMI family. We are now accepting submissions and happy to discuss advertising & sponsorship opportunities.
If you enjoyed this story, we recommend reading our latest tech stories and trending tech stories. Until next time, don’t take the realities of the world for granted!