Clever White Space Handling

For Better Performance In Angular

This post is part of my Angular Performance Tuning article series. If you looking for ways to make your Angular application faster, you might find the other articles in this series useful too.

Update: This optimization technique has now been merged into Version 4.4.0-rc.0 too.

Update 2: Beginning with Angular 6.0.0-beta.6 this optimization option is activated by default. You can deactivate it using the the configuration entries outlined below.

Although tuning an application’s performance can be difficult, sometimes all we need to do is laying back and waiting for the next version of the used framework. This especially holds true for Angular as the Core Team is working constantly on improving things under the hoods. A good example for this is the conciser code the Angular Compiler emits beginning with Version 4 or the Angular Bundle Optimizer that transforms code to make it more treeshakable.

On more of this optimization techniques landed with 5.0.0-beta.4 about two weeks ago. It allows the compiler to remove unneeded (consecutive) white spaces from text nodes and to remove even whole text nodes that only contain white space characters. This leads to less code emitted by the AOT compiler and therefore to smaller bundle sizes as well as faster start up times.

In this post I’m describing how to use it, which performance gains I measured when applying to an example application as well as how this approach works under the covers. The example application I’ve used for this can be found in my GitHub repository.

Removing white spaces

While removing white spaces from HTML is most of the time a safe operation (at least when respecting some rules outlined below), it can also destroy your layout. Because of this, you can opt-out from it. For instance, you could do this on component level by setting the new property preserveWhitespaces to true:

@Component({
  selector: 'app-passenger-search',
  templateUrl: './passenger-search.component.html',
  styleUrls: ['./passenger-search.component.css'],
  preserveWhitespaces: true
})
export class PassengerSearchComponent  {
}

Instead of this, you can also opt-out for your whole application by using the flag preserveWhitespace in your tsconfig.app.json:

[…]
"angularCompilerOptions": {
    "preserveWhitespaces": true
}
[…]

If you use the Angular CLI please make sure you add this to the file tsconfig.app.json and not to your tsconfig.json.

In addition to this, you can protect a section within a template by adding the attribute ngPreserveWhitespaces to a tag. If you want to spot a space that isn’t allowed to be removed, you can use the pseudo-entity &ngsp;, which is converted to a space in the emitted code. Don’t confuse this with the well known entity.

Results for example application

When I applied this optimization technique to my example application, I got the following results:

  • ~ 8% reduction in size for the javascript bundle
  • ~ 4% reduction in size for the gzipped javascript bundle
  • ~ 9% faster application startup time

As this shows, this approach allows for an appreciable performance gain. This gain we get after “just” removing white spaces is higher than you might have expected. The next section shows how this is possible.

Under the hoods

To understand why such a high performance gain is possible by “just” removing white spaces, let’s look at a simple template:

<h1>
  Search for Passengers 
</h1>
<p>
  Lorem ipsum dolor sit amet.
</p>

When we compile this template, the AOT compiler transforms it to JavaScript code:

function View_PassengerSearchComponent_0(_l) {
  return __WEBPACK_IMPORTED_MODULE_1__angular_core__["_37" /* ɵvid */](
      0,
      [
        (_l()(), __WEBPACK_IMPORTED_MODULE_1__angular_core__["_15" /* ɵeld */](
                     0, null, null, 1, 'h1', [], null, null, null, null, null)),
        (_l()(), __WEBPACK_IMPORTED_MODULE_1__angular_core__["_35" /* ɵted */](
                     null, [ '\n  Search for Passenger \n' ])),
        (_l()(), __WEBPACK_IMPORTED_MODULE_1__angular_core__["_35" /* ɵted */](
                     null, [ '\n' ])),
        (_l()(), __WEBPACK_IMPORTED_MODULE_1__angular_core__["_15" /* ɵeld */](
                     0, null, null, 1, 'p', [], null, null, null, null, null)),
        (_l()(), __WEBPACK_IMPORTED_MODULE_1__angular_core__["_35" /* ɵted */](
                     null, [ '\n  Lorem ipsum dolor sit amet.\n' ]))
      ],
      null, null);
}

As you can see, this code contains a function call for each dom node we need. For instance, there is a function call for the h1 tag as well one for the p tag. In addition to that, there are function calls for text-nodes. When analyzing this, we find characters as well as even a text node that aren’t displayed in the browser due to the way HTML works. Examples are consecutive white spaces as well as empty text nodes like the one between the closing h1 tag and the opening p tag.

When using the discussed technique, the compiler removes these whites spaces or these text nodes. The following image shows a diff between the emitted code above and the code that is created after activating white-space-removal:

diff between version with and w/o whitespaces

This shows that a whole function call as well as some consecutive white space characters are removed. When we just count the removed lines for the sake of simplicity we see they make up about 10 % of all lines. This explains the reduction in bundle size we’ve faced. Of course, a reduction in function calls means that the Browser can download the bundles faster as well as Angular has less to do when starting the application. Both leads to the improvement of start up performance mentioned above.