With the soon-ish release of Angular 2, it makes 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:
The Angular teams upgrade guide says that one of the preparation steps 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 really had to do for my source files was to rename them from fileName.js to fileName.ts.
The challenge in this migration was to get the build setup to compile the TypeScript files and still have everything work.
So what actually needs to still work after migration?
- Development Environment
- 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 to 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, let's get down to business!
Turn Every File into TypeScript Files
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, TypeScript provides no value, but more on that in a later blog post.
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 used typescript and 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 something called gulp-bundle-assets
to bundle all my files, which is used by the development environment and the distribution builds.
It basically bundles all JavaScript files together into smaller files (my own + 3rd party ones).
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 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 fairly 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 Continuous 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.
I will come back with a follow-up blog post on just that.
But at least we CAN start writing TypeScript now, without having to worry about everything breaking apart.