Migrating from JS to TS (CRA)

Rafael Mariano
Opensanca
Published in
6 min readAug 13, 2020

In this article, I will explain how I migrate to typescript a Create React App project written in javascript. You can follow instructions on CRA's official documentation about adding typescript, but I promise my intention here is to go farther than this.

I will explain an excellent strategy to migrate your whole codebase to typescript. For this, I’ve separated this article in three steps

  1. How can you configure typescript by yourself, fixing linter/prettier and integrating with jest;
  2. A migration strategy to move your current codebase to typescript with simple refactoring and less headache as possible;
  3. What I’ve learned so far, what helped-me, what kind of mistakes I’ve made, and some configs that helped me.

Configs

I’ve separated this topic into three phases. First, I focused on making the application work. Second, I fixed linter and prettier to make my life easier. And third, I configured the tests to operate with both TS and JS.

App

This config is the easiest. The CRA configuration can operate with JS and TS at the same time, so you only need to configure the typescript compiler. The doubt is, where do I extract a tsconfig.json once I never wrote one before.

So, I’ve created a new app create-react-app with the typescript template and used the generated tsconfig.json after I’ve added some rules, one for the baseUrl (to replace NODE_PATH) and other to exclude test files.

To start the migration of your app to TypeScript to a Create React App project, install TS dependencies, and add the tsconfig.json to your app’s root.

yarn add typescript @types/react @types/react-dom @types/node

Linter/Prettier

There’s nothing so exciting to say about linter and prettier. I’ve just updated my eslint config to operate with typescript. If you don’t use linter and prettier, I suggest you stop what you are doing and configure it as soon as possible in your project, even your small or side projects.

Add the following dependencies to integrate with eslint and prettier and update your .eslintrc.js file to recognize .ts files and use typescript plugin.

yarn @typescript-eslint/eslint-plugin @typescript-eslint/parser

Tests

In the first moment, all your tests and components are written in JS. The jest test suit doesn’t care if your component is written in JS or TS. You don’t need to do anything. Once you write your components in TS, it’s interesting to write your tests ins TS too. In this case, you need to configure your jest to transform your tests written in TS. For this, you need to add dependencies to integrate jest with TS and update your jest.config.js.

yarn add ts-jest @types/jest

Migration Strategy

It’s easy to configure a hybrid approach to operate the app with both TS and JS. Migrate the whole application is the biggest challenge. I will describe four different strategies that help me migrate my application.

First approach, start migrating the leaves: Your application works just like a tree. Working as a tree, if you rewrite the components closest to a leaf (where there’s no other internal dependency), your application will conflict less. So, this strategy consists of migrating every leaf, and every parent, until reach the root component.

A simple view of a component tree (similar to a DOM)
An app components tree

Second approach, rewrite your tests first: Start migrating your codebase by renaming each test from .js to .ts and fixing every broken test. The tests can display a lot of exceptions due to the non-compliance with the typescript.

Third approach, starts with new features: Start migration only with new features will make things easier once you write a brand-new code and a new routine to test this new feature. New feature it’s easy to measure and exposes a controlled impact on the customer.

Fourth approach, migrate on demand: Possible this is the most challenging strategy but the most realistic. Migrate the files on-demand, as you need to make any change in a specific file. If you have tests for the file (and I pray for this 🙏), I recommend you migrate this specific test first and after migrating your component, so then modify your component for your new product requirement or design change.

To bootstrap the migration of my app, I’ve chosen the first approach. This proves the concept and creates the inertia to the team use typescript. Once you are encouraged and have more expertise in programming typescript, you can follow other approaches.

What I've learned so far?

Everything I said can sound like good music for those who want to use typescript or are interested in using this language. But the sky it’s not clear all the time. I want to describe some benefits I’ve learned with this migration and some blocking challenges that make me waste some hours.

The glad things

Backend teams use Kotlin in our microservices. Typescript is more close to Kotlin than you can imagine. I think this helps everyone in the code review and encourage Kotlin developers to try typescript and vice-versa.

Moreover, typescript helped me find incredible inadequate implementations and bad integrations with external packages. As examples:

  • Bad implementation: when I’m working with timers (setInterval and setTimeout), I always address the timer’s id to a variable and check if the timer exists before unmounting the component; this is never necessary. You don’t need to check if exists a timeout in progress to clear it. You can use clearIntervall(null) without any exception. I realized how bad this is with typescript and bad usage of useContext.
  • Bad integration: I always use date-fns in my projects. But, when you try to diff times, the method diff does not support a time declared in milliseconds; this explains some unexpected behavior in my app. Moreover, this encourages me to read the implementation of the package instead of the docs.

The sad things

I’ve underestimated typescript. I’ve never worked with a typed language before, and there’s a lot of features that were hard to understand, like type inference, type overloads, and generics.

It’s easier to handle side effects (like requests) with typescript once the language requires to access the responses safely. I work with axios and SWR, so there’s no secret to operating requests with typescript. I’ve never worked with GraphQL, only in my side-projects. Typescript works perfectly with GraphQL.

Some packages do not have integration with typescript and probably, will never have. I’ve tried to type this kind of package and failed consistently. First, I’ve discovered that kind of package was deprecated. Second, I realized the package isn’t necessary for my app. My sad here was that I take so long to realize that the package isn’t required.

PLEASE, do not leave any warning behind, fix it as soon as possible! I left a lot of hints to fix after, for example, explicit-return-function-type. In a short time, a lot of other new warnings started to appear. I spend a lot of time to fix these new warnings that I’ve ignored. For the case o explicit-return-function-type I’ve disabled this warning in my eslint rules.

If your application is written in Gatsby or NextJS framework, the logic is the same, for configuration and the migration strategy.

I didn’t say why I choose to use typescript. My focus here is to show how you can do it and not why you should do it. Many articles show the benefits of using typescript, and you can find it with a simple search on Google. I do recommend this paper comparing javascript with typescript/flow: http://earlbarr.com/publications/typestudy.pdf

Moreover, if you want to talk with me about it, you can find me on Twitter, LinkedIn, and in some Brazilian slack community as Opensanca and react-slack-brasil by @rafael.

--

--