Lazy Loading with React and Webpack 2

Picture under Creative Commons license (source: https://goo.gl/2Qqz8Z)

Sometimes, being lazy is a smart choice

Back in the old days, most of the front end applications were written with the support of the backend languages (and their template engines). Now with the API First approach, and even with technologies as Backend as a Service (like Firebase), the idea of build up totally backend detached front-end applications turned much more charming.

Following this idea, front-end web applications, purely written in the holy triad: JavaScript, HTML, CSS (at least the final output), are growing strong, and with that, their sizes too. When dealing with really big web apps, with fancy dashboards, and a ton of features, the developers often struggle with the final application bundle size. The bundle being one file and in a big size doesn’t affects only the loading time, but the browser loading strategy and the initial page performance too.

The Idea 💡

A lot of developers who uses the Webpack, already make use of the Commons Chunk Plugin to break the bundle in two parts, the app itself and the vendor/third party code. This idea is great, as the vendor bundle don’t change that often and we can use the power of the browser cache to serve this files almost instantly.

Thinking in that way, not all the features changes in each deploy right? Why do we need to download them every time a new app version is released?Also, why make the app bundle bigger unnecessarily?

I WILL …

No bundles (or bundlers) were harmed while writing this article

First we need a well defined architecture convention. Let’s keep it simple and suppose you’re building up a SPA using React (although this lazy loading strategy works in non-SPAs too). So each big feature of your application will be inside a module, composed by the components, services, some utility scripts, constants, etc.

As a SPA, you’re going to use a router. Let’s maintain the simplicity and choose the React Router (but any router that render components should be fine). Every module must expose a main component, and when the router call the correspondent “page”, it will be rendered.

If you’re using React+Redux, It’s going to be a little more complicated, as you need to expose the action creators and the reducers. Based on that, you can mount your own architecture, or use some predefined modular designs as Re-Ducks, for instance. What is really important is that your application have the concept of independent modules.

Enough talk!

“Talk is cheap, show me the code!” — Torvalds, Linus

First lets plan a small app:

app/
- App.js
- modules/
-- home/
-- projects/
-- contact/
- commons/
-- ui/

And let’s convention that each module inside “modules” directory will have a “index.js” as entry point and, inside it, we’re going to expose the main component.

🤓 “Why not export as default?”

We can’t assume that every module is going to expose a Component. Some module are componentless, like utility modules, which exposes only functions. If you’re going to export as default, do like most of package does nowadays, get all the named exports and export as default inside a literal object:

Exposing both named exports and default export, you can avoid the use of the alias syntax:

import * as myModule from "./myModule"

And you can import everything and/or some parts:

import myModule, {Component} from "./myModule";

And when dynamically import them, you can access in both ways:

But again, let’s avoid some overengineering for this simple example, right?

🤓 “Where is the Home.js(x) code?”

I’ll not provide it now, as it’s not important for the example. Can be any type of component, like a class, a container (in the redux case) or a stateless one. The important thing is, it needs to be a React component.

So, in the App.js we’re going to import this and use in the router. Let’s see what normally we does:

🤓 “Ok, but what about that story of breaking the stuff?”

For that, we’re going to do some changes. We’ll need to use the, ES2015 loader spec, now supported by Webpack: dynamic import.

Important! To use this feature in Webpack, you’re going to need to install the babel plugin syntax-dynamic-import.

Note that JavaScript doesn’t have native lazy evaluation, as pure functional languages do, we can simulate this effect by encapsulating the expression in a function though. Of course you can use other alternatives as reactive programing like RxJS, and create a stream like this:

RX.Observable
.defer(() => RX.Observable.fromPromise(import(" ... ")))
.share();

But let’s forget about it now, let’s keep it simple right?

🤓 “But now we don’t have react components anymore! We have functions which return Promises!”

I know! And now is where enters our React knowledge! Let’s create a React component capable of getting this “async module providers” (fancy name right?) and extracting the component from inside of it.

Simple right? It receives the function with the dynamic import and extracts the component from inside. While it waits for the desired component to load (the state “Component” stills “null”) it will show a loading component to the user.

🤓 “But what I do with this async component now?”

Simply refactor that code where we defined the routes and instead of giving the page components, we just pass the AsyncComponents, associated to their respective module loaders, to the Routes:

Another addition (optional) was the webpack magic comments, so we can properly define the chunk names, turning more easy to see what is being loaded. Other option you can provide is the webpackMode, which defines how webpack breaks and loads the chunks, but let’s stick to the default one: “lazy”. Don’t forget to add in the webpack.config.js the output.chunkFilename option:

...
output: {
...
chunkFilename: '[name].chunk.js',
...
Note this feature is available only since Webpack 2.6, so if you’re using older versions, this isn’t going to work.

It’s real!

“After read this long article, I’m really tired indeed…”

Not much extra configuration needed! Just installing the Babel plugin for dynamic imports and, if you want, using the magic comments and you’re done! All the dealing with dependencies and injections is part of Webpack job, just relax and see the magic happening!

Live Example

If you want to take a look, I’ve provided a minimum setup based on the examples used in this post. You can check the live version and the source code on my GitHub.

Conclusion

Remember to analyse each case before doing lazy loading!

The lazy loading is intended to big apps where breaking in feature chunks really can make a difference. Although all the process is pretty simple, it can be hard to apply it in already existing projects, as refactoring large apps can be a really complex work.

If you liked it please some claps 👏 and share with your team and friends! If you’re interested on a more deeper example, you can read the follow up of this article: Lazy Loading with React + Redux and Webpack 2.