Introducing React Loadable
Component-centric code splitting and loading in React
When you have a large enough application, a single large bundle with all of your code becomes a problem for startup time. You need to start breaking your app into separate bundles and load them dynamically when needed.
But now you need to find places in your application where you can decide to split off into another bundle and load it asynchronously. You also need a way to communicate in your app when something is loading.
Route-based splitting vs Component-based splitting
A common piece of advice you will see is to break your app into separate routes and load each one asynchronously. This seems to work well enough for most apps, clicking on a link and loading a new page is not a terrible experience.
But we can do better than that.
Using most routing tools for React, a route is simply a component. There’s nothing particularly special about them. So what if we optimized around components instead of delegating that responsibility to routes? What would that buy us?
It turns out quite a lot. There are many more places than just routes where you can pretty easily split apart your app. Modals, tabs, and many more UI components hide content until the user has done something to reveal it.
Not to mention all the places where you can defer loading content until higher priority content is finished loading. That component at the very bottom of your page which loads a bunch of libraries: Why does that need to be loaded at the same time as the content near the top?
You can still easily split on routes too since they are simply components. Just do whatever is best for your app.
But we need to make splitting up at the component-level as easy as splitting at the route-level. To split in a new place should be as easy as changing a few lines of app code and everything else is automatic.
Introducing React Loadable
React Loadable is a small library I wrote after getting fed up of you people saying this was hard to do.
Loadable is a higher-order component (a function that creates a component) which makes it easy to split up bundles on a component level.
Let’s imagine two components, one that imports and renders another.
Right now we are depending on
AnotherComponent being imported synchronously via
import. We need a way to make it loaded asynchronously.
Using a dynamic import (a tc39 proposal currently at stage 3) we can modify our component to load
However, this is a bunch of manual work, and it doesn’t even handle a lot of different cases. What about when the
import() fails? What about server-side rendering?
Instead you can use
Loadable to abstract away the problem. Using Loadable is simple. All you need to do is pass in a function which loads your component and a “Loading” component to show while your component loads.
But what if the component fails to load? We need to also have an error state.
In order to give you maximum control over what gets displayed when, the
error will simply be passed to your
LoadingComponent as a prop.
Automatic code-splitting on import()
The great things about
import() is that Webpack 2 can actually automatically split your code for you whenever you add a new one without any additional work.
This means that you can easily experiment with new code splitting points just by switching to
import() and using React Loadable. Figure out what performs best on your app.
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.
So your loading component will also get a
pastDelay prop which will only be
true once the component has taken longer to load than a set
delay defaults to 200ms but you can also customize the
delay using a third argument to
As an optimization, you can also decide to preload a component before it gets rendered.
For example, if you need to load a new component when a button gets clicked you could start preloading the component when the user hovers over the button.
The component created by
Loadable exposes a
preload static method which does exactly this.
Loader also supports server-side rendering through one final argument.
Passing the exact path to the module you are loading dynamically allows Loader to
require() it synchronously when running on the server.
This means that your async-loaded code-splitted bundles can render synchronously server-side.
The problem then comes with picking back up on the client. We can render the application in full on the server-side but then on the client we need to load in bundles one at a time.
But what if we could figure out which bundles were needed as part of the server-side bundling process? Then we could ship those bundles to the client all at once and the client picks up in the exact state the server rendered.
You can actually get really close to this today.
Because we have the all the paths for server-side requires in
Loadable, we can add a new
flushServerSideRequires function that returns all the paths that ended up getting rendered server-side. Then using
webpack --json we can match together the files with the bundles they ended up in (You can see my code here).
The only remaining issue is to get Webpack playing nicely on the client. I’ll be waiting for your message after I publish this Sean.
There’s all sorts of cool shit we could build once this all integrates nicely. React Fiber will enable us to be even smarter about which bundles we want to ship immediately and which ones we want to defer until higher priority work is complete.