Check out myAngular article series with live demos and Facebook group Angular - Advanced Topics

Lazy Loading with the Closure Compiler

Torgeir "Tor" Helgevold
- JavaScript Developer and Blogger
Published: Wed Dec 07 2016

In the following post I will show how to do code splitting with the Closure compiler.

In my previous post I showed how to use the Closure compiler, and how to get around challenges from using external libraries, not compatible with the Closure compiler.

In my past examples the output was always a single file bundle. In this post I will expand on this and show how to use code splitting to spread the code across multiple bundle files. The typical use case for this is lazy loading.

When bundles become sufficiently large it's no longer practical to download the entire application as a single bundle file. It's much better to split the application into multiple files and lazy load the application on demand instead.

I will show a simple example of how this can be done using the Closure compiler.

My demo application is a trivial app that supports two distinct features: As a user you can load a list of countries and a list of friends.

From a bundling perspective I will split the code for the friends list and the country list into two separate bundles. This will allow for lazy loading of each feature on demand.

Let's start by looking at the code.

The code is split into two different modules: countries.js and friends.js

Country list

index.js
import {Countries} from './countries'; let countries = new Countries();
country.js
import {HeadingService} from '../heading-service'; export class Countries { constructor() { this.countries = this.getCountries(); this.renderCountries(); new HeadingService().setHeading('countryHeading', 'Countries'); } getCountries() { return [{name: 'USA'}, {name: 'France'}, {name: 'Denmark'}, {name: 'Canada'}]; } renderCountries() { var countryList = document.getElementById("countrylist"); this.countries.forEach(function (country) { var li = document.createElement("li"); li.textContent = country.name; countryList.appendChild(li); }); } }

Friends List

index.js
import {Friends} from './friends'; let friends = new Friends();
friends.js
import {HeadingService} from '../heading-service'; export class Friends { constructor() { this.friends = this.getFriends(); this.renderFriends(); new HeadingService().setHeading('friendsHeading', 'Friends'); } getFriends() { return [{firstName: 'Joe', lastName: 'Smith', age: 20}, {firstName: 'Jane', lastName: 'Doe', age: 30}, {firstName: 'John', lastName: 'Doe', age: 40}, {firstName: 'Peter', lastName: 'Jackson', age: 50} ]; } renderFriends() { var friendsList = document.getElementById("friendslist"); this.friends.forEach(function (friend) { var li = document.createElement("li"); li.textContent = this.createText(friend); friendsList.appendChild(li); }); } createText(friend) { return `${friend.firstName} ${friend.lastName}, age: ${friend.age}`; } }

A common HeadingService has been split out into its own module in heading-service.js

heading-module.js
export class HeadingService { setHeading(id, text) { document.getElementById(id).innerText = text; } }

The last piece is the entry point into the application. This is where the lazy loading will happen.

The specifics of the lazy loading will of course depend on how the application is built. If you are using a framework you might be able to leverage a router.

In my case I've kept it simple by letting a simple button click control the lazy loading. When clicking on either “Load Friends” or “Load Countries”, the corresponding bundle will be loaded.

In main.js I wire up the click handlers that will trigger lazy loading. I am using a framework called SystemJS to load the bundles on click.

main.js
document.getElementById('btnFriends').addEventListener('click', function(){ System.import('friends.js'); }); document.getElementById('btnCountries').addEventListener('click', function(){ System.import('countries.js'); });

Generate Bundles

Next we have to talk about how to turn these modules into separate bundles that can be loaded independently.

As in my previous article I have put all my Closure compiler configurations in a build.sh file as seen below:

OPTS=( "--language_in=ES6_STRICT" "--language_out=ES5" "--compilation_level=ADVANCED_OPTIMIZATIONS" "--js src/heading-service.js" "--module common:1" "--js src/friends/index.js" "--js src/friends/friends.js" "--module friends:2:common" "--js src/countries/index.js" "--js src/countries/countries.js" "--module countries:2:common" ) set -ex java -jar node_modules/google-closure-compiler/compiler.jar $(echo ${OPTS[*]})

I explain this in more detail in my previous article, but there are a few new items to talk about.

There are three new sections consisting of --js and --module combinations. These sections define the module boundaries for the code splitting.

The format is as follows: --js defines the JavaScript files that make up the bundle --module defines the module for each split point.

The first parameter to --module is the bundle name. The second parameter is the number of files that make up the bundle. This number must match the number of --js entries. The last parameter is used to specify a dependency on another module if this applies. In this case both of my modules depend on the “common” module where the shared heading-service lives.

In my configuration I have three modules: One shared module and two feature modules.

However, there is no guarantee that the shared module will exist after the code is compiled. The Closure compiler might decided that we are better of flattening/inlining the common module in the two feature modules.

In fact that is what will happen here. The result of setHeading will be inlined in both feature modules as

document.getElementById("countryHeading").innerText="Countries"};

and

document.getElementById("friendsHeading").innerText="Friends"};

This is more optimized than keeping the original service in a separate file.

Splittable

This example requires some familiarity with Closure compiler configurations. There is actually an alternative if you prefer to abstract away the configuration details. If you want to avoid manual configuration you can use a framework called Splittable.

Splittable (https://www.npmjs.com/package/splittable) uses the Closure compiler under the hood, but streamlines the configuration a bit.

In this case the split point configuration reduces to:

splittable src/countries/index.js src/friends/index.js

Splittable will figure out the rest and output the corresponding Closure compiled bundles. The output consists of the two modules and a third common module.

Demo

If you are interested in the source code for this article, you may download it from Github. The source has examples showing how to use the Closure compiler directly.

I have deployed a live demo here. The demo uses the bundles generated by the Closure compiler directly, not the “Splittable” version.

If you liked this article, share it with your friends on social media:

We also have a new Facebook group about advanced Angular topics.

I invite you to follow me on twitter