Disclaimer: I’m not describing the migration process step-by-step or TypeScript itself in this article. You can find many resources on that online, including official TS docs.
- starting to use TypeScript compiler and migrate step-by-step.
After a short discussion, we decided to go with the step-by-step migration strategy. Why? First, it’s less time-consuming. Today, we know that it would kill us if we decided to convert all our JS codebase to TS at once. It would also freeze our codebase for a while – until every
.JS(X) file is renamed to
.TS(X) and all compile errors are fixed.
That’s very comfortable. Just think about that – you can switch your project to TypeScript without actually having to change any code. We decided to immediately re-write a few of our recently-added functionalities to TypeScript and convert the rest gradually.
What TypeScript gave us
Types in new code
As soon as you switch your compiler to ts-loader, you can start creating files with TypeScript extensions (
.TSX). It means that all your new code can now leverage types:
Typing information for existing libraries
Have you been wondering what about the external libraries you are already using? How to get types for them? I was thinking exactly about this before migration.
npm packages you use already have built-in typing information. You can check it on your package’s
If it doesn’t, you can try installing
npm package called “@types/your_library_name”, e.g.
npm i @types/backbone.
As a last resort, you can create the declaration files yourself.
services.js file where we kept “services” exported as follows:
After migrating it to TypeScript (meaning renaming to
services.ts) it turns out that these two definitions can co-exist, even in the same file:
As you can see, we added typing information to the legacy
StockService as well. That’s because we have
noImplicitAny enabled in our TypeScript configuration. It doesn’t allow leaving any variables which are of
any type. In any case, if you don’t know what type to use for a given variable/function, you can always type it explicitly as
any, while moving the new code to fully-typed equivalents. That’s the power of TypeScript ?
Many TS compiler options
allowJs. We have already mentioned the
noImplicitAny setting that forces you to always explicitly specify types in your code. There’s much more stuff helpful in various scenarios of JS-TS migration. We were having some issues with imports from modules without default exports – enabling
allowSyntheticDefaultImports saved us there.
What I’d like to emphasize here is that TypeScript compiler is really helpful ? It allows you to set your “typing” level by enforcing less or more rules. Thanks to that, you can slowly move towards more strict type checking rules like
noImplicitAny. You’re not forced to do that from the beginning.
Better coding experience
You can catch your errors earlier. No need to debug the code to see if the property you used actually exists on an object. Your IDE gives you code completion suggestions. It’s a pure pleasure ?
Of course, we met some challenges during migration. I don’t call them issues, because they are all manageable. Many people have already done those things, so you can find solutions to almost any issue online. Let’s see what we had to overcome.
If there’s something I don’t like about web development, it’s definitely webpack... This is a typical “fighting with machines” case. Poor documentation, lack of well-described solutions online and some specificity in every project.
We had problems with
babel-loader for legacy JS files. We couldn’t make it work, so we decided to switch completely to
Next issue was a lack of proper source maps and problems in debug mode. The breakpoints were often not hit. After a bit of searching and trial-error sessions, we found a solution. It was necessary to modify
webpack.config.js and set
devtool webpack setting to
You need to be really patient with webpack… But if you have already worked with it, I guess you know that very well ?
Adding types to legacy JS code
Adding types information with
noImplicitAny set to
true is sometimes challenging. Especially if you have some bigger JS files with no typing information at all. Sometimes you need to search for function’s calls to find out of what types are its parameters. In other cases, you might need to run your app and see that at runtime ?
Fortunately, as we already said, TypeScript doesn’t force you doing that. You can play with tsconfig or use @ts-ignore as a last resort.
Handling libraries without types
Some of the libraries we used don’t provide any typing information. It makes working with them a bit difficult, especially if your TS compiler is already configured in a bit stricter mode. In such case, you might get an error similar to the following one:
Your options here are to either write the declaration files yourself, or import the library differently. You can, for instance,
const FormIO = require('react-formio');
and then use this
FormIO variable which is typed as
any. Again – that works because of TypeScript’s flexibility, but might not be very comfortable to live with.
Of course! There’s only one right answer. Don’t wait too long – start being more productive with TypeScript as soon as you can ? Believe me – you will not regret it.