ASP.Net Core + Angular 2 Build automation

In this blog post, some of the important steps for the automation of ASP.Net Core Angular 2 application are explained. The explained practices might differ from your case (dependent on your requirements and constrains); At the same time this post could help you understand different techniques you might want to use for your Angular 2 project.

Setup Visual Studio Project including Angular files

Assuming that you are building an Angular 2 web-application with .Net back-end and you use Visual Studio Team Foundation Server for keeping your code centralized we first explain in short how to set up your application.

The steps are as follows:

  • Install Node.js
  • Install Visual Studio 2015 Update 3
  • Configure External Web tools
  • Install TypeScript 2 for Visual Studio 2015
  • Download the QuickStart files from Angular.io
  • Create the Visual Studio ASP.NET project
  • Copy the QuickStart files into the ASP.NET project folder
  • Restore required packages
  • Build and run the app

More information:

https://angular.io/docs/ts/latest/cookbook/visual-studio-2015.html#!#prereq2

Consider using this template for creating ASP.Net Core + Angular 2 application:

http://blog.stevensanderson.com/2016/10/04/angular2-template-for-visual-studio/

Gulp Installation

In order to automate some of the most common tasks that are required to run Angular 2 applications we could use JavaScript task runners. Two most well-known JavaScript task runners are Grunt and Gulp. Gulp is a streaming build system, by using node’s streams file manipulation is all done in memory, and a file isn’t written until you tell it to do so. Gulp prefers code over configuration. Being that your tasks are written in code, gulp feels more like a build framework, giving you the tools to create tasks that fit your specific needs.

In this tutorial, we have chosen for Gulp, if you need to play around with Gulp in order to understand it’s functionality please check the next links:

https://css-tricks.com/gulp-for-beginners/

https://scotch.io/tutorials/automate-your-tasks-easily-with-gulp-js

These links are a good starting point if you would like to run and test Gulp separately from Visual Studio. For instance, if your Gulp file contains errors, sometimes it is easier to test it outside Visual Studio, because you are sure that Visual Studio has no effect on it. In such a case always make sure you close Visual Studio. Some Plugins in Visual Studio can have effect on your files and projects, which might drive you crazy if you are looking for a fix.

NPM Package installation

In the root of your Angular application you will find package.json file (otherwise add this your self). Make sure you add at least these packages (take the latest versions, shown versions are just as example):

  • “del”: “2.2.2”,
  • “gulp”: “3.9.1”,
  • “gulp-concat-css”: “^2.3.0”,
  • “gulp-cssmin”: “0.1.7”,
  • “gulp-newer”: “1.3.0”,
  • “gulp-rename”: “1.2.2”,
  • “gulp-sass”: “3.1.0”,
  • “gulp-sourcemaps”: “2.4.0”,
  • “gulp-typescript”: “3.1.4”,
  • “gulp-uglify”: “2.0.0”,
  • “gulp-watch”: “4.3.11”,
  • “merge-stream”: “1.0.1”,
  • “lodash”: “4.17.4”,
  • “yargs”: “6.6.0”
  • “run-sequence”: “^1.2.2”

You will use these packages in your Gulp automation file.

 

Gulp Automation Configuration

Create a file called gulpfile.js in the root of your project en copy next lines into that file:


// *************************************************************************
// Variables
// *************************************************************************

'use strict';

// Reference libraries
var _ = require("lodash"),
 del = require("del"),
 merge = require("merge-stream"),
 gulp = require("gulp"),
 logger = require('gulp-logger'),
 watch = require('gulp-watch'),
 uglify = require("gulp-uglify"),
 cssmin = require("gulp-cssmin"),
 rename = require("gulp-rename"),
 newer = require("gulp-newer"),
 sass = require("gulp-sass"),
 ts = require("gulp-typescript"),
 sourcemaps = require('gulp-sourcemaps'),
 argv = require('yargs').argv,
 exec = require('child_process').exec,
 concatCss = require("gulp-concat-css"),
 runSequence = require('run-sequence');

var isTFSbuild = (argv.tfs === undefined) ? false : true;

var paths = {
 nodeModules: "./node_modules/",
 clientDeps: "./wwwroot/lib/",
 wwwroot: "./wwwroot/"
};

// Libraries used in web app

var commonClientLibraries = [
 "core-js",
 "zone.js"
];

var devLibraries = [
 "reflect-metadata",
 "systemjs",
 "@angular",
 "rxjs"
];

var externalLibraries = [
 "foundation-sites"
];

Step 1: Copy libraries:
First you will need to copy all files and folders from the node_modules folder to the right folder where they are used by Angular. This way you can skip the folders, that are not necessary to run the application.

gulp.task("before-build", ["copy-libraries"]);
// *************************************************************************
//  Copy libraries from node_modules to ./wwwroot/lib
// *************************************************************************
gulp.task("copy-libraries",
    function () {
        var mergeStream = merge();

        // add common libraries
        for (var i = 0; i < commonClientLibraries.length; i++) {
            mergeStream.add(
                gulp.src([paths.nodeModules + commonClientLibraries[i] + "/**/*", "!" + paths.nodeModules + commonClientLibraries[i] + "/**/*tsconfig.json"])
                    .pipe(newer(paths.clientDeps + commonClientLibraries[i]))
                    .pipe(gulp.dest(paths.clientDeps + commonClientLibraries[i]))
            );
        }

        return mergeStream;
    });

Step 2: Typescript compilation:

It is necessary to compile all the typescript files that your app folder contains. Since you don’t want to compile the typescript files included in the nodemodules folders (npm packages you have downloaded) you exclude those.

Remark: It is important in which order the provided tasks are called and finished. For instance in the code below  typescript-clean-map and typescript-clean-js tasks are finished first before the typescript-compile-jit is executed.  This is because you first want to clean any existing maps or javascript files before creating new ones.

gulp.task("after-build", ["typescript-compile-jit", "create-styling-files"]);
// compiles all typescripts in wwwroot, exclude compiling nodemodules 
gulp.task("typescript-compile-jit", ["-typescript-clean-map", "-typescript-clean-js"], function () {
    var angularTypeScriptProject = ts.createProject('tsconfig.json');
    var angularTypeScriptProjectResult = angularTypeScriptProject.src()
        .pipe(sourcemaps.init())
        .pipe(angularTypeScriptProject())
        .pipe(sourcemaps.write('.'))
        .pipe(logger({
            before: 'Compiling typescripts in ' + tsAngular,
            display: 'abs'
        }));
    return angularTypeScriptProjectResult.pipe(gulp.dest(paths.wwwroot));
});

gulp.task("-typescript-clean-js", function () {
    _.forEach(jsPaths, function (jsPath, _) {
        del(paths.wwwroot + jsPath);
    });
});

gulp.task("-typescript-clean-map", function () {
    _.forEach(jsMapPaths, function (mapPath, _) {
        del(paths.wwwroot + mapPath);
    });
});

Step 3: Create Styling files:

Creating styling files requires the two cleaning tasks to be executed first before it can be executed.


gulp.task("create-styling-files", ['-clean-min-css-files', '-clean-css-files'], function () {
    runSequence('-compile-sass-themes', '-bundle-css-files');
});

var themes = ["theme1", "theme2"];
gulp.task("-compile-sass-themes", function () {
    var mergeStream = merge();

    _.forEach(themes, function (theme, _) {
        mergeStream.add(
            // Foundation
            gulp.src(paths.wwwroot + "css/foundation.scss")
                .pipe(sass({
                    includePaths: [
                        paths.wwwroot + "css/themes/" + theme + "/*.scss",
                        paths.wwwroot + "lib/foundation-sites/scss/*.scss"
                    ]
                }).on("error", sass.logError))
                .pipe(gulp.dest(paths.wwwroot + "css/themes/" + theme))
        );

        mergeStream.add(
            // Site
            gulp.src(paths.wwwroot + "css/site.scss")
                .pipe(sass({
                    includePaths: [
                        paths.wwwroot + "css/themes/" + theme + "/*.scss"
                    ]
                }).on("error", sass.logError))
                .pipe(gulp.dest(paths.wwwroot + "css/themes/" + theme))
        );
    });
    return mergeStream;
});

gulp.task("-clean-css-files", function () {
    del(paths.wwwroot + "css/themes/**/*.css");
});

gulp.task("-clean-min-css-files", function () {
    return del(paths.wwwroot + "css/**/*.min.css");
});

gulp.task("-bundle-css-files", function () {
    var normalizeCss = paths.wwwroot + "css/normalize.css";
    _.forEach(themes, function (theme, _) {
        var themePath = paths.wwwroot + "css/themes/" + theme;
        var themeFoundationCss = themePath + "/foundation.css";
        var themeSiteCss = themePath + "/site.css";
        gulp.src([normalizeCss, themeFoundationCss, themeSiteCss])
            .pipe(concatCss("workflow-app.css"))
            .pipe(cssmin())
            .pipe(rename({ extname: ".min.css" }))
            .pipe(gulp.dest(themePath));
    });
});

Wrap-up

In this article, I have explained some steps that you might want to take for creating your own ASP.Net Core Angular application. Also, some example gulp tasks are given so that you can use them as inspiration for your own compilation and bundling automation. One last tip might be to do each step one by one, check the results and follow with the next one. In order to keep the configuration as simple as possible some parts of the configuration are left out. Good luck with coding!