I used to object to the idea of TypeScript. To me, it didn’t seem to make sense to add static typing to a language that was designed not to have it. I started my career as a C++ Graphics Engineer, before transitioning to being a Front-End Designer and Developer. So static typing wasn’t anything I wasn’t already familiar with.
Moreover, at the time tooling was split between communities, there were people asking why they couldn’t just write C# for the web instead, and support for most modern frameworks was pretty limited.
One of the biggest questions a fledgling startup has to answer when considering any kind of tech stack change is: how long will it take? You want to be releasing changes quickly and easily, and you definitely don’t want a holistic port to create big bubbles in your release pipelines. Additionally, you want to consider the implications of such a change on your DX (Developer Experience) and also the speed at which you get things done. We all know that as codebases mature and swell, dev speed inevitably slows down. So you’re looking to avoid introducing something that’ll massively swell your codebase, increase your test burden (we’ll talk more about this later) and give your dev team(s) a worse quality of life, work-wise.
So let’s talk about how moving to TypeScript for your React project fits nicely avoids those pitfalls, and can add a lot of value.
You Can Port Existing Code Incrementally
- Any new code should be written in TypeScript.
- If a change is made to a JS file, port it to TypeScript as you go past.
What I never really realized is that TypeScript isn’t about entirely static typing, it uses a progressive typing system — that is to say the compiler comes with a very strong type inference system, which in general means you can use as much or as little typing as you like. For the most part, the inference engine is strong enough to make the right call in so many cases that you won’t need to annotate lots of your code, while still getting the type safety you want. This means a lot of porting JS to TS is effectively trivial; at a minimum, type annotations will need to be added anywhere it’s not possible for the compiler to infer a type, and this usually means annotating a couple of function signatures and not much more.
Developers can easily write new code in TypeScript, as this incurs zero porting cost (obviously), and porting existing code over is generally extremely straightforward due to the power of type inference.
I have ported many React codebases, both large and small over to TypeScript, and I’ve been able to do so incrementally every single time, immediately enjoying its benefits without any kind of pipeline bubble.
One thing one often hears is the ‘increased boilerplate’ that any static type system may introduce.
Consider the following example, a React component in JS, following ‘best practices’:
Now in TypeScript:
Not too different size-wise In fact, our TS example is actually shorter.
Generally speaking, TS with React incurs no more boilerplate (sometimes even less) than using plain JS.
You can Still Move Fast
As mentioned previously, as a young startup you want to move as fast as possible. You want to delay code bloat and keep your dev speed fast and your release cycle tight. You want to be able to react to changes in priority easily, and a great way to do that is a simple, elegant codebase that requires low effort to make a simple change (Believe it or not, this is not the case for many large codebases, where seemingly meaningless changes take forever.). There inevitably comes a point where your development speed slows down, as your code swells, and your test quantity grows. It’s therefore extremely desirable to try and push that slowdown as far back as possible. One of the ways we can do this is to write fewer tests.
Before I continue, I want to say this: there is no substitute for good tests. It’s very hard to be completely confident in a software product if it doesn’t have good tests. Each release should be simple and you should be confident that it ‘works’. The thing is, in reality, a lot of tests that get written aren’t good tests. Lots of unit tests you find in production codebases either:
- Test implementation details so they break whenever you change anything, or
- Are just glorified type-checkers, verifying the types of inputs and outputs for the code they’re testing.
Now, there’s no easy remedy for the former. If you have got into the situation, you’ll have to dig yourself out by rewriting these tests. For the latter, TypeScript can replace these tests very easily. You can dump a whole bunch of now-superfluous code, without sacrificing the confidence you’ll have in your next release. Type checking in this way is like a suite of little tests that run on every save, as you write the code. While this is not a substitute for behavioral tests, and higher-level approaches like integration and end-to-end tests, an entire class of bugs is de-facto removed from your codebases. Incorrect prop names, case errors etc. all disappear as they’ll never be allowed into your production branch. Couple this with the added ‘implicit documentation’ that static types are often said to bring, and you can be so much more confident about shipping new code.
If you want to move fast, having a small test burden is essential. If you want to be sure about what you’re releasing, good quality code is essential. You’ll have to strike the balance yourself, but TypeScript can be a good way to find a great compromise here.
The Tooling is Now Just as Good (if not better)
As a developer, working on just about anything is a breeze if you’ve got the right tools. Fortunately, TypeScript is a great example of good tooling!
It used to be that in order to adopt TypeScript in an existing React project, it was not easy to set up, and you had to give up using some tools. For example, in the past, you couldn’t use ESLint for TypeScript codebases. A TS-targeting linter, TSLint, was your only choice. TSLint, while still a great project, was way short of the maturity, power and ecosystem of ESLint. So immediately you had to compromise tooling to get types. For me, this was never a fair trade and it was a big argument against migration. Nowadays, this isn’t a problem, as Babel (a widely used JS transpiler) supports TypeScript, meaning you can use tools like ESLint for your TypeScript projects easily. No more compromising on tools.
Libraries Have Rich Support
Most (if not all) large JS libraries support TypeScript. Many of them have been written using TypeScript itself, or the community has lovingly gone through the module and written type declarations for it, meaning you can plug your TypeScript codebase into it and get type support for other people’s code as well as your own. It makes it much harder to misuse libraries this way, and avoids lots of time spent head-scratching because you’ve misspelt something.
One of the most important tools is the editor they use. If your editor can automate a lot of simple tasks, tell you when you’re getting things wrong and help you with meaningful suggestions a lot of ancillary tasks get accelerated to light speed, and with fewer errors. Most of programming is these ancillary tasks, so speeding them up has a tangible business impact, as you’ll get good stuff done faster without a drop in quality. Visual Studio Code is a very popular text editor because it includes out-of-the-box support for TypeScript, along with great addons that make it even better. Without these tools I know I’d have never met many tight deadlines in the past. TS’ great ecosystem, tooling and editor support make it one of the most pleasant languages to write, and my level of confidence in what I produce is a world away from the equivalent JS.
In this article I hope to have shown you the incredible value-add you get from using TypeScript in your startup’s tech stack, and just how easy it is to reap the benefits with very few (if any) caveats!
Thanks for reading!