Before going into the technicalities of the subject, allow me to provide a bit of background story to this article.
We already have so many articles about the pros and cons of type systems, yet I wanted to do my own research to ensure that it’s also applicable to me personally. Many state that the primary reason for using type systems is better code safety and fewer bugs. I decided to test this assumption myself.
With every new bug, I would determine if this bug could have been predicted and avoided using TypeScript. Well after one year of research, I found that in 30% of cases the answer was a resounding “YES!”. Some might say, well that’s not even half. My response to this would be that it’s a third of bugs that you could have avoided “for free.”
But type systems give you a lot more than just this. And for this reason, I’ve decided to share some of my experience with TypeScript.
Do we really need type systems?
Here is my personal flowchart for determining whether you need to use TypeScript (feel free to replace it with any type system in this context) when you start a new project:
Make it your habit, let your hands remember it as a standard and you’ll never need to make this decision again. It’s just the way it should be. As a hidden bonus, most companies view knowledge of type systems as a great advantage for a frontend developer position, so let’s use it every day! Hitting interesting edge cases and resolving them is only possible when you have everyday experience with the technology.
However, if you still need a bit of reasoning behind using type systems, this next section is for you.
The benefits of type systems have been debated many times, but it wouldn’t be a TypeScript article if I didn’t mention:
- Code safety: Catch unexpected bugs faster and make your application more reliable. According to the research, TypeScript can catch 15-30% of bugs. Regardless of the exact numbers, we should all agree that it does catch some very nasty buggers.
- Developer experience: It’s often said that TypeScript makes your IDE/tooling work. That means that the language server has way more context and is able to provide better suggestions and even generate documentation based only on source code. All the above makes a developer’s life easier and surely more fun.
- Better API communication: It’s no surprise that when both the frontend and backend speak the same language in terms of clearly defined request/response types and shapes, this provides both sides with better confidence and fewer surprises. Moreover, you could use tools to auto-generate free types (e.g., with OpenAPI or GraphQL). Wow! Pure magic.
Although it’s easy to recommend TypeScript for a new project, for existing ones it’s not as easy as there are several prerequisites to meet:
- Learning: TypeScript doesn’t come for free and requires learning by all the engineers. Although nowadays it’s standard de-facto, we still need to make sure everyone knows it.
- Stricter coding “jazz”: sometimes you would like to sketch some algorithm without thinking too hard about types and safety. In such cases, a type system would be a grumpy instructor screaming “Not quite my tempo!”, but deep down you know it is right.
- Effort: migration requires quite a bit of effort and dedication.
And it’s true that if you’re planning some tremendous changes in the architecture of the project anyway or a complete rewrite from scratch, it would be a clever idea to make migration part of this upgrade.
Let’s imagine we decided to migrate our project to TS. What would be the most painless way to do it? There must be a quick, free, effortless way, without registration and SMS, right?
In fact, gradual, stable increments are the name of the game. If you make progress all the time you can be sure that migration is possible, and the process is finite (given the migration rate is faster than the new code introduction). See below for how to ensure this.
Wanna explore TypeScript along with Alexandr?
The ideal time to join Mews Frontend Team!
There are two main ways we can carry out a migration:
- Band-aid: focused on gradual strictness.
- Hybrid: focused on incremental coverage.
Both techniques have pros and cons which mostly depend on you and your team’s working style. Let’s review them in more detail:
Band-aid (TS everything)
This strategy is recommended primarily by the official TypeScript guide. You would need to set up ts-config and rename all .js(x) files to .ts(x).
Part of the method is to start with less strict rules and gradually increase the strictness of the types in the code. It’s even okay to allow
any type in the beginning, but be sure to act on it as soon as possible to start reaping the benefits of strong typing.
- Allows you easily add/enhance types.
- Seamless and gradual increase of coverage/strictness of the codebase.
- You start with really weak type strictness (but gradually increase it).
- Hard to track actual migration progress.
This approach takes one file at a time (or even part of it) and migrates to TS.
By using the
allow-js option in your
ts-config, you can allow the usage of TS and JS files side by side.
This way, you have a hybrid codebase with TS files possessing your final strictness and JS in their original forms. The benefit of this is that most IDEs can still use information from TS even while you edit a JS file.
- Instant use of strict TypeScript rules and their associated benefits.
- You know exactly how much of your codebase is migrated.
- Slow, as it requires an extra step to enforce a type.
- There would be a lot of exceptions in the beginning as dependencies are not strongly typed yet.
Tips and tricks
There are several tricks we’ve learned along the way that we would like to share with you.
If you need to ignore the error from untyped/badly typed code, opt for the use of `// @ts-expect-error` instead of
// @ts-ignore. This implies that you’re aware of the issue, would like to fix it, and when fixed you would be notified by the compiler that this error is no longer valid. This would help to keep the code tidy.
In most cases, it would help to start with the most used parts of the application (e.g., core modules, utils, helpers) as it would propagate types up the tree of dependencies and would make it much easier to migrate the rest of the application.
Oftentimes we depend on third-party libraries and would use them across our applications.
In case the library doesn’t support the type system in its current state, it might be a great sign to switch to something more up-to-date.
In case the library isn’t typed and there’s no substitute, there’s the possibility of
In the event that you don’t find yours, you would need to create your own type declarations and consider sharing them with DefinitelyTyped.
Monitoring and metrics
When it comes to huge projects, one tool that would help you a lot in this process is having metrics to see that we’re moving in the right direction and at a good enough speed. One way to do this is by counting the number of lines in JS(X) vs the number of lines in TS(X). Some might say these metrics are naive. This is true, but the fact that you can track the progress with every PR is really empowering. It’s hard to describe the feeling when you migrate a huge file into TS and are able to bump the statistics by a percent.
On the other hand, if you introduce new JS code this would show that you’re harming the progress. And trust me, this doesn’t feel good.
You can implement such tooling yourself (based on your migration strategy) or use an already implemented one like typescript-coverage-report.
It’s in the culture
However, the most powerful tool in the process of migration is to bring using TypeScript into the company culture so that every developer feels the need to contribute to the process. The truth is that in most cases development isn’t stopped during migration, and we should avoid situations when migration happens slower than new lines of JS are introduced to the codebase.
Some simple rules can help with this:
- Make sure that people understand TypeScript and the benefits it brings to the table.
- Provide enough documentation to solve the main hurdles and “gurus” of TypeScript to help people when stuck.
TypeScript flavoured Boy Scout rules:
- Introducing a new component/module/file – please do it in TS.
- Adding a code to an already existing JS file – consider migrating all of it in full or partially into TS.
In conclusion, I would like to outline that TypeScript makes your code safer, more secure, and much easier to work with. So, let’s do our best to utilize this great technology.
Select the strategy that works best for you and your team and provide all the information needed to help with migration. Encourage the correct mentality in the organization to accelerate the process and organize help points for every use case.
However, don’t expect quick results. In a large project, it can take quite a bit of effort to complete the migration, so we should approach it with a bit of patience. In the end, this patience is sure to yield impressive results.