Improving Angular 2 Load Times and a 29KB Hello World App

James Judd

Reading time: about 8 min

Topics:

  • Architecture
  • Behind The Scenes
At the beginning of this year, Lucidchart rebuilt its editor in Angular 2. The new editor delivered a better experience with far fewer lines of code. Both engineers and product managers enjoyed it because it made writing features easier.
The old (left) and new (right) Lucidchart editor The old (left) and new (right) Lucidchart editor
But, there was a big problem: load times. The Angular 2 version consistently took several seconds longer to load than the non-Angular version. Over the summer, we worked with the Angular team to improve our load times. The end result of these efforts is that our new editor loads five seconds faster than it did before and one second faster than our old editor. We even created a 29KB Hello World Angular 2 app along the way.

The Problem

When we released the beta of the Angular 2 version of our editor, we consistently saw load times about four seconds slower than our old editor.
Load times before improvement Load times for our old editor (purple) vs our new editor (blue) for the week of July 10th.
While analyzing our load times, we found that a large amount of time was being spent bootstrapping the Angular app with the runtime template compiler. We saw mentions of an offline compiler, but it did not appear to be available or documented yet. In an attempt to improve our load times, we reached out to the Angular team in March about Ahead of Time (AoT) compilation. They kept us up to date on the progress of the AoT compiler and informed us that it only worked with TypeScript. Problem was, we were using the JavaScript version of Angular 2 in concert with the Google Closure Compiler to typecheck and minify our JavaScript. When we mentioned to the Angular team our inability to use TypeScript due to the Closure Compiler, they suggested two tools they built at Google to solve this same problem: Tsickle and Clutz.

The Solution

The Angular team built two tools to use TypeScript with the Closure Compiler: Tsickle and Clutz. Tsickle produces Closure annotated JavaScript from TypeScript, and Clutz produces TypeScript definition files from Closure annotated JavaScript. Together, they enable you to use TypeScript in a Closure compatible codebase, and vice versa. At Lucid, we modified Angular 2, its dependencies, and the AoT compiler in order to produce Closure compatible JavaScript from Angular 2 TypeScript. The end result is a five second improvement in load time for our new editor. Load times before and after improvement The above figure shows load times from May to September for our old editor (purple) and our new editor (blue). We switched from JavaScript to TypeScript with simple optimizations from the Closure compiler in the middle of August, which is when we see the first dip in load times. The second dip comes at the end of August, when we got Closure’s advanced optimizations working with TypeScript. Currently, we see load times about five seconds lower, on average, with AoT compilation than with runtime compilation. The new load times are even about one second faster, on average, than our old editor.
Load times after improvement Load times for our old editor (purple) vs our new editor (blue) for the week of Aug 29th.
When we compare flame graphs collected while loading our new editor with AoT and runtime compilation, we see a large difference. The biggest difference is, as expected, between the Angular 2 bootstrap and the first Angular 2 tick. The ahead of time compiler cuts out a large amount of template parsing and compilation that otherwise happens at runtime. Accordingly, things are much faster. Fair warning, the scales on these graphs are not equal.
Runtime compiler flame graph Runtime compiler flame graph
AoT compiler flame graph AoT compiler flame graph

How We Did It

In this section and the next, we discuss what we did to make this happen and provide an example, in case you would like to reproduce our efforts. For those who want to skip straight to the example, you can find it here: https://github.com/lucidsoftware/closure-typescript-example

Tsickle

Tsickle is a project from the Angular team that produces Closure compatible JavaScript from TypeScript. It wraps the TypeScript Compiler and uses the TypeScript Compiler API to add Closure compatible annotations to TypeScript, compiles TypeScript to JavaScript, and converts CommonJS modules to goog.modules. You can use Tsickle to create TypeScript dependencies in your Closure JavaScript codebase. Tsickle transforms something like this:
import Statement from 'goog:Statement';

export class Greeter {
    constructor(public statement: Statement) {}
    greet(): string {
        return this.statement.getStatement();
    }
};
Into this:
goog.module('js.app.ts.greeter');var module = module || {id: 'js/app/ts/greeter.js'};

class Greeter {
    /**
     * @param {?} statement
     */
    constructor(statement) {
        this.statement = statement;
    }
    /**
     * @return {?}
     */
    greet() {
        return this.statement.getStatement();
    }
    static _tsickle_typeAnnotationsHelper() {
        /** @type {?} */
        Greeter.prototype.statement;
    }
}

exports.Greeter = Greeter;
;
//# sourceMappingURL=greeter.js.map

Clutz

Clutz is a project from the Angular team that produces TypeScript definition files from Closure compatible Javascript. It uses the Closure Compiler’s API mode and performs a full closure compilation in order to produce a definition file that lets you use all your existing Closure JavaScript in a TypeScript project. Symbols or modules from your Closure JavaScript are available in TypeScript under the "goog:" prefix. For example, if you had "goog.provide('lucid.model.CustomShape');" in your JavaScript, you could import it in TypeScript using "import CustomShapeModel from 'goog:lucid.model.CustomShape';" Clutz can be used to turn something like this:
goog.provide('Statement');

/**
 * @constructor
 * @param {string} statement
 */
var Statement = function(statement) {
    /** @private {string} */
    this.statement = statement;
}

/**
 * @return {string}
 */
Statement.prototype.getStatement = function() {
    return this.statement;
}

/**
 * @param {string} statement
 * @return {string}
 */
Statement.prototype.setStatement = function(statement) {
    this.statement = statement;
}
Into this:
declare namespace ಠ_ಠ.clutz {

    class Statement extends Statement_Instance {
    }

    class Statement_Instance {
      private noStructuralTyping_: any;
      constructor (statement : string ) ;
      getStatement ( ) : string ;
      setStatement (statement : string ) : string ;
    }
}

Putting it All Together

Using Tsickle and Clutz, we modified our build system to support TypeScript dependencies in JavaScript and vice versa. It works like this: TypeScript and JavaScript build targets can mark other build targets as dependencies. Those build targets are then built, using either Tsickle or Clutz, depending on the type of dependency.
Example application using Tsickle and Clutz Example application using Tsickle and Clutz
In order to support Angular 2 generating Closure compatible JavaScript with AoT compilation, we modified Angular 2 and RxJS. We built a custom version of both projects and currently use them in our build process. If you would like to know more about the modifications we made to get this working, please see this GitHub issue.

The 29 KB Hello World App

Lucid built a proof of concept Hello World Angular 2 application using Tsickle and Clutz before we tried implementing the same thing in our large JavaScript codebase. You can find the example on GitHub at https://github.com/lucidsoftware/closure-typescript-example. The purpose of the project is to provide an example of Tsickle, Clutz, and Angular 2 working together. Things are built from source, so you can see how we go from modified Angular 2 all the way to the final product. Comments, questions, and pull requests are welcome. When we compress the resulting bundle using Brotli, the final bundle size is right around 29KB. gzip is not far behind:
$ ll --block-size=KB
-rw-rw-r-- 1 james james 115kB Sep 27 02:45 main.js
-rw------- 1 james james 29kB  Sep 27 02:45 main.js.brotli-11
-rw-rw-r-- 1 james james 35kB  Sep 27 02:45 main.js.gz

How to Run the Example

More information can be found in the example’s README, but to run the example, either:

1. Docker

docker pull jjudd/closure-typescript-example
docker run -t -i -p 8000:8000 --net=host jjudd/closure-typescript-example
localhost:8000/index.html?compiled=1

2. Build it Yourself

make run
Note: You will need to make sure you have all the dependencies installed for this to work. It has only been tested on Ubuntu 14.04.

About Lucid

Lucid Software is a pioneer and leader in visual collaboration dedicated to helping teams build the future. With its products—Lucidchart, Lucidspark, and Lucidscale—teams are supported from ideation to execution and are empowered to align around a shared vision, clarify complexity, and collaborate visually, no matter where they are. Lucid is proud to serve top businesses around the world, including customers such as Google, GE, and NBC Universal, and 99% of the Fortune 500. Lucid partners with industry leaders, including Google, Atlassian, and Microsoft. Since its founding, Lucid has received numerous awards for its products, business, and workplace culture. For more information, visit lucid.co.

Get Started

  • Contact Sales

Products

  • Lucidspark
  • Lucidchart
  • Lucidscale
PrivacyLegalCookie privacy choicesCookie policy
  • linkedin
  • twitter
  • instagram
  • facebook
  • youtube
  • glassdoor
  • tiktok

© 2024 Lucid Software Inc.