Migration to TypeScript - The Why.

Introduction

Unless you’ve been living under a rock you might have come across TypeScript at some point. It’s made quite a bang in the Javascript world. The buzz it has generated has been for mostly good reasons. TypeScript is an open-source language that builds on JavaScript, one of the world’s most used tools, by adding static type definitions.

Our team recently migrated our codebase from JavaScript to TypeScript. In this blog, I’ll give some context as to what triggered the conversation and key reasons why the conversion to

Background

I had spoken about TypeScript casually with another colleague when we were casually talking about potential topics for my next tech talk. I was down for the idea but there was a catch - I barely knew what TypeScript was 😱 One thing I did know was that I was sick and tired of coming across it everywhere and not taking the time to read up on it. I ran the idea by my manager and next thing you know I was scheduled to give a talk to the team about it.

Sidebar: You do not have to be an expert in a topic to give a tech talk about it. Knowing the basics of your topic - The what, why and how could be enough. As talks sometimes are just to start a conversation, reinforce your understanding or simply to just hone your public speaking skills

So, I was no expert. By the time I was ready to talk about it, I had only used it on really small side projects and even then, it wasn’t used extensively. More so, to grasp the syntax, check out it’s nuances and see how useful it could be.

Reasons why we migrated

Incremental adoption

First off, the fact that adopting TypeScript was not a binary choice was soothing for us. We did not have to go all-in right from the jump. Why? Because every valid JavaScript code is indeed also valid TypeScript and we could massage the configs as we deemed fit. This meant that we could adopt it incrementally. We could pace ourselves, take breaks ( to work on critical bugs and features ) and pop right back in.

Static type analysis

This is usually the main selling point for TypeScript adoption. With TypeScript, you can catch potential bugs at compile time (as opposed to runtime) and patch them before it leaks into production. For example, consider this arbitrary function which takes in an object o, and determines if property count of o is greater than 2. If it is, the function should return the string big, else it should return small

function bigOrSmall(o) {
  if (o.count > 2) {
    return "big";
  } else {
    return "small";
  }
}

On the surface, this looks like a simple function that could cause no harm - But what happens when we call bigOrSmall(1). What does it return? Does it error out?

Ding ding ding! It returns small. If one glosses over this quickly, you might think 1 is indeed less than 2 so the return value is right. But that is not why it returned small. Indeed it returned so because the number 1 is not an object and the condition o.count > 2 was not satisfied hence the else case was evaluated.

38% of bugs at Airbnb could have been prevented by TypeScript according to postmortem analysis

To curb code like this from leaking into the product, a simple type check for the parameter o would have caught this and the call bigOrSmall(1) would have been flagged with an error telling us that the function bigOrSmall indeed takes an object. We could even go a step further and say the type of properties the object takes and if count wasn’t a valid property in that object, it will be flaggaed as well. A simple inline typing of the same function will look like this:

function bigOrSmall(o: { count: number} ) {
  if (o.count > 2) {
    return "big";
  } else {
    return "small";
  }
}

Improved API-driven development

The domain model that drives our UI is a fairly complex one. So, as you can imagine, for someone new entering the codebase, it’s a fairly steep curve to figure out the different puzzle pieces and know exactly they fit together. The nomenclature of the different pieces doesn’t make it trivial either - at least for a newbie coming in.

Our API calls return objects that were just that - objects. Vanilla (Un-typed)JavaScript obviously could not infer what kind of object it was automatically unless you dug deeper into some properties it had. The result? Potential holes that could lead to bugs like the bigOrSmall example. What type of object is this? What static/utils methods are available for this kind of object. Does it make sense to feed this type of data into this React Component? These were some of the questions we felt that TypeScript could help us answer automagically and keep us truly honest throughout the codebase.

Improved developer experience

Our code barely had any documentation. I don’t fault anyone for this. As developers. we are usually trying to churn out code quickly and documentation is the last thing on our minds. Having intuitive names of variables may sometimes just do the trick. If you’ve ever looked at any matured code, more often that there will be situations where some function blocks, for example, are not fully documented. When I joined the team, I found that particular developers were disciplined in using JSDoc to annotate their code. I always said a silent “thank you” 🙏 whenever I hovered over a function and I could get a short blurb (however shabby it was written) of what it’s meant to do and info about the signature - the type of parameters it took and the return type. But in an evolving codebase, while this is praiseworthy, the reality of the situation is that things do change. My perfectly annotated function could be obsolete real fast if all of a sudden we decide to change the signature of the function. The function could now take in a new param or return void instead of a string. Over time, this could compound and lead the reader astray if we don’t dilegently update comments whenever such changes our made. TypeScript is self-documenting in that, it will automatically infer the new types and correctly report the changes if any.

Also, all our developers used VSCode, an IDE developed by Mircosoft - the maintainers of TypeScript! Hence, the tooling provided for TypeScript is phenomenal. Actually, the TypeScript server is always running quietly behind the background even in our JavaScript code. So all we had to do was to rename our extensions from .js to .ts, set some configs in tsconfig.json and now we could easily start type checking. The IntelliSense support allowed for cool things like code completion, hovering / signature information and auto imports straight out of the box. Even though some of these toolings were also present while working with JavaScript, it was great to know that we didn’t need to install any extra plugins to get these features. It even had JSDoc support so we could even still use JSDoc in tandem and write faster comments through code inference.

In the next blog post, I’ll go into the HOW part of the migration to TypeScript. Here, I’ll discuss the execution process and elaborate on a few gotchas we encountered along the way. Stay tuned. 😉