How I Migrated an Existing AngularJs Project from JavaScript to TypeScript (Part 2 of 2)
Posted on: 2017-10-05
In part 1 of how to migrate an existing JavaScript project to TypeScript, we saw that we can have only JavaScript files and use TypeScript as transpiler with almost no effort. This gave us the capability to transpile into a different version of EcmaScript, but also open the door to bringing typed object and primitives in an existing project. In this phase 2, we will change TypeScript to allows .ts file and benefit of strongly typed parameters and objects.
As a reminder, the project that I was migrating to TypeScript was an AngularJS 1.5, with RequireJs as AMD loader that was using Grunt to workout the files. In part 1, we configured TypeScript to read only .js file. Now, we need to read both, but also to use AMD module. This wasn't required in the first place and still isn't because of the way the actual project was built -- it was explicitly using the requireJs library in all files. The tsconfig.json also specify what we want to include: .ts file. I added the "checkjs" to report errors in JavaScript file. This is not required, but since it's a phase 2, I desired to kick the notch up more in term of validation. However, the checkjs is limited since it relies on inference or JsDoc comment style which wasn't used in the project that I was converting.
Few changes are required in term of libraries. We need to bring some definition files for AngularJS, RequireJs, JQuery and Angular-ui-router and also the Angular library. This can be done easily with NPM, here is the JSON output.
"angular": "^1.5.11",
"@types/angular": "^1.5.23",
"@types/angular-ui-router": "^1.1.37",
"@types/jquery": "^3.2.10",
Minor changes was required in the Gruntfile.js because if we recall, we were using the tsconfig.json file to do the heavy lifting. The main change was to bring .ts file into the final distribution folder since we want to debug the .ts with the map file.
From there, I chose some JavaScript files, and changed the extension of the file to .ts. I started with the entry JavaScript file and went deeper and deeper. Without modifying anything, it was working. But, it wasn't leveraging the typed aspect of TypeScript. That is the reason, I started to change all requirejs type. When "angular" was injected, I added to the parameter the type. Almost every file needed to have ng.IAngularStatic
angular: ng.IAngularStatic
The definition file are well exhaustive and provides everything from compileProvider, to scsDelegateProvider, to httpProvider and so on. With the power of TypeScript, it's a matter of typing "ng." and wait for Intellisense to come up suggesting type from AngularJS' definition file.
Finally, I went into a situation where this project was using another of our project that was written in JavaScript. No definition file was available. However, I wanted to have the return object to be typed. I ended up creating a folder, in the project I am converting, that I called "CustomDefinitionFiles" and I added the missing type and used it. Be sure to have the extension ".d.ts" and you will be able to use it in your project. While it's better to have the definition files provided by the library, this give us a quick way to have typed content without forcing anything.
At the end of these two phases, I was able to show that an existing JavaScript project can be converted to use TypeScript without any impact on the actual code. I demonstrate that it's possible to live in a hybrid mode where JavaScript and TypeScript co-exist. Finally, I demonstrated the power of type by not having a system that is getting type progressively without creating any burden for developers. No one in the team was affected by this transformation and in long term, the code base will get in better quality, easier to read and maintain.