100 days of Angular 2 - Day 6: Serving an Angular 2 App in Production

Today I'm going to start off with making my Angular 2 / Express application a little more "production" worthy than it already is.

Serving an Angular 2 app in Production

Let's first revisit some key metrics from my first Angular 2 quickstart application from day 1.

My basic "Hello World" application had 39 requests and involved 1.8 MB transferred.

Since then (on day 4 and day 5) I've switched from SystemJS to Webpack and started using the bundler and uglifier to consolidate my files.

I've also added a few more components (which means more HTML/CSS/JavaScript), so this test isn't entirely fair, but take note that I'm still handling fewer requests and sending even less on the wire:

This was a result of bundling and uglifying/minifying my code.

But, I can do even better! One, I can enable gzip compression on my web server, and two, I can investigate why Angular 2 thinks it's running in development mode.

Turning on gzip

As I've mentioned (on day 1), I have nginx sitting between the world wide web and my Angular 2 app. But I haven't actually enabled gzip yet, as you can see in the response headers:

I took a look at my current nginx configuration located at /etc/nginx/nginx.conf

##
# Gzip Settings
##

gzip on;
gzip_disable "msie6";

# gzip_vary on;
# gzip_proxied any;
# gzip_comp_level 6;
# gzip_buffers 16 8k;
# gzip_http_version 1.1;
# gzip_types text/plain text/css application/json application/x-javascript text/xml application/xml application/xml+rss text/javascript;

Despite it saying that is on, I actually need to uncomment the extra settings. I also added one more directive

gzip_min_length 256;

because some brief googling suggested I add it. Apparently files smaller than 256 bytes don't really benefit from compression.

Remember, gzip is great for text-based assets, which includes HTML, CSS, and JavaScript, which is probably why the default gzip_types above specify them already. It is not so great for images. Read more here.

Once I uncommented the settings above and added the extra min length directive I checked my nginx settings:

stephen@ubuntu:/etc/nginx$ nginx -t
nginx: the configuration file /etc/nginx/nginx.conf syntax is ok
nginx: configuration file /etc/nginx/nginx.conf test is successful

And then did a hot reload:

stephen@ubuntu:/etc/nginx$ service nginx reload
* Reloading nginx configuration nginx    [ OK ] 

I refreshed my Angular 2 app with the Chrome network tab open in the developer console aaaand....

.... saw no decrease in file size! What the heck?

Turns out Webpack or Express or someone is serving the .js files as Content-Type:application/javascript which was not one of the included gzip_types above, so I just needed to add it.

I refreshed my Angular 2 app with the Chrome network tab open in the developer console aaaand....

Woohoo! My 1.1 MB Angular 2 app magically turned into a 246 KB Angular 2 app!

I can't recommended gzip enough! Other folks also recommend it. Although compression is a trade off between bandwidth and CPU, the CPU required to compress files is pretty minimal with today's computers.

This is probably why 92% of the top 1,000 websites use compression. 69.5% (and growing) of the rest of the sites out on the web use compression.

Okay, with that taken care of, onto...

Enabling production mode

See the console in the screenshot above?

Angular 2 is running in the development mode. Call enableProdMode() to enable the production mode.

This was because I forgot to include the following lines in my main.ts file

import { enableProdMode } from '@angular/core';

if (process.env.ENV === 'production') {
  enableProdMode();
}

right before the bootstrap(...) call.

Note that the process.env.ENV values looks like it comes from the webpack.prod.js file:

...(snip)...

const ENV = process.env.NODE_ENV = process.env.ENV = 'production';

...(snip)...

plugins: [
  new webpack.DefinePlugin({
    'process.env': {
      'ENV': JSON.stringify(ENV)
    }
  })
]

Once I added that call to enableProdMode() the warning went away.

A brief mention of forever

Yesterday I incorporated forever into my project without explaining why I did so.

Remember on days 1 and 2 where I just used screen to keep the lite-server running?

$ screen
$ npm run-script lite
(Ctrl-A and 'd' to detach the process)

Or on days 4 and 5 where again I used screen to keep http-server running?

$ screen
$ npm run build
$ http-server ./dist -p 3000
(Ctrl-A and 'd' to detach the process)

(As a quick aside, I could also have used nohup to keep the process running instead of screen)

These were fine, short term hacks, but they do not handle situations when the server crashes or enters an invalid state.

The first person to crash the server would cause everyone else's requests to go unfulfilled. There is no mechanism with screen or nohup to detect a bad exit code and automatically restart the server.

The Express page lists the three most popular process managers for Express, and includes StrongLoop Process Manager, PM2, and Forever.

Currently PM2 is more popular than Forever and Forever is more popular than StrongLoop Process Manager.

I've used Forever for a few years and have liked its simplicity and robustness, but at some point I'd like to check out PM2 which looks to offer better clustering/swapping support.

Anyways, using one of these will allow further "production polish" because it will monitor the app and automatically restart it if it crashes for some reason.

Day 6 summary

I didn't actually get to spend too much time working on the project today, since some other things came up. But I was able to do some quick, minor polish for the production environments.

I have a fun idea for tomorrow! See day 7 here.

As a reminder, you can see all of my code on GitHub.