As you may know, we've had a web UI for TFS for quite some time.  In our TFS 2012 release, we did a pretty major overhaul of our web UI to modernize it - both in look and feel and in architecture.  We moved to REST/Json, and a lot more in-browser Javascript and a lot fewer Ajax calls.  The result has been a faster, more responsive and enjoyable experience.  The result has also been a lot more Javascript.  All told, we now have about 80,000 lines of Javascript (actual code, not counting balnk lines, comments, etc - it's about 150,000 if you count that).  This is a fairly sizeable chunk of Javascript - and we've struggled with some of the pain involved in trying to write a pretty sizeable app in a late-bound untyped language.

A couple of months ago (before it was even announced), we made the decision to convert all of our Javascript to Typescript.  We finally got around to doing it a few weeks ago and this week I was trying to assess what measurable benefits we got for it.  The first way I've looked at it is how many bugs did we find in the code that were uncovered just by getting Typescript compiler errors after the conversion.  If the premise that large Javascript programs are difficult to get right and hard to validate and Typescript is a good tool for helping write more "correct" Javascript holds, you'd expect to find some bugs in a large code base you migrated.  And sure enough, we did...

Now, to be clear, this wasn't some pile of spaghetti, hacked up Javascript to start with.  It was Javascript that was heavily tested - both with unit tests and with functional tests.  It was JSLint clean.  It was code that we felt we'd done everything reasonable to ensure it was the highest quality, production code.  And yet, the conversion helped find 13 bugs that we did not know about.  Just so you can get a flavor of the kinds of issues Typescript helped find, here's the list of bugs:

  • Bug 987091:TFS.TestManagement.Controls.debug.js incorrect call to RegExp results in no matches ever
  • Bug 986991:Call to baseConstructor missing "this" parameter in multiple places
  • Bug 986997:Reference to undefined property of ActionManager puts this action worker at the beginning not the end - TFS.Agile.debug.js
  • Bug 987103:Referenced to undefined location property should be path() in TFS.WorkItemTracking.debug.js
  • Bug 987016:Unbinding of nonexisting functions in TFS.TestManagement.Controls.debug.js - refactoring job gone bad?
  • Bug 987071:RichContentTooltip _setPosition calls a non-existing function - looks like casing typo
  • Bug 987080:TFS.UI.Controls.Grids.debug.js _onToggle calls this._addSelection with mismatched parameters
  • Bug 987086:_beginCopySelection in TFS.UI.Controls.Grids.debug.js references nonexisting property to determine if there is a selection
  • Bug 986993:Missing 'this' parameter in call to base constructor in TFS.Agile.Boards.debug.js
  • Bug 987104:Calls to Diag.assert* passing only one parameter when two are required
  • Bug 986987:Missing resource string values in Area Iterations dialog
  • Bug 987054:Incorrect resource reference in TFS.Build.debug.js
  • Bug 987101:Spelling errors in resource references in TFS.WorkItemTracking.Controls.Query.debug.js

The approach...

As we looked at how to approach the effort of migrating our code, we talked to a few other team at Microsoft who had adopted Typescript before us.  One was Erich Gamma's team.  Erich estimated that manual conversion, to get fully annoted Typescript, one class/function/line at a time could be done at a rate of 300 or so lines per hour.  Of course, all valid Javascript is valid Typescript so you can just change the file extension and be compiling it but if you want all the benefits you will want to take advantage of some type annotations and that's the manual part.  80,000 lines, at 300 lines per hour was a daunting proposition.  So we decided to invest in a tool to help with the process.  Thankfully, we had been pretty rigorous in our approach.  We used Javadoc fairly consistently to document our APIs.  We used consistent patterns for our classes and modules, etc.

All told, it took us (1 dev) less than a week to write a tool (in Typescript, of course, :)) that would recognize Javadoc and the rest of our patterns and convert them to the corresponding Typescript constructs.  It took about another week to run the tool, tweak our Javadoc comments (like filling in some that had been missed), update our build process, test the conversion, etc.  Of course there's more we can do with Typescript.  For instance, we didn't have any previous recognizable pattern for interface contracts - so there was nothing for the tool to use to generate the Typescript constructs.  Over time, we'll be going through by hand, as we have reason to revisit modules and further tightening up the Typescript.  I expect we'll find more issues that we don't know about now.

Overall, we've been super happy with the result.  We feel like we're going to be more productive with better tooling experiences like Intellisense, the code will be better structured, more maintainable and ultimately higher quality.  It's been a very good experience and if you've got a sizeable Javascript codebase, I encourage you to give it a try.  You may be surprised at the bugs you find in your code even if you've worked super hard already to make sure there aren't any.

Brian