TypeScript build automation in ASP.NET 5 (MVC6) using Gulp

Introduction

ASP.NET 5 introduces some major changes in terms of how we develop applications like no need for project and file association, wwwroot folder, first class support for JSON config files, .NET framework choice, separation of client side and server side package management and more. You can read more on this here.

Above all of these, it supports JavaScript build automation by providing support for the nodejs platform. 

Visual Studio 2015 has in-built task runner to run grunt or gulp tasks, this is great because we don’t have to leave Visual Studio to run these tasks. 

When you create a new ASP.NET 5 Starter Web project, you will get predefined gruntfile to automate most of the frequent tasks you may want to do. I personally prefer Gulp over Grunt.

Now, we will see how we can configure TypeScript build system in ASP.NET 5 MVC 6 project using Gulp.

Gulp package and plugins

First, we will see all the packages we need to setup TypeScript build system

  1. gulp - JavaScript build automation tool
  2. gulp-typescript - TypeScript compiler with incremental compile support
  3. gulp-tslint - TypeScript lint tool
  4. gulp-tslint-stylish - Formats TypeScript lint errors nicely
  5. gulp-sourcemaps - Creates sourcemaps to enable debugging in TypeScript
  6. gulp-inject - Injects JavaScript and CSS into HTML file
  7. gulp-rimraf - Deletes folders and files

Installation

All these are node modules and we need to install these as dev dependencies. I assume you have node installed in your machine, if not, go to nodejs website and follow the instructions.

Open command prompt and navigate to your project folder the follow the steps below to install all the packages you need

1. Run the following command and answer all the questions

# creates package.json
npm init

This will create package.json file which will have all the node packages you depend on.

2. Next, run the following command 

# install packages
npm install gulp gulp-typescript gulp-tslint gulp-tslint-stylish gulp-sourcemaps gulp-inject gulp-rimraf --save-dev

This will install all the packages you need and save them as development dependency in package.json file.

Now we are ready to create our gulpfile.

Gulpfile.js

We will assume the following folder structure for our gulp tasks

wwwroot/app - this is will have all our TypeScript files. This folder may have subfolders as well

wwwroot/js/app - this is where we will place all the compiled JavaScript files

wwwroot/js/lib - this is where we will place all thrid party libraries like AngularJS, jQuery, etc...

Next, import all the packages we installed into gulpfile.js

// Package imports
var gulp = require('gulp');
var typescript = require('gulp-typescript');
var tslint = require('gulp-tslint');
var tslintStyle = require('gulp-tslint-stylish');
var sourcemaps = require('gulp-sourcemaps');
var inject = require('gulp-inject');
var rimraf = require('gulp-rimraf');

Lets create our tasks now

1. Clean

We need a task to clean wwwroot/js/app to clean up old compiled JavaScript files

// clean
gulp.task('clean', function () {
        gulp.src('wwwroot/js/app', { read: false })
            .pipe(rimraf())
});

This task takes a source location and deletes everything in it.

2. TSLint

Just like JSLint, we have TSLint to report mistakes in our TypeScript. The TSLint needs tslint.json file with the lint configuration, you can find a sample one here but you can add/remove any rules. Instead of having the rules in a json file, I created JavaScript based config file tslintConfig.js and kept all the rules in it.

The task created for TSLint looks like this

// TS Lint
var tsLintConfig = require('./tslintConfig');
gulp.task('ts-lint', function () {
        gulp.src('wwwroot/**/*.ts')
            .pipe(tslint(tsLintConfig))
            .pipe(tslint.report(tslintStyle, {
                emitError: false,
                sort: true,
                bell: true
            }));
});

This task also uses gulp-tslint-stylish plugin to produce nicely formatted errors.

3. TS Compile

This task compiles TypeScripts and produces JavaScript with sourcemaps

// TS Compile
gulp.task('ts-compile', ['ts-lint', 'clean'], function () {
        var tsResult = gulp.src('wwwroot/**/*.ts')
                           .pipe(sourcemaps.init())
                           .pipe(typescript());
        tsResult.js
            .pipe(sourcemaps.write('.'))
            .pipe(gulp.dest('wwwroot/js'));
});

This task runs 'ts-lint' and 'clean' before running compile task. The compiled files are placed in wwwroot/js/app folder.

4. Inject

This task is to inject compiled JavaScript files into our main HTML file

// Inject
 gulp.task('inject', ['ts-compile'], function () {
        var target = gulp.src('wwwroot/index.html');
        var sources = gulp.src(['wwwroot/js/app/**/*.js'], { read: false });

target.pipe(inject(sources, { relative: true }))
          .pipe(gulp.dest('wwwroot'));
});

The main html file in wwwroot has the following HTML comment to indicate where the scripts should be injected,

<!--injection comment-->
<!--inject:js-->
<!--endinject-->

This task generates script tag for all the generated JavaScript files in our wwwroot/js/app and places them between start and end comment tags.

5. Watch

Next, we create watch task to run TSLint, Clean, TS Compile and Inject whenever any change in detected in wwwroot/app with *.ts extension

// Watch
gulp.task('ts-watch', function () {
        gulp.watch('wwwroot/**/*.ts', ['inject']);
});

Due to dependent task setups, when we run 'inject' it will trigger 'ts-lint', 'clean' and 'ts-compile' for us.

6. Default task

Finally, we will make 'inject' as default task. This allows us to run inject task when we ask gulp to run the default task

// Default task
gulp.task('default', ['inject']);

That's it. We are now ready to use TypeScript as our client side development language in ASP.NET 5  MVC 6.

I have not created tasks to minify and concatenate compiled JavaScript files. You can add them easily using gulp-uglify and gulp-concat.

 

Update: sourcemaps was not resolving source files correctly for me. It started working after specifying sourceRoot in sourcemaps.write() method as follows 

// ts-compile updated
gulp.task('ts-compile', ['ts-lint', 'clean'], function () {
        var tsResult = gulp.src('wwwroot/**/*.ts')
                           .pipe(sourcemaps.init())
                           .pipe(typescript());
        tsResult.js
            .pipe(sourcemaps.write(".", { sourceRoot: 'wwwroot/app' }))
            .pipe(gulp.dest('wwwroot/js'));
});
comments powered by Disqus