In preparation for the DevUp keynote a few weeks ago, I took some time to learn the minimum amount possible to demonstrate code splitting in React. Now your first reaction is probably…
Burke! Why the heck are you doing a keynote? Do keynotes mean nothing anymore?
Let me answer that by saying two things…
As part of the keynote that I weaseled my way in to, I was looking at how to optimize React builds. Which is a question that we should answer before we start spitting the code splitting.
When I say “optimize” a build, I am referring to anything which makes the code smaller and faster. There are a few techniques that are used across the JavaScript landscape to pull this off…
Three of the above items happen by default with create-react-app: you get bundling, minification and tree shaking for free with create-react-app because of Webpack.
React doesn’t do AoT as that is primarily used to compile string templates to executable JavaScript so the browser doesn’t have to do that part. React is already doing that with JSX. This is one of the reasons why React people love to say , “It’s just JavaScript!”. For the record, strings are valid JavaScript too. Just saying. It’s not like Angular and Vue people are using some made up pre-processor language that only a proprietary compiler understands — you know, like JSX.
BUT ANYWAY
That leaves us with code splitting. This is simply the idea that you want to split out your code into chunks that you can lazy load so you aren’t loading JavaScript that might not ever be used depending on where a user goes in your application.
There are good and profound technical reasons behind why you would want to do code splitting in React. These concrete reasons include…
Let’s take a look at how this might work in real life by examining a demo application that you would never use in real life.
In the GIF below you’ll see an application for tracking a list of heroes. It’s just a simple CRUD application. One interesting part of this application is that there is an edit form that only shows up when a new item is added or an existing item is selected. That means that if the user comes to this app and never adds or modifies a hero, they have loaded a chunk of JavaScript that they won’t ever use.
We can lazy load this small form so that it only shows up when someone either clicks to edit an item, or clicks “Add New Hero”. Both of those actions show the edit form.
First I am going to add a spot in my state for this “EditForm” component to live.
class Heroes extends Component {constructor(props) {super(props);this.state = {...,lazyEditHero: null}}
...,
render() {return (...)}}
I put mine in a property called lazyEditHero,
but you can call it whatever you like. Naming things is hard and there is a 100% chance you will regret it later anyway no matter what you do.
Next, we need to actually load this component in whenever someone selects “Add New Hero” or selects an existing hero to edit it. Then we’ll set the component reference in our state so we can use it later in our JSX. I have put this in a function called LoadEditForm
class Heroes extends Component {constructor(props) {super(props);this.state = {...lazyEditHero: null}}
async LoadEditForm() {const { default : EditHero } = await import('./EditHero');this.setState({ lazyEditHero: EditHero })}
render() {return (...)}}
Now we just need to call this LoadEditForm from the two functions that trigger the editor component to be shown.
class Heroes extends Component {constructor(props) {super(props);this.state = {...lazyEditHero: null}}
async LoadEditForm() {const { default : EditHero } = await import('./EditHero');this.setState({ lazyEditHero: EditHero })}
handleSelect = async hero => {await this.LoadEditForm();this.setState({ selectedHero: hero });}
handleEnableAddMode = async () => {await this.LoadEditForm();
this.setState({
addingHero: true,
selectedHero: { id: '', name: '', saying: '' }
});
}
...
render() {return (...)}}
A few items of note…
./EditHero
file is loaded and assigned (through destructuring) to a variable called “EditHero”. The reason why “default” is specified is that is where the component actually lives in the response returned by the async call which loads the file.Lastly, we need to add the component to our JSX, but only if it is not null. If it is null, we’ll just show an empty string. This also allows us to pass any props to our component when it is so lazily loaded.
class Heroes extends Component {constructor(props) {super(props);this.state = {...lazyEditHero: null}}
async LoadEditForm() {const { default : EditHero } = await import('./EditHero');this.setState({ lazyEditHero: EditHero })}
handleSelect = async hero => {await this.LoadEditForm();this.setState({ selectedHero: hero });}
handleEnableAddMode = async () => {await this.LoadEditForm();
this.setState({addingHero: true,selectedHero: { id: '', name: '', saying: '' }});}
...
render() {const EditHero = this.state.lazyEditHero;return (<div>...<div className="editarea"><button onClick={this.handleEnableAddMode}>Add</button>{EditHero ? (<EditHeroaddingHero={this.state.addingHero}onChange={this.handleOnChange}selectedHero={this.state.selectedHero}onSave={this.handleSave}onCancel={this.handleCancel}/>) : (<div></div>)}</div></div>);}}
And there you have it! Now when we run this app, it will only load the list of heroes until we click on one or try to add a new one. Once we do that, you can open the developer tools and watch the browser make an HTTP request for the new chunk of JavaScript which is loaded and executed by the browser.
So nifty! Also notice that even though we are calling that LoadEditForm
over and over again whenever we add or select an item, Webpack is smart enough not to try and reload the component so no subsequent calls are made.
Hey, it’s your world. I’m not here to tell you what to do.
But if I was, I would recommend using code splitting wherever you have particularly heavy components (byte wise) and any place that you have components that your users have a significant statistical likelihood of not using.
React Router is another fantastic place to use code splitting since you can load things as the URL changes. It would make sense to load pages of a SPA on demand and not all at once. In fact, most examples of code splitting will involve React Router.
Code splitting is cool, but don’t get carried away. If your entire application is a bunch of lazy components, you are a) defeating the purpose of the bundler, b) prematurely optimizing and c) making your code legitimately harder to read and debug.
So use code splitting carefully and then tell all your friends that you know how to do it. You will sound extremely cool and everyone will like you. That much is gauranteed.