Migrating to TypeScript


Kurt McIntire


In 2018 I led an effort to migrate an existing Javascript codebase to TypeScript. One year after the implementation, I find it to be an overwhelming success. New engineers say that navigating the codebase is easier, and long-time team members say that we have less bugs.

The motivation

When I first started writing Javascript & React, I spent too much time trying to figure out the types of objects. Since I was new to the team and code base, I didn't yet know our various data models and schemas. React prop-types were used in some files, and other times, not. Sometimes the prop types were wrong.

I spent the previous few years working in Swift, which is strongly typed. This means that Swift code will not compile if you try to pass a Dog model object into a function expecting a Cat object. In contrast, using a dynamically typed language, Javascript, felt like coding in the dark.

To identify the input types of a function or class, I added breakpoints and console.log to my code. This is time consuming and repetitive. My productivity slowed. I was frustrated.

The pilot

I did some research into types for Javascript and discovered a few options. TypeScript and Flow were most popular, so I considered them. I proposed that our team try types in a small pilot. The goal of the pilot would be to evaluate if types were useful, while identifying pros and cons. The pilot was scoped to incrementally add types without having to perform a complete re-write of our mobile app.

  • I wrote a few key files in Flow and TypeScript, then documented the process.
  • I showed the files to my team members, and gathered feedback.

After the pilot, as a team, we decided to move forward with a 3-month rollout of TypeScript. We chose TypeScript over Flow because TypeScript had strong developer backing. We liked that Flow didn't actually change our code, since types are added with code comments. But, we felt that the strong developer roadmap for TypeScript trumped the pros of Flow.

The 3-month rollout was planned as follows:

  • All new code was to be written in TypeScript.
  • We would not purposely re-write old Javascript code unless we were already refactoring or fixing a bug in that file.

The goal with these rules was to dive head-first into TypeScript as a team, but not get bogged down in a full codebase re-write. Though tempting to convert the entire project to TypeScript, we needed to maintain strong productivity on feature work. Also, I wanted us to be able to move away from TypeScript if the 3-month rollout was a failure.

After the rollout

Over the next three months, we had a few pain points as we rolled out TypeScript to the whole team. First, I struggled to integrate TypeScript into our custom eslint configuration. I accidentally configured our project so that there were collisions between eslint and TypeScript. Unknowingly, this meant that we shipped code for a week or two that didn't conform to eslint. The ultimate fix was easy, requiring a tweak to our tsconfig file. However, that hiccup showed me that it is important to nail down all configuration setup before embarking on a team-wide pilot like this.

Despite the configuration issue, feedback from the team was positive. All team members said that TypeScript made them more productive. Though adding TypeScript type annotations takes a bit more time than writing pure Javascript, confidence when modifying or interfacing with someone else's code was higher.

In conclusion

  • The benefits of TypeScript are strongest when working with multiple engineers.
  • With TypeScript, we experience fewer bugs because collaborators know what functions expect.
  • With TypeScript, engineers have better productivity because of syntax highlighting and auto-completion inside of VSCode.
  • TypeScript compiles down to Javascript, so the code shipped to our customers is the same as it would have been if we didn't use TypeScript. We never saw user-facing bugs due to TypeScript.
  • TypeScript can be added incrementally to a code base, slowly improving its type coverage. A full refactor or re-write of a code base isn't needed.