Partial application loading is an essential technique for improving the time-to-first-impression for single page applications. The goal is to prioritize loading of the code needed to render the view whilst deferring other assets. We are about to see how we can achieve that with modern web development’s latest and greatest tools:
– React 15.0
– React Router 2.0
– Webpack 2.1
In addition to using the latest versions of the above stack, weโll also do it in style:
– Use native ES6 imports (without requires)
– Implement code splitting
– Harness tree shaking
I showcased the process in a simple React app that uses React Router and loads route chunks dynamically. The entire projectโs source code is available on Github and you can also see the final demo live.
Basic Setup
Our basic npm setup will include React, React Router and Webpack 2. You can set it up manually following my example or just clone this git repo.
Code Splitting (Webpack Chunks)
Webpack is smart enough to figure out the chunks automatically when it scans your application code. All you need to provide it with is an entry point.
Our sample Webpack config file lists two entry points, application logic (js) and libraries (vendor):
entry: { js: [ 'index', 'pages/Home' ], vendor: [ 'react', 'react-dom' ] },
While this step isnโt required, it makes sense to separate libraries from app logic so that we can leverage browser caching to a greater extent. Vendors will become a separate javascript bundle that we can reuse for any initial route the visitors use to access the app.
The js
entry point should load most of the shared application code. This is how we expect Webpack to learn about the code that needs to be split into separate chunks. All direct imports that belong to the js
entry point will be bundled into a single file. However, Webpack will read through our source code and understand which bits we might need to load asynchronously, then create meaningful chunks (separate javascript files) for future usage.
How does it do that, you might ask? Letโs take a look at that now.
Dynamic Routes
Ryan Florence authored a great article on the future of web application delivery that outlines a very similar process using require.ensure
. My recent post on Tree Shaking with Webpack 2 explains how Webpack 2 is capable of reading native import statements. This example, in contrast to Ryan Florenceโs approach, makes use of the newly introduced Webpack 2 features.
Hereโs the key concept: React Router configuration will use getter functions for the routes that we want bundled separately. Check it out:
import App from 'containers/App'; function errorLoading(err) { console.error('Dynamic page loading failed', err); } function loadRoute(cb) { return (module) => cb(null, module.default); } export default { component: App, childRoutes: [ { path: '/', getComponent(location, cb) { System.import('pages/Home').then(loadRoute(cb)).catch(errorLoading); } }, { path: 'blog', getComponent(location, cb) { System.import('pages/Blog').then(loadRoute(cb)).catch(errorLoading); } }, { path: 'about', getComponent(location, cb) { System.import('pages/About').then(loadRoute(cb)).catch(errorLoading); } }, ] };
In our example, we used the getComponent
function that Router automatically calls when a route is requested. System.import
will know which file to load because Webpack created all the instructions needed. Webpack will look for System.import
statements to know which chunks of your code should be bundled. Magic!
Letโs review the process:
- Webpack scans your code during the build process. It treats
System.import
calls as import statements and bundles the imported file along with its dependencies. Itโs important to know that dependencies will not collide with those in the main application bundle (aka the initial entry point of the Webpack configuration file). - A visitor accesses the app, starting from any random route. Anything from index.html is loaded, including the vendor files and the initial entry point (your main app logic). React Router executes
getComponent
based on the requested route path. System.import
is now a polyfill that executes a JSONP request in order to fetch the bundle required for the route to execute. This is an asynchronous process (like any XHR request).System.import
is also a Promise. When the response is delivered, weloadRoute
. In other words, we execute the chunk that was loaded. Voila, the view is displayed to our user.- If the route is accessed again, the router (actually the
System.import
polyfill) will already know about it and no additional code downloading will occur.
Go ahead and try it on the demo site. Open your network inspector tab and monitor the traffic. You will see how chunks are loaded (or not) automatically. You can also go to a route (e.g. /blog) and reload from there. Pure awesomeness!
Tree Shaking for React Router
Tree shaking works with native ES6 modules, but not the CommonJS counterpart. Luckily, React Router ships with es6 module support, but you need to access them explicitly.
Our index.js shows exactly how to do it:
import { Router, browserHistory } from 'react-router/es6';
Notice the trailing /es6. Now our Webpack 2 build process will only load the components it needs from the React Router package.
Caveats
System.import should be straightforward for Webpack to read. Routes built from variables will not load properly, at least in Webpack 2.1 beta 4. This makes sense as it would take complex parsing to figure out all the possible combinations.
There is a good reason why we didnโt include React Router and the History package in our vendors chunk. Remember, we only used React in it. Thatโs because we want to leverage the tree shaking technology and bundle only the code our app needs. React doesnโt ship with ES6 imports quite yet, so we can move the entire thing into vendors.js. To recap, we put infrequently changed libraries into the vendors bundle, which can be more aggressively cached on the browser side. Having a separate file also helps with download concurrency.
Conclusion
41% of modern React apps use React Router, according to npm package download stats for February 2016. This makes React Router an essential part of the modern React stack. Support for partial application loading is extremely important from the performance perspective. As we can see, itโs also a lot of fun to implement.
Grgur Grisogono
Related Posts
-
Ext JS to React: Class Instantiation and Code Style
This is part of the Ext JS to React blog series. You can review the…
-
Ext JS to React: Class Instantiation and Code Style
This is part of the Ext JS to React blog series. You can review the…