Building production ready Angular apps with Visual Studio and ASP.NET Core

This article shows how Angular SPA apps can be built using Visual Studio and ASP.NET Core which can be used in production. Lots of articles, blogs templates exist for ASP.NET Core and Angular but very few support Angular production builds.

Although Angular 2 is not so old, many different seeds and build templates already exist, so care should be taken when choosing the infrastructure for the Angular application. Any Angular template, seed which does not support AoT or treeshaking should NOT be used, and also any third party Angular component which does not support AoT should not be used.

This example uses webpack 2 to build and bundle the Angular application. In the package.json, npm scripts are used to configure the different builds and can be used inside Visual Studio using the npm task runner.

Code: Visual Studio 2015 project | Visual Studio 2017 project

Blogs in this series:

2017.01.14 Added lazy loading, updated the Angular 2.4.3 and webpack 2.2.0-rc.4

Short introduction to AoT and treeshaking


AoT stands for Ahead of Time compilation. As per definition from the Angular docs:

“With AOT, the browser downloads a pre-compiled version of the application. The browser loads executable code so it can render the application immediately, without waiting to compile the app first.”

With AoT, you have smaller packages sizes, fewer asynchronous requests and better security. All is explained very well in the Angular Docs:

The AoT uses the platformBrowser to bootstrap and not platformBrowserDynamic which is used for JIT, Just in Time.

// Entry point for AoT compilation.
export * from './polyfills';

import { platformBrowser } from '@angular/platform-browser';
import { enableProdMode } from '@angular/core';
import { AppModuleNgFactory } from '../aot/angular2App/app/app.module.ngfactory';




Treeshaking removes the unused portions of the libraries from the application, reducing the size of the application.

npm task runner

npm scripts can be used easily inside Visual Studio by using the npm task runner. Once installed, this needs to be configured correctly.

VS2015: Go to Tools –> Options –> Projects and Solutions –> External Web Tools and select all the checkboxes. More infomation can be found here.

In VS2017, this is slightly different:

Go to Tools –> Options –> Projects and Solutions –> Web Package Management –> External Web Tools and select all checkboxes:


npm scripts


ngc is the angular compiler which is used to do the AoT build using the tsconfig-aot.json configuration.

"ngc": "ngc -p ./tsconfig-aot.json",

The tsconfig-aot.json file builds to the aot folder.

  "compilerOptions": {
    "target": "es5",
    "module": "es2015",
    "moduleResolution": "node",
    "sourceMap": false,
    "emitDecoratorMetadata": true,
    "experimentalDecorators": true,
    "removeComments": true,
    "noImplicitAny": true,
    "suppressImplicitAnyIndexErrors": true,
    "skipLibCheck": true,
    "lib": [
  "files": [
  "angularCompilerOptions": {
    "genDir": "aot",
    "skipMetadataEmit": true
  "compileOnSave": false,
  "buildOnSave": false


The build-production npm script is used for the production build and can be used for the publish or the CI as required. The npm script used the ngc script and the webpack-production build.

"build-production": "npm run ngc && npm run webpack-production",

webpack-production npm script:

"webpack-production": "set NODE_ENV=production&& webpack",


The watch build monitors the source files and builds if any file changes.

"watch-webpack-dev": "set NODE_ENV=development&& webpack --watch --color",

start (webpack-dev-server)

The start script runs the webpack-dev-server client application and also the ASPNET Core server application.

"start": "concurrently \"webpack-dev-server --inline --progress --port 8080\" \"dotnet run\" ",

Any of these npm scripts can be run from the npm task runner.



When deploying the application for an IIS, the build-production needs to be run, then the dotnet publish command, and then the contents can be copied to the IIS server. The publish-for-iis npm script can be used to publish. The command can be started from a build server without problem.

"publish-for-iis": "npm run build-production && dotnet publish -c Release" 


When deploying to an IIS, you need to install the DotNetCore.1.1.0-WindowsHosting.exe for the IIS. Setting up the server IIS docs:

Why not webpack task runner?

The Webpack task runner cannot be used for Webpack Angular applications because it does not support the required commands for Angular Webpack builds, either dev or production. The webpack -d build causes map errors in IE and the ngc compiler cannot be used, hence no production builds can be started from the Webpack Task Runner. For Angular Webpack projects, do not use the Webpack Task Runner, use the npm task runner.

Full package.json

  "name": "angular2-webpack-visualstudio",
  "version": "1.0.0",
  "description": "",
  "main": "wwwroot/index.html",
  "author": "",
  "license": "ISC",
  "scripts": {
    "ngc": "ngc -p ./tsconfig-aot.json",
    "start": "concurrently \"webpack-dev-server --inline --progress --port 8080\" \"dotnet run\" ",
    "webpack-dev": "set NODE_ENV=development && webpack",
    "webpack-production": "set NODE_ENV=production && webpack",
    "build-dev": "npm run webpack-dev",
    "build-production": "npm run ngc && npm run webpack-production",
    "watch-webpack-dev": "set NODE_ENV=development && webpack --watch --color",
    "watch-webpack-production": "npm run build-production --watch --color",
    "publish-for-iis": "npm run build-production && dotnet publish -c Release"
  "dependencies": {
    "@angular/common": "~2.4.3",
    "@angular/compiler": "~2.4.3",
    "@angular/core": "~2.4.3",
    "@angular/forms": "~2.4.3",
    "@angular/http": "~2.4.3",
    "@angular/platform-browser": "~2.4.3",
    "@angular/platform-browser-dynamic": "~2.4.3",
    "@angular/router": "~3.4.1",
    "@angular/upgrade": "~2.4.3",
    "angular-in-memory-web-api": "0.2.4",
    "core-js": "2.4.1",
    "reflect-metadata": "0.1.9",
    "rxjs": "5.0.3",
    "zone.js": "0.7.5",
    "@angular/compiler-cli": "~2.4.3",
    "@angular/platform-server": "~2.4.3",
    "bootstrap": "^3.3.7",
    "ie-shim": "~0.1.0"
  "devDependencies": {
    "@types/node": "7.0.0",
    "angular2-template-loader": "^0.6.0",
    "angular-router-loader": "^0.5.0",
    "awesome-typescript-loader": "^2.2.4",
    "clean-webpack-plugin": "^0.1.15",
    "concurrently": "^3.1.0",
    "copy-webpack-plugin": "^4.0.1",
    "css-loader": "^0.26.1",
    "file-loader": "^0.9.0",
    "html-webpack-plugin": "^2.26.0",
    "jquery": "^2.2.0",
    "json-loader": "^0.5.4",
    "node-sass": "^4.3.0",
    "raw-loader": "^0.5.1",
    "rimraf": "^2.5.4",
    "sass-loader": "^4.1.1",
    "source-map-loader": "^0.1.6",
    "style-loader": "^0.13.1",
    "ts-helpers": "^1.1.2",
    "tslint": "^4.3.1",
    "tslint-loader": "^3.3.0",
    "typescript": "2.0.3",
    "url-loader": "^0.5.7",
    "webpack": "^2.2.0-rc.4",
    "webpack-dev-server": "^1.16.2"
  "-vs-binding": {
    "ProjectOpened": [


var path = require('path');

var webpack = require('webpack');

var HtmlWebpackPlugin = require('html-webpack-plugin');
var CopyWebpackPlugin = require('copy-webpack-plugin');
var CleanWebpackPlugin = require('clean-webpack-plugin');
var helpers = require('./webpack.helpers');

console.log('@@@@@@@@@ USING PRODUCTION @@@@@@@@@@@@@@@');

module.exports = {

    entry: {
        'vendor': './angular2App/vendor.ts',
        'polyfills': './angular2App/polyfills.ts',
        'app': './angular2App/main-aot.ts' // AoT compilation

    output: {
        path: './wwwroot/',
        filename: 'dist/[name].[hash].bundle.js',
        chunkFilename: 'dist/[id].[hash].chunk.js',
        publicPath: '/'

    resolve: {
        extensions: ['.ts', '.js', '.json', '.css', '.scss', '.html']

    devServer: {
        historyApiFallback: true,
        stats: 'minimal',
        outputPath: path.join(__dirname, 'wwwroot/')

    module: {
        rules: [
                test: /\.ts$/,
                loaders: [
                test: /\.(png|jpg|gif|woff|woff2|ttf|svg|eot)$/,
                loader: 'file-loader?name=assets/[name]-[hash:6].[ext]'
                test: /favicon.ico$/,
                loader: 'file-loader?name=/[name].[ext]'
                test: /\.css$/,
                loader: 'style-loader!css-loader'
                test: /\.scss$/,
                exclude: /node_modules/,
                loaders: ['style-loader', 'css-loader', 'sass-loader']
                test: /\.html$/,
                loader: 'raw-loader'
        exprContextCritical: false

    plugins: [
        new CleanWebpackPlugin(
        new webpack.NoEmitOnErrorsPlugin(),
        new webpack.optimize.UglifyJsPlugin({
            compress: {
                warnings: false
            output: {
                comments: false
            sourceMap: false
        new webpack.optimize.CommonsChunkPlugin(
                name: ['vendor', 'polyfills']

        new HtmlWebpackPlugin({
            filename: 'index.html',
            inject: 'body',
            template: 'angular2App/index.html'

        new CopyWebpackPlugin([
            { from: './angular2App/images/*.*', to: 'assets/', flatten: true }



  1. Really terrific work. Thanks!

  2. ambarki youssef · · Reply

    Very interesting example that works.
    Thank you very much!!!!
    I am a little confused because a lot of tools are used.
    I wish to customize bootstrap by modifying the variable variables variable.less.
    Can you tell me how I can do it?

    1. thanks, which variable?

      main.ts is the bootstrap for dev, main-aot.ts is the production bootstrap.

      Greetings Damien

  3. What do you think about this yeoman generator: generator-aspnetcore-spa which is developed by Steve Sanderson from the ASP.NET team:

    1. Steve is doing great work here.

Leave a Reply

Fill in your details below or click an icon to log in: Logo

You are commenting using your account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: