DEV Community

Caleb Collins-Parks for 15Five Engineering Blog

Posted on

100,000 lines of Typescript: A data-backed retrospective

PROLOGUE:

If you're more interested in the results of the migration than the journey itself, scroll on down to DATA & RESULTS.

At first, types seemed tedious, and not worth the effort. I remember thinking, back in college, why was I specifying that a string (as indicated by the name foo_str) was a string (as indicated by the quotation marks) was a string (as indicated by the type)? Then Typescript came with a revelation. Type inference1! The computer would do the grunt work for you, leaving you free to wander outside and sing and dance and skip and shout joyfully into the sunlight2. I would never use Javascript again.

REGEXES EVERYWHERE

I was using Javascript again.

Or to be more accurate, my coworkers at my new company 15Five were using Javascript. Those poor souls. I nobly volunteered (was assigned a ticket) to save them from their struggle (they were doing okay, really), by migrating the codebase to Typescript (by migrating the codebase to Typescript).

This was my first time doing a migration of this size, and it was a daunting task. There were over 300 files and literally thousands upon thousands of Typescript errors. I knew migrating it by hand, alone, was out of the question, so I checked to see if there were any auto-migration tools. There was one! Yes! It failed to run! Yes! Wait, No!

Unfortunately, that was the only one I found. Other large migrations I read of were done by teams of people with a solid chunk of time dedicated solely to the migration, and the assistance of specialized tools.

Fortunately, we already used React PropTypes, which are somewhat similar to Typescript types. In some cases, very similar. A simple search/replace across the entire project (VSCode made this very easy) sufficed to take care of basic cases like converting PropTypes.string to string. This alone was a massive time saver as I could change ~300 files at once.

But plain text replacement can still be slow, as you have to go through each scenario one by one. With regex you can handle multiple scenarios at once:

PropTypes.(string|number|object|symbol|any).isRequired
Substitute with $1 to replace all the primitive PropTypes at once!

PropTypes.arrayOf\((\w+)\).isRequired
Substitute with $1[] to replace one-line array types.

([^:]+): PropTypes.array,
Substitute with $1?: any[]; to replace bare arrays.

Regexes aren't as powerful as codemods, but they were enough for me. Within a week I had a PR merged that converted the entire react codebase to Typescript.

TSC-SILENT

But I wasn't done. Not by a long shot. Although the PropTypes were in Typescript, and the files were technically Typescript files with a .ts extension, there were still thousands of miscellaneous type errors that polluted a Typescript run. Devs could still have the benefits of Typescript on new code, but on old code it wasn't as powerful. Furthermore, the type errors prevented us from running Typescript in our continuous integration (CI) environment.

Fortunately, I found a tool called tsc-silent, that allowed you to run Typescript while silencing certain types of errors. That way the CI could catch any new errors that appeared, while also giving us a progress report on how many old errors were left. Tsc-silent was highly configurable, letting us silence certain errors in directors of the old codebase while catching all errors in other directories.

Another option I could have done but didn't think of at the time was a snapshot test. By saving Typescript errors to a file and failing CI if the run doesn't match the file, you can catch any new errors introduced and keep track of existing errors. For more details see https://dylanvann.com/incrementally-migrating-to-typescript.

THE LONG GRIND

For a while I tried tackling the remaining Typescript errors by myself. This went slowly, given my limited time and lack of expertise in the codebase. Eventually, I realized I needed subject matter experts, so I created an every-other-week meeting with a few generous volunteers from the frontend team. Thanks to the expertise of the volunteers, we made good headway at first, but as the months went by each type error took longer and longer to solve. We were so close - only 500 errors left! Yet fixing just a couple errors could take an entire hour.

For example, certain errors may involve your code's use of a foreign package, so in order to fix it you need a good knowledge of both your code and the package's API. If the library doesn't have good types then the issue can take a while to untangle. The fix may pretty easy (one PR literally just added <Date>) but knowing how to fix it is a whole other story.

As 2020 neared its end (good riddance) I decided to switch strategies.

TS-MIGRATE

Airbnb open-sourced their automatic JS->TS migration tool, a project aptly called ts-migrate. Although our codebase was already migrated, the feature to automatically add ts-expect-error comments to all existing errors seemed useful. Using that to ignore existing errors would let us Typescript directly in our CI instead of tsc-silent and prevent new errors from creeping in. Running it took a while, but soon tsc produced a clean run! I added it to our CI, and a simple grep allowed us to keep track of the remaining type errors.

- run:
    name: Leftover type errors per file
    command: grep -cR "// @ts-expect-error ts-migrate" your/path/here | grep -v :0
- run:
    name: Number of leftover type errors
    command: grep -R "// @ts-expect-error ts-migrate" your/path/here | wc -l
Enter fullscreen mode Exit fullscreen mode

There's now ~200 errors left, but a lot of them will be gone once we refactor out our usage of Redux. I would like to say we got down to 0 errors, as that would make for a neat and tidy ending to this story, but the real world is not so perfect. When Redux is gone we can go through the remaining errors and decide what is worth fixing.

DATA & RESULTS:

Team size:

  • 11 Frontend engineers in total (not directly involved with TS)
  • 3-4 engineers directly involved in migration
  • 1 Project lead (not included in above)

For context, most of our employees are senior or mid-level coders. We have no interns.

Codebase Size:

  • Jun 19th, 2019: 291 files, 25,866 lines
  • Dec 7th, 2020: 1560 files, 126,816 lines

PR's: 47

Person-hours:
very rough estimates follow

  • Assuming each PR ~= 1 hour, that's 47 hours
  • Plus 10 hours for meetings
  • Plus 5 hours for PR reviews
  • Total: 62 hours

If we had ts-migrate at the start, that might have saved 9 hours or so.

Number of TS Errors Fixed: 3,168

Time:

  • Jun 19th, 2019: Started work
  • Jun 25th, 2019 (6 days): Migrated to TS
  • Sep 30th, 2019 (3 months): TS enforced in CI
  • Dec 7th, 2020 (1.5 years): Vast majority of errors fixed, TS used directly in CI

Note that most of the benefit of the migration was realized within the first few months. This follows the Pareto Principle - 80% of the outcome results from 20% of the work.

Bugs: 0 bugs, thanks to great reviews by Razh!

Survey data:

Although the sample size was too small to be statistically valid, the survey results were very good. Most developers were happy with Typescript. It caught bugs, improved intellisense, and made refactoring easier. The survey did reveal mixed results on making development faster, but that is no surprise. The work it takes to add types somewhat negates the improved development speed from refactoring and intellisense. The benefit you are left with is catching bugs.

SUMMARY:

Converting to Typescript with ts-migrate is a quick and easy win to improve your frontend code. Fixing all Typescript errors on a large codebase, on the other hand, is not easy nor quick. Going through them all can be a slog, so you should maintain inertia by holding type-fixing meetings or hackathons. Certain type errors may be rather difficult to resolve, so you should decide ahead of time the maximum amount of effort you are willing to spend. You can realize the majority of benefit from Typescript without fixing all the errors, so you don't need to worry about fixing everything in one long all-nighter. You can take your time - so why not start today?


  1. I don't mean to suggest Typescript was the first to come about with type inference, just that it was the first to bring it to Javascript at a large scale. 

  2. Or to stay inside and write articles about how free you are 

Top comments (0)