How to setup TypeScript and ASP.NET 5 DNX with grunt

Aug 2, 2015

I didn’t find a lot of documentation on how to setup TypeScript in an ASP.NET 5 project. This is how I did it with grunt and ASP.NET 5 Beta 6. If you know of a better method, comments welcome.

The TypeScripts are located in wwwroot/typescript. Intitially I had them in $\Scripts and used grunt to copy them to wwwroot but then Visual Studio sees two copies of every *.ts file and the TypeScript compiler complains about duplicates.

Scripts folder

We could keep the TypeScript in $\Scripts and copy the *.js to wwwroot with grunt, but then we lose the benefit of sourcemaps and stepping through TypeScript in the browser.

Project Open Task

First things first, lets ensure the tsd package manager is firing when the project opens and downloads any missing type definitions. Add a reference to grunt.loadNpmTasks("grunt-tsd"); in your gruntfile.js and a matching task:

    tsd: {
        refresh: {
            options: {
                command: 'reinstall',
                latest: false,
                config: 'tsd.json',
                // experimental: options to pass to tsd.API
                opts: {
                    // props from tsd.Options
                }
            }
        }
    },

Add a new tsd.json to your project root. It might look like this where I am referencing Angular.

{
  "version": "v4",
  "repo": "borisyankov/DefinitelyTyped",
  "ref": "master",
  "path": "wwwroot/typescript/typings",
  "bundle": "wwwroot/typescript/typings/tsd.d.ts",
  "installed": {
    "angularjs/angular.d.ts": {
      "commit": "4df20c9706ce6ca27137617770b57f3a0d3f9689"
    },
}

Now you can use the tsd console application to install type definitions. I ignore these *.d.ts files in git.

Compile time tasks

Add a grunt.loadNpmTasks("grunt-contrib-clean"); then make sure our javascript is kept nice and clean. I guess this is optional.

  clean: {
        typescriptJS: ["wwwroot/typescript/**/.js"]
        ,appJS: ["wwwroot/assets/app*.js"]
    },

Next we add a grunt.loadNpmTasks("grunt-typescript"); task:

typescript: {
        webui: {
            src: ['wwwroot/typescript/**/*.ts'],
            dest: 'wwwroot/assets/app-ts-full.js',
            options: {
                module: 'amd', //or commonjs 
                target: 'es5', //or es3
                sourceMap: true,
                declaration: false
            }
        }
    },

This is compiling and concatenating the TypeScript into a single JavaScript file: wwwroot/assets/app-ts-full-js which we can reference in our Shared/Layout.cshtml.

In the TypeScript options we set sourceMap=true so that the browser can use load them and allow us to step through TypeScript in the browser with F12 tools.

This does mean a double compile since Visual Studio compiles TypeScript but it doesn’t take long on my solution. I wasn’t sure whether concat would honour the individual sourcefiles, so that is another reason I did it this way.

At this point we now have TypeScript, JavaScript and sourcemaps being compiled and created off wwwroot on every project build. What if we want to simply edit a TypeScript file and hit F5 in the browser?

Another Project Open Task

Lets watch the TypeScript source folder with grunt.loadNpmTasks('grunt-contrib-watch');

watch: {
    typescript: {
        files: ['wwwroot/typescript/**/*.ts'],
        tasks: ['typeScript']
    }
}

One final trick is that you will note the zzApp folder in my folder structure (see screenshot above). This holds app.ts which forces my angular modules to register last otherwise I had runtime errors in the browser when the app is trying to load modules that are yet to be parsed in the JavaScript file.

TLDR - Complete gruntfile.js

Pulling it all together, here is the final gruntfile.js. Notice how the first line is binding tasks to Visual Studio events. In the real world, you probably also want to copy your bower references to wwwroot, which you also see below.

/// <binding AfterBuild='postBuild' ProjectOpened='projectOpen' />
/*
This file in the main entry point for defining grunt tasks and using grunt plugins.
Click here to learn more. http://go.microsoft.com/fwlink/?LinkID=513275&clcid=0x409
*/
module.exports = function(grunt) {
grunt.loadNpmTasks('grunt-contrib-watch');
grunt.loadNpmTasks('grunt-contrib-copy');
grunt.loadNpmTasks("grunt-typescript");
grunt.loadNpmTasks("grunt-tsd");

grunt.initConfig({

    typescript: {
        webui: {
            src: ['wwwroot/typescript/**/*.ts'],
            dest: 'wwwroot/assets/app-ts-full.js',
            options: {
                module: 'amd', //or commonjs 
                target: 'es5', //or es3
                sourceMap: true,
                declaration: false
            }
        }
    },

    tsd: {
        refresh: {
            options: {
                command: 'reinstall',
                latest: false,
                config: 'tsd.json',
                // experimental: options to pass to tsd.API
                opts: {
                    // props from tsd.Options                    
                }
            }
        }
    },
copy: {
        bowerDist: {
            src: 'bower_components/**/dist/*',
            dest: 'wwwroot/assets/lib/',
            expand: true,
            flatten: true
        }
    },

    watch: {
        typescript: {
            files: ['wwwroot/typescript/**/*.ts'],
            tasks: ['typeScript']
        }
    }
});

grunt.registerTask('postBuild', ['typescript']);
grunt.registerTask('projectOpen', ['tsd', 'copy', 'watch']);
};

Now with our JavaScript sourceMaps we can browse to TypeScript source and hit breakpoints in our application. Happy days!

Scripts folder

Lastly, if you want to minify your app-ts-full.js, there is no bundling anymore. Just throw in an uglify grunt task to make a app-ts-min.js.