W3cubDocs

/Angular 4 TypeScript

Deployment

This page describes tools and techniques for deploy and optimize your Angular application.

Table of contents

Overview

This guide describes techniques for preparing and deploying an Angular application to a server running remotely. The techniques progress from easy but suboptimal to more optimal and more involved.

Simplest deployment possible

The simplest way to deploy the app is to publish it to a web server directly out of the development environment.

It's already running locally. You'll just copy it, almost as is, to a non-local server that others can reach.

  1. Copy everything (or almost everything) from the local project folder to a folder on the server.

  2. If you're serving the app out of a subfolder, edit a version of index.html to set the <base href> appropriately. For example, if the URL to index.html is www.mysite.com/my/app/, set the base href to <base href="/my/app/">. Otherwise, leave it alone. More on this below.

  3. Configure the server to redirect requests for missing files to index.html. More on this below.

  4. Enable production mode as described below (optional).

That's the simplest deployment you can do.

This is not a production deployment. It's not optimized and it won't be fast for users. It might be good enough for sharing your progress and ideas internally with managers, teammates, and other stakeholders. Be sure to read about optimizing for production below.

Load npm package files from the web (SystemJS)

The node_modules folder of npm packages contains much more code than is needed to actually run your app in the browser. The node_modules for the Quickstart installation is typically 20,500+ files and 180+ MB. The application itself requires a tiny fraction of that to run.

It takes a long time to upload all of that useless bulk and users will wait unnecessarily while library files download piecemeal.

Load the few files you need from the web instead.

(1) Make a copy of index.html for deployment and replace all node_module scripts with versions that load from the web. It might look like this.

<!-- Polyfills -->
<script src="https://unpkg.com/core-js/client/shim.min.js"></script>

<!-- Update these package versions as needed -->
<script src="https://unpkg.com/[email protected]?main=browser"></script>
<script src="https://unpkg.com/[email protected]/dist/system.src.js"></script>

(2) Replace the systemjs.config.js script with a script that loads systemjs.config.server.js.

<!-- This SystemJS configuration loads umd packages from the web -->
<script src="systemjs.config.server.js"></script>

(3) Add systemjs.config.server.js (shown in the code sample below) to the src/ folder. This alternative version configures SystemJS to load UMD versions of Angular (and other third-party packages) from the web.

Modify systemjs.config.server.js as necessary to stay in sync with changes you make to systemjs.config.js.

Notice the paths key:

paths: {
  'npm:': 'https://unpkg.com/' // path serves as alias
},

In the standard SystemJS config, the npm path points to the node_modules/. In this server config, it points to https://unpkg.com, a site that hosts npm packages, and loads them from the web directly. There are other service providers that do the same thing.

If you are unwilling or unable to load packages from the open web, the inventory in systemjs.config.server.js identifies the files and folders that you would copy to a library folder on the server. Then change the config's 'npm' path to point to that folder.

Practice with an example

The following trivial router sample app shows these changes.

index.html
<!DOCTYPE html>
<html>
  <head>
    <!-- Doesn't load from node_modules! -->

    <!-- Set the base href -->
    <base href="/">
    <title>Simple Deployment</title>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <link rel="stylesheet" href="styles.css">

    <!-- Polyfills -->
    <script src="https://unpkg.com/core-js/client/shim.min.js"></script>

    <!-- Update these package versions as needed -->
    <script src="https://unpkg.com/[email protected]?main=browser"></script>
    <script src="https://unpkg.com/[email protected]/dist/system.src.js"></script>

    <!-- This SystemJS configuration loads umd packages from the web -->
    <script src="systemjs.config.server.js"></script>

    <script>
      System.import('main.js')
            .catch(function(err){ console.error(err); });
    </script>
  </head>

  <body>
    <my-app>loading...</my-app>
  </body>

</html>
systemjs.config.server.js
/**
 * System configuration for deployment without installing node_modules
 * Loads umd packages from the web instead
 * Adjust as necessary for your application needs.
 */
(function (global) {
  System.config({
    paths: {
      'npm:': 'https://unpkg.com/' // path serves as alias
    },
    // map tells the System loader where to look for things
    map: {
      app: 'app', // location of transpiled app files

      // angular minimized umd bundles
      '@angular/core': 'npm:@angular/core/bundles/core.umd.min.js',
      '@angular/common': 'npm:@angular/common/bundles/common.umd.min.js',
      '@angular/compiler': 'npm:@angular/compiler/bundles/compiler.umd.min.js',
      '@angular/platform-browser': 'npm:@angular/platform-browser/bundles/platform-browser.umd.min.js',
      '@angular/platform-browser-dynamic': 'npm:@angular/platform-browser-dynamic/bundles/platform-browser-dynamic.umd.min.js',
      '@angular/http': 'npm:@angular/http/bundles/http.umd.min.js',
      '@angular/router': 'npm:@angular/router/bundles/router.umd.min.js',
      '@angular/router/upgrade': 'npm:@angular/router/bundles/router-upgrade.umd.min.js',
      '@angular/forms': 'npm:@angular/forms/bundles/forms.umd.min.js',
      '@angular/upgrade': 'npm:@angular/upgrade/bundles/upgrade.umd.min.js',
      '@angular/upgrade/static': 'npm:@angular/upgrade/bundles/upgrade-static.umd.min.js',

      // other libraries
      'rxjs':                      'npm:[email protected]',
      'angular-in-memory-web-api': 'npm:angular-in-memory-web-api/bundles/in-memory-web-api.umd.js'
    },
    // packages tells the System loader how to load when no filename and/or no extension
    packages: {
      app: {
        main: './main.js',
        defaultExtension: 'js'
      },
      rxjs: {
        defaultExtension: 'js'
      }
    }
  });
  })(this);
main.ts
import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';

import { AppModule } from './app/app.module';

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

// Enable production mode unless running locally
if (!/localhost/.test(document.location.host)) {
  enableProdMode();
}

platformBrowserDynamic().bootstrapModule(AppModule);
app/app.module.ts
import { NgModule }             from '@angular/core';
import { BrowserModule }        from '@angular/platform-browser';
import { RouterModule, Routes } from '@angular/router';

import { AppComponent }           from './app.component';
import { CrisisListComponent }    from './crisis-list.component';
import { HeroListComponent }      from './hero-list.component';

const appRoutes: Routes = [
  { path: 'crisis-center', component: CrisisListComponent },
  { path: 'heroes',        component: HeroListComponent },

  { path: '', redirectTo: '/heroes', pathMatch: 'full' }
];

@NgModule({
  imports: [
    BrowserModule,
    RouterModule.forRoot(appRoutes)
  ],
  declarations: [
    AppComponent,
    CrisisListComponent,
    HeroListComponent
  ],
  bootstrap: [ AppComponent ]
})
export class AppModule { }
app/app.component.ts
import { Component } from '@angular/core';

@Component({
  selector: 'my-app',
  template: `
    <h1>Simple Deployment</h1>
    <nav>
      <a routerLink="/crisis-center" routerLinkActive="active">Crisis Center</a>
      <a routerLink="/heroes" routerLinkActive="active">Heroes</a>
    </nav>
    <router-outlet></router-outlet>
  `
})
export class AppComponent { }
app/crisis-list.component.ts
import { Component } from '@angular/core';

@Component({
  template: `
    <h2>CRISIS CENTER</h2>
    <p>Get your crisis here</p>`
})
export class CrisisListComponent { }
app/hero-list.component.ts
import { Component } from '@angular/core';

@Component({
  template: `
    <h2>HEROES</h2>
    <p>Get your heroes here</p>
  `
})
export class HeroListComponent { }

Practice with this sample before attempting these techniques on your application.

  1. Follow the setup instructions for creating a new project named .

  2. Add the "Simple deployment" sample files shown above.

  3. Run it with npm start as you would any project.

  4. Inspect the network traffic in the browser developer tools. Notice that it loads all packages from the web. You could delete the node_modules folder and the app would still run (although you wouldn't be able to recompile or launch lite-server until you restored it).

  5. Deploy the sample to the server (minus the node_modules folder!).

When you have that working, try the same process on your application.

Optimize for production

Although deploying directly from the development environment works, it's far from optimal.

The client makes many small requests for individual application code and template files, a fact you can quickly confirm by looking at the network tab in a browser's developer tools. Each small file download can spend more time communicating with the server than tranfering data.

Development files are full of comments and whitespace for easy reading and debugging. The browser downloads entire libraries, instead of just the parts the app needs. The volume of code passed from server to client (the "payload") can be significantly larger than is strictly necessary to execute the application.

The many requests and large payloads mean the app takes longer to launch than it would if you optimized it. Several seconds may pass (or worse) before the user can see or do anything userful.

Does it matter? That depends upon business and technical factors you must evaluate for yourself.

If it does matter, there are tools and techniques to reduce the number of requests and the size of responses.

  • Ahead-of-Time (AOT) Compilation: pre-compiles Angular component templates.
  • Bundling: concatenates modules into a single file (bundle).
  • Inlining: pulls template html and css into the components.
  • Minification: removes excess whitespace, comments, and optional tokens.
  • Uglification: rewrites code to use short, cryptic variable and function names.
  • Dead code elimination: removes unreferenced modules and unused code.
  • Pruned libraries: drop unused libraries and pare others down to the features you need.
  • Performance measurement: focus on optimizations that make a measurable difference.

Each tool does something different. They work best in combination and are mutually reinforcing.

You can use any build system you like. Whatever system you choose, be sure to automate it so that building for production is a single step.

Ahead-of-Time (AOT) compilation

The Angular Ahead-of-Time compiler pre-compiles application components and their templates during the build process.

Apps compiled with AOT launch faster for several reasons.

  • Application components execute immediately, without client-side compilation.
  • Templates are embedded as code within their components so there is no client-side request for template files.
  • You don't download the Angular compiler, which is pretty big on its own.
  • The compiler discards unused Angular directives that a tree-shaking tool can then exclude.

Learn more about AOT Compilation in the AOT Cookbook which describes running the AOT compiler from the command line and using rollup for bundling, minification, uglification and tree shaking.

Webpack (and AOT)

Webpack 2 is another great option for inlining templates and style-sheets, for bundling, minifying, and uglifying the application. The "Webpack: an introduction" guide will get you started using webpack with Angular.

Consider configuring Webpack with the official Angular Ahead-of-Time Webpack Plugin. This plugin transpiles the TypeScript application code, bundles lazy loaded NgModules separately, and performs AOT compilation — without any changes to the source code.

Dead code elimination with rollup

Any code that you don't call is dead code. You can reduce the total size of the application substantially by removing dead code from the application and from third-party libraries.

Tree shaking is a dead code elimination technique that removes entire exports from JavaScript modules. If a library exports something that the application doesn't import, a tree shaking tool removes it from the code base.

Tree shaking was popularized by Rollup, a popular tool with an ecosystem of plugins for bundling, minification, and uglification. Learn more about tree shaking and dead code elmination in this post by rollup-creator, Rich Harris.

Pruned libraries

Don't count on automation to remove all dead code.

Remove libraries that you don't use, especially unnecessary scripts in index.html. Consider smaller alternatives to the libraries that you do use.

Some libraries offer facilities for building a custom, skinny version with just the features you need. Other libraries let you import features a la carte. RxJS is a good example; import RxJS Observable operators individually instead of the entire library.

Measure performance first

You can make better decisions about what to optimize and how when you have a clear and accurate understanding of what's making the application slow. The cause may not be what you think it is. You can waste a lot of time and money optimizing something that has no tangible benefit or even makes the app slower. You should measure the app's actual behavior when running in the environments that are important to you.

The Chrome DevTools Network Performance page is a good place to start learning about measuring performance.

The WebPageTest tool is another good choice that can also help verify that your deployment was successful.

Angular configuration

Angular configuration can make the difference between whether the app launches quickly or doesn't load at all.

The base tag

The HTML <base href="..."/> specifies a base path for resolving relative URLs to assets such as images, scripts, and style sheets. For example, given the <base href="/my/app/">, the browser resolves a URL such as some/place/foo.jpg into a server request for my/app/some/place/foo.jpg. During navigation, the Angular router uses the base href as the base path to component, template, and module files.

See also the APP_BASE_HREF alternative.

In development, you typically start the server in the folder that holds index.html. That's the root folder and you'd add <base href="/"> near the top of index.html because / is the root of the app.

But on the shared or production server, you might serve the app from a subfolder. For example, when the URL to load the app is something like http://www.mysite.com/my/app/, the subfolder is my/app/ and you should add <base href="/my/app/"> to the server version of the index.html.

When the base tag is misconfigured, the app fails to load and the browser console displays 404 - Not Found errors for the missing files. Look at where it tried to find those files and adjust the base tag appropriately.

Enable production mode

Angular apps run in development mode by default, as you can see by the following message on the browser console:

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

Switching to production mode can make it run faster by disabling development specific checks such as the dual change detection cycles.

To enable production mode when running remotely, add the following code to the main.ts.

src/main.ts (enableProdMode)

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

// Enable production mode unless running locally
if (!/localhost/.test(document.location.host)) {
  enableProdMode();
}

Lazy loading

You can dramatically reduce launch time by only loading the application modules that absolutely must be present when the app starts.

Configure the Angular Router to defer loading of all other modules (and their associated code), either by waiting until the app has launched or by lazy loading them on demand.

Don't eagerly import something from a lazy loaded module

It's a common mistake. You've arranged to lazy load a module. But you unintentionally import it, with a JavaScript import statement, in a file that's eagerly loaded when the app starts, a file such as the root AppModule. If you do that, the module will be loaded immediately.

The bundling configuration must take lazy loading into consideration. Because lazy loaded modules aren't imported in JavaScript (as just noted), bundlers exclude them by default. Bundlers don't know about the router configuration and won't create separate bundles for lazy loaded modules. You have to create these bundles manually.

The Angular Ahead-of-Time Webpack Plugin automatically recognizes lazy loaded NgModules and creates separate bundles for them.

Server configuration

This section covers changes you may have make to the server or to files deployed to the server.

Routed apps must fallback to index.html

Angular apps are perfect candidates for serving with a simple static HTML server. You don't need a server-side engine to dynamically compose application pages because Angular does that on the client-side.

If the app uses the Angular router, you must configure the server to return the application's host page (index.html) when asked for a file that it does not have.

There is no issue when the user navigates to that URL from within a running client. The Angular router interprets the URL and routes to that page and hero.

But clicking a link in an email, entering it in the browser address bar, or merely refreshing the browser while on the hero detail page — all of these actions are handled by the browser itself, outside the running application. The browser makes a direct request to the server for that URL, bypassing the router.

A static server routinely returns index.html when it receives a request for http://www.mysite.com/. But it rejects http://www.mysite.com/heroes/42 and returns a 404 - Not Found error unless it is configured to return index.html instead.

Fallback configuration examples

There is no single configuration that works for every server. The following sections describe configurations for some of the most popular servers. The list is by no means exhaustive, but should provide you with a good starting point.

Development servers

historyApiFallback: {
  disableDotRule: true,
  htmlAcceptHeaders: ['text/html', 'application/xhtml+xml']
}

Production servers

RewriteEngine On  
# If an existing asset or directory is requested go to it as it is
RewriteCond %{DOCUMENT_ROOT}%{REQUEST_URI} -f [OR]
RewriteCond %{DOCUMENT_ROOT}%{REQUEST_URI} -d
RewriteRule ^ - [L]

# If the requested resource doesn't exist, use index.html
RewriteRule ^ /index.html
try_files $uri $uri/ /index.html;
  • IIS: add a rewrite rule to web.config, similar to the one shown here:
"rewrites": [ {
  "source": "**",
  "destination": "/index.html"
} ]

Requesting services from a different server (CORS)

Angular developers may encounter a cross-origin resource sharing error when making a service request (typically a data service request). to a server other than the application's own host server. Browsers forbid such requests unless the server permits them explicitly.

There isn't anything the client application can do about these errors. The server must be configured to accept the application's requests. Read about how to enable CORS for specific servers at enable-cors.org.

Next steps

If you want to go beyond the simple copy-deploy approach, read the AOT Cookbook next.

© 2010–2017 Google, Inc.
Licensed under the Creative Commons Attribution License 4.0.
https://angular.io/docs/ts/latest/guide/deployment.html