Converting My Angular 1 Application to TypeScript

Posted by Gjermund Bjaanes on March 23, 2016

With the soon-ish release of Angular 2, it made sense to start upgrading Angular 1 apps to use TypeScript.

I have an Angular 1.x app called “Extreme Results”, which I want to upgrade to Angular 2 at some point. All the code mentioned in this post can be found in the GitHub repo for that app:

https://github.com/bjaanes/ExtremeResults-WebApp

If you want to know more about the app itself, and what it does, head over to: Extreme Results - A digital solution for Agile Results

 

Typescript logo

The Angular teams upgrade guide says that one of the steps to prepare for an update to Angular 2 is to migrate your app to TypeScript.

For this migration I have focused on the build steps, not actually writing anything in TypeScript specifically. This works out, because TypeScript is a super-set of JavaScript. All I’ve had to do to my source files was to rename them from fileName.js to fileName.ts.

The challenge in this migration was getting the build steps up and running for TypeScript compilation.

 

So what actually needs to still work after migration?

  • Development environement
    • I can still start a development server which automatically compiles and gets everything ready every time I save a file
    • No extra steps for development is crucial. It should not be more complicated to code, just because I use TypeScript!
  • Distribution builds
    • Minification and everything compile with one simple command. It shall be no harder to create a proper build!
  • Unit tests with coverage
    • This turned out the be the most difficult part to get right
    • Getting the tests to run properly was not that difficult, but coverage was not quite as easy as it required sourcemaps and an extra step before working.

 

Now that we know what needs to work, lets get down to business!

 

Turn every file into a TypeScript file

This was the easy part. Just rename every single file from fileName.js to fileName.ts.

I will later change them to actually use TypeScript functionality. Right now, the “TypeScript” provides no value, but that will change once I start using the features it provides.

If you see any errors while compiling later, you can safely just ignore them for now. We will fix that another time. Right now we will just focus on getting the build processes working with TypeScript.

If I wanted to, I could have compiled everything manually, but I need it integrated into the build process I already have. Which brings me to the next step.

 

Bundles

I use a gulp plugin called gulp-bundle-assets to bundle all my files, which is used in the development environment as well as the production builds.

It basically bundles all javascript files together into smaller files (my own + 3rd party libraries and such).

 

For this to work, I needed to add an extra step in the build processs to compile all my TypeScript files into JavaScript.

I used a gulp plugin called gulp-typescript.

 

I installed typescript and gulp-typescript with npm:

npm install --save-dev typescript gulp-typescript

 

I then added a the gulp task to do the actually compilation:

gulp.task('typescript', ['clean'], function () {
    return gulp.src('src/app/**/*.ts')
        .pipe(typescript({
            target:'es5'
        }))
        .js
        .pipe(gulp.dest('tmp/typescript'));
    });

This compiles every .ts file into JavaScript (specifically ES5, which works on all browsers) and saves them to ‘tmp/typescript’.

 

And then configured the gulp task to run before the bundle task.

gulp.task('bundle', ['clean', 'templates', 'typescript'], function() {

 

I also had to update my bundle configuration file to look for my compiled files instead of the normal JavaScript files (since they no longer exist).

scripts: [
    './tmp/typescript/app.js',
    './tmp/typescript/**/*.module.js',
    './tmp/typescript/**/*.js'
],

 

This was actually everything that was required for development and distribution builds to work. The bundle task picks up all the compiled files and bundles them just like before.

If you don’t use bundles, you have to tweak this to make sense for you. Perhaps you have to point your development server to the compiled output folder, or something similar.

It should hopefully require little more effort than this, but you might have to find a solution that fits your particular build process.

 

Unit tests

The first step needed for unit tests to run was to point karma.conf.js to my compiled files, instead of the regular JavaScript files (which again, don’t really exist anymore).

This also means that you have to build before running tests.

If you are using Gulp or Grunt to start your tests, then you just have to make sure you perform the TypeScript compilation before you fire up Karma.

 

So in karma.conf.js, all I had to do was:

files: [
    ...,
    'tmp/typescript/**/*.module.js',
    'tmp/typescript/**/*.js',
]

 

The tests now load the compiled files instead, and the tests run like they should. Huzzah!

 

Everything works! Except for one thing…

 

Coverage

I used to get a proper coverage report right of the bat from Karma. I now had to change this a little bit, since the coverage needs to be on the TypeScript files, not the compiled JavaScript files.

To do this, I needed to do several things.

 

Create sourcemaps

When compiling TypeScript, you need to generate sourcemaps as well. This is farily easy and just requires a new gulp plugin that does everything for you.

I used gulp-sourcemaps and changed the typescript task to do the following:

gulp.task('typescript', ['clean'], function () {
    return gulp.src('src/app/**/*.ts')
        .pipe(sourcemaps.init())
        .pipe(typescript({
            target:'es5'
        }))
        .js
        .pipe(sourcemaps.write({sourceRoot: __dirname + '/src/app'}))
        .pipe(gulp.dest('tmp/typescript'));
    });

 

Notice that it does a sourcemaps.init() before starting to compile the TypeScript files.

It then writes the sourcemaps to those files with a very particular sourceRoot.

The sourceRoot option in sourcemaps.write makes sure that we find the sources from where the compiled output comes from.

This is needed because I output the compiled files to a different folder (tmp/typescript) than the source files (which can be found in src/app).

 

Create a coverage report for the tests

I will assume you have coverage reports already and have installed karma-coverage as well as configured it.

If not, take a look at my previous blog post about coverage: Add coverage to your Angular project

 

Before we can create a proper coverage report, we need to create one for the tests, which ran against the JavaScript files. This is done in Karma.

I still use karma-coverage to do this, but I now just use the output as a middle step to generate a proper report using the sourcemaps later.

 

The karma configuration file now needs these changes:

preprocessors: {
     'tmp/typescript/**/*.js': ['coverage']
}

 

coverageReporter: {
    type : 'json',
    subdir: '.',
    dir : 'coverage/'
}

 

I now run the coverage against the compiled TypeScript files (in my tmp/typescript folder) and generate a JSON coverage report for those. This report is what I’ll be using in the next step.

 

Create a proper coverage report for the TypeScript files using sourcemaps

To create the proper report, I used a tool called remap-istanbul. I have not integrated this tool into any build processes other than creating an npm script for it.

The reason for this, is that I don’t really need the coverage report all the time.

I create them when I need them, and more specifically, I create them in Travis (my Continous Integration system) and send them to codecov to get a nice coverage badge in my GitHub README file.

 

The following npm script is all that is needed to generate a proper report:

"generateLcov": "remap-istanbul -b src/app/ -i coverage/coverage-final.json -o coverage/lcov.info -t lcovonly"

 

 

All I do is to create the coverage report is in my .travis.yml file:

- npm run citest
- npm run generateLcov

 

I could, and probably will create some HTML reports on the fly to get the most out of the coverage report:

Using the test coverage report

 

For more information on how to use remap-istanbul, take a look at:

https://github.com/SitePen/remap-istanbul

 

What now?

Well, now I have TypeScript, but with no benefits. No real value has been added to anything just yet. So that is the next step for the app. Actually using TypeScript to get some value.

But at least we CAN start writing TypeScript now.


Follow me on Twitter: @gjermundbjaanes