TypeScript has many benefits for teams when it comes to writing neat, comprehensible, and reusable code. It’s no surprise that more than 80% of the front-end code in Wix is written in TypeScript.
This is also why when we started working on a new API for Ricos - the rich content editor component at Wix - we saw a big opportunity in starting to use TypeScript in our project. Ricos is an open-source project by Wix that allows rich content editing capabilities in comments, posts (just like this one) and different pages throughout the Wix platform. When we were making that decision, however, we already had around 50k lines of JavaScript code in our project, and converting to TypeScript seemed pretty ambitious.
I’ll share some insights from our migration process, some are practices we used along the way for prioritization and working efficiently, and some are thoughts we had in retrospect.
Photo by Rick Mason on Unsplash
Why the hassle
As you’re reading this, you might already be familiar with some of the benefits that come with TypeScript. I’ll share some of those that motivated us in making the drastic move.
Code auto-completion for our library - requested by consumers
Making it easier to understand code written by other teammates (or your own code from 6 months ago) - using types to describe functions and classes makes them more readable and comprehensible
Catch bugs in compile-time - type errors, typos, edge cases, etc.
Rich language for describing a well-defined API
Debugging untyped code
In a large monorepo library, tracing errors can be tricky. Imagine a function that adds an image to a post after the image was uploaded.
Let’s say we suspect there’s an issue with the data received in handleFilesAdded - an old function that’s been there long before you came along.
So now the question is - what is data? Going over the function code, we can infer that
data might be an array, but it also might not (line 2).
data has a width property (line 4). What other properties might it have?
To figure out what properties we can expect data to have we’d probably need to trace the function calls or actually run the code. With TypeScript, we’d have a well-defined type like RawImageData to clear things up.
TypeScript has a huge focus on finding errors in compile-time, taking advantage of its structural nature. This is expressed well in the TypeScript design goals written back in 2014, the first of which states:
| 1. Statically identify constructs that are likely to be errors.
This demonstrates how the language was designed to make bugs easier to find from day one.
Setup
To be able to write TS code in your project, you’ll need to install the typescript package and configure your build process to support ts and tsx files. The way this step is implemented can vary greatly, depending on your current project build configuration.
Whether you’re using a standard bundler like Webpack or Rollup, or running Node.js, you’ll find plenty of helpful guides on the web. TypeScript’s Migrating from JavaScript page is a nice start. I do however want to note a few points to consider in your configuration.
1. Hybrid JS/TS project
Rome wasn’t built in a day, and converting a big project can take weeks or months. You also might still want to keep some of your code in JS and that’s completely fine.
The hybrid method allows your team to migrate to TypeScript without stopping the development process. Use the allowJs compiler option to add new TS files and convert old JS files as you go, at your own pace.
The TypeScript compiler can implicitly understand some types in your JavaScript code, and create type definitions solely based on your code’s structure, usage of constant variables, and dependencies that provide type definitions.
For example, consider the following JS file.
Since the TS compiler can understand structures and constants, it can generate the following type declaration for the createPerson function.
This is a huge benefit you can get from just setting up your TypeScript environment with the existing JavaScript code, even before writing a single line of TS code.
2. `any` help
TypeScript has a special any type. It basically instructs the TypeScript compiler to disable type checking for a certain value. It’s a powerful tool that lets you keep converting code, without struggling with a specific line of code that’s causing you trouble.
However, it should be used sparingly because static type checking is one of the main reasons you moved to TS in the first place. A value defined with the any type will allow access to any property without checking. Furthermore, any of its properties and some of the values calculated based on it will also have the any type.
When the TypeScript compiler cannot determine the type of a value - think, for example, of a function parameter with no type - it will default to any.
The noImplicitAny compiler option lets you control whether it is mandatory to explicitly define types.
Conversion
Once you’re done setting up your project to run TypeScript code, you can start converting your JavaScript code.
3. Look for repetitive structures
Photo by Alex Block on Unsplash
Reusing code is always a good idea, it makes your code more consistent, more comprehensible, and generally improves programming cost-effectiveness. Reusing types is no different.
Ricos, which I mentioned above, is a monorepo project comprising several core editor and viewer packages, and many more plugin packages. Most of the plugin packages follow a common structure. This created an opportunity for us to convert multiple files at once.
Try creating types or interfaces that describe your common structure. If you’re already familiar with TypeScript generics this could be your time to shine with some useful generic types.
Every plugin package in Ricos exposes a plugin creator function. In the following example, we can notice the generic PluginCreator function type that requires a type variable - PluginConfig. Notice how the image plugin implements its own plugin creator by providing its own PluginConfig.
4. Large scale code refactoring
Suppose you found some repetitive code structures in your code, like in the example above. You’d probably want to convert as much code as you can in bulk.
We found it very useful to identify our code patterns and use regexes to apply changes to the code. You might have dozens of instances of the same code pattern, and regexes make refactoring super fast. Just bear in mind that some preprocessing might be required to eliminate slight code differences you encounter.
An alternative approach can be to use a code modification script (codemod). A promising tool you can look into is ts-migrate, it offers an automatic solution for converting code in bulk, bringing it to an initial working stage.
5. Prioritizing
When you have a large amount of code to convert, it might be difficult to decide what to convert first. Depending on the type of project you are converting, try to think in terms of “which part of the code, when converted, would be the quickest to create the most benefit”.
Ricos, for instance, is a library - so we started by converting our API to TypeScript. The immediate benefit was to provide our consumers with code auto-completion (both for JS & TS consumers).
Consider things like:
Common modules that are heavily used throughout your code - this gives you auto-completion when using them and makes conversion of dependent files easier
Outfacing APIs - this gives your consumers the benefit of auto-completion, and your TypeScript consumers the ability to reuse your types in their code
Repetitive code - makes it easier to convert large pieces of code in bulk and reach your conversion goal faster
Example code - if you have a playground or example code, converting it will both help consumers use your library, and give you another layer of type safety from a consumer’s point of view
API-first is a good approach for libraries. In other cases, you’ll most likely want to start by converting a common module that’s used throughout your code.
Once you’re done with your first step, try going over your initial list of motivations for conversion again. Remembering what made you start the migration can help you decide what your next goal should be.
Our next step was converting the core editor & viewer components. This made the main area of the code more type-safe and made it easier for the devs to start converting other dependent modules.
Getting the team on board
Assuming those 50k lines of code aren’t all your own, converting your codebase and starting to write TypeScript code will require a team effort. Imposing new guidelines out of the blue on a bunch of devs is obviously not a good idea. To succeed you’ll need your team to see the benefits they will get with TypeScript and give them the support they need as they’re adopting the new methodologies.
6. Share tutorials
The web is full of TypeScript tutorials for beginners and advanced engineers, and also dedicated ones for React or Node. We used typescript-exercises.github.io in our team since it offers an interactive experience of learning based on exercises and code fixing, which most people find more enjoyable.
7. Help teammates read TS errors correctly
This was a recurring pain point for the team. Errors tend to be quite lengthy due to TypeScript’s structural nature. The top line gives a very high-level explanation of the error:
Type '{ ... deep type definition ... }' is not assignable to type 'VeryDeepInterface'.
And the bottom line provides the most atomic piece of information:
Type 'string | undefined' is not assignable to type 'string'.
Type 'undefined' is not assignable to type 'string'.
The answer you’re looking for will tend to be around the bottom. Though finding it isn’t that easy due to the long description, and your IDE/terminal might be squeezing the text, making it unreadable.
When starting to code, these might look intimidating. Here’s a nice read on understanding errors from the TypeScript documentation you can share with your team.
8. Using `any` with caution
During the migration process, consider allowing teammates to explicitly use the any type, or to suppress errors with the @ts-ignore comment whenever they’re in doubt, so they can keep going without being held back by type errors.
These are powerful tools, but overusing them is extremely discouraged. These do come in handy when developers new to TypeScript start writing or converting TS code, but it’s necessary to keep track of your any and @ts-ignore usage in the project.
Try to make sure any helps the team go forward, but doesn’t get forgotten in your project. One way to do so is to have someone from the team in charge of reviewing PRs containing JavaScript to TypeScript conversions to help better define the tricky types before committing them to the main branch.
Conclusion
Photo by Brett Jordan on Unsplash
From type-safety to auto-completion, and from clearer code to happier debugging, there are so many benefits your team can gain from converting your project to TypeScript.
Since TypeScript is a superset of JavaScript, from the moment you’re done with the setup, the TS compiler creates basic type definitions for your JS code, so you can start enjoying things like code completion immediately.
Making such a wide change in a large project seemed very ambitious at the start. Making this change happen without stopping the development process required planning well and working efficiently. Advocating the move and bringing the team on board was a personal challenge for me.
I learned that big changes come in baby steps. This kind of change isn’t binary. Aiming for 100% TypeScript code is amazing, but I figured that with good planning, even at 20% conversion we can already substantially improve dev experience for the team and our consumers.
I also learned that if you believe that something can bring significant benefit, and can back it with reasoning, then others will eventually follow. Making a change that would affect other teammates’ work can’t be a hasty decision. Supporting the team as it adapted its current working methodologies and adopted new ones was a crucial step in this project’s success.
You can still convert that big JavaScript project to TypeScript. Set it up, get your team on board, and convert it at your own pace. You’ll gain more benefits as you go, with every newly converted file.
This post was written by Matan Harsat
You can follow him on Twitter
For more engineering updates and insights:
Join our Telegram channel
Visit us on GitHub
Subscribe to our YouTube channel