Developing In Real Time

How We Completed a (Partial) TypeScript Migration In Six Months

Strategies for orchestrating successful refactoring projects without postponing development

If you develop applications for the web, chances are that you or someone on your team works with JavaScript. Today, JavaScript is the most commonly used programming language. But JavaScript has a notoriously arcane type system, and no static type-checking, which means that large JavaScript codebases can become unwieldy and bug-prone over time.

‘Types’ are the low-level sorts of data that programs work with, such as booleans, numbers, and functions. Weakly-typed languages can be especially forbidding to new developers, as the application’s internal API is not self-documenting and there is no compiler to catch novice mistakes. But this is exactly the position many companies find themselves in, Abacus being no exception.

TypeScript is not.

Last year, we began a small refactoring project to see if TypeScript could solve some of these issues for us. Six months later, we had moved all our React-based code and all new front-end development to TypeScript, without delaying or postponing feature development. This is how we did it.

What is TypeScript?

TypeScript is a ‘typed superset’ of JavaScript. That means that all JavaScript code is semantically valid TypeScript, which sets it apart from languages like CoffeeScript, Elm, or Reason. Compared to these, TypeScript has a much lower barrier to entry, though it does still have its own learning curve (especially for developers who lack experience in other typed languages). The creators of TypeScript wanted to keep most of the flavor and flexibility of JavaScript while delivering the rigor and reliability of a statically typed, compiled language.

TypeScript’s website describes it as “JavaScript that scales.” With TypeScript, a slew of common developer mistakes (like improperly referenced functions and mis-typed variables) are caught before the code is ever run. Errors are thrown at build-time, and even communicated while writing. Complicated application states can be locked into strict interfaces. APIs can be made more exact, conventions can be enforced, and engineers can develop with greater confidence as they learn to trust the compiler. In short, TypeScript empowers teams to build and maintain complex systems with greater safety and reliability.

As the development team at Abacus matured, we came to favor more explicit conventions even if it meant more verbose code. Our application is built on Node, so JavaScript is at the root of almost everything we do. TypeScript seemed like a natural fit. But it’s easy enough to recommend a new system; actually conducting a migration is difficult and time-consuming.

Planning our migration

We had three major goals for our migration. First and foremost, we needed to evaluate the technology not just by its merits on paper but as a practical component of our application. Second, we didn’t want to dramatically change our build process before we’d had a chance to vet the technology. Third, we wanted to establish clear boundaries around what we were refactoring and what we weren’t, to minimize context switching and scope the project. For bonus points, we wanted to find a way to leverage our existing back-end database models in TypeScript.

A thoughtful high-level analysis of our application’s components helped us address these requirements. On the server, we would need to introduce a new tool to deploy our code. That would mean adding a build step that didn’t exist before. On the front end, we were already using Webpack and Babel (tools for bundling and transpiling JavaScript), so adding TypeScript support would be as simple as installing a new loader package.

As for the web client’s makeup, we had an expansive volume of legacy AngularJS code. But we also had an initiative to write new features in React, and there was much less React code in total. Not only did ‘front end’ not scope the migration very tightly, but we didn’t want to spend effort refactoring AngularJS code to TypeScript when we were moving away from Angular. So that made the decision clear: we would set a goal to refactor all of our existing React components to TypeScript by the end of the year.

As for leveraging our existing data models, that part ended up being easy. We wrote a quick little utility that generated TypeScript interfaces from the models in our ORM (we use Sequelize). And there are established tools for doing something similar with classes in C# or Java.

Hopefully, the principles at work here are clear. A company with many customers, a tight budget, and a small engineering team cannot afford to bog down the development process for months over an uncertain new technology. Scoping the initial migration and minimizing infrastructure changes is a critical first step, as much to make the task achievable as anything else. And that’s discounting the possibility that TypeScript might have proven unstable, or hopelessly slow. A risk-averse team (and all product development teams should be risk-averse) needs to know that the shiny new toy can be discarded if necessary, with minimal sunk cost.

A future with TypeScript

Our migration was successful. We moved all our React code to TypeScript, and all new front-end development is now done in TypeScript. We did this in six months, without delaying any major feature work, just by assigning a few components each week and leaving the most complicated ones for last. Not only is working with TypeScript more satisfying and safe, but it has helped us better map out and discuss technical plans for projects because we can use interfaces to describe common models and patterns.

Obviously, there will be challenges. Adopting new technologies always comes with cost, and after planning comes patience, mentorship, and learning as a community. We had frustrating days of staring at arcane type errors, Googling in vain as we scoured the documentation, and it is important in these moments not to lose sight of the forest for the trees. We time-limited such sessions; if we hit the hour-mark on a single problem, we found a workaround and moved on.

No one enjoys this.

TypeScript catches bugs early for us. It comes with amazing out-of-the-box support in VS Code, and unparalleled intellisense. It generates clean, readable JavaScript for the browser to run, and it empowers strict data modeling in our front-end architectures. Soon, we’ll be discussing adding TypeScript to the back end. But the success of the endeavor is due to the care with which we approached it. The technology and its value proposition were evaluated carefully. A trial implementation was planned and it was achievable in the near-term. And within the scope of that implementation, there was a commitment across the team to seeing it through.

Types can be beautiful.

Migrations are hard. But building a methodology around them doesn’t have to be.

Related Posts