Converting a Create-React-App using Craco to TypeScript
Tags: typescript, createreactapp, craco, javascript
Published: 2019-11-12 20:49:22

---
title: Converting a Create-React-App using Craco to TypeScript
published: true
description: Migrating a legacy create-react-app codebase to TypeScript
tags: TypeScript, Create-React-App, Craco, JavaScript
cover_image: https://images.unsplash.com/photo-1535054820380-92c41678b087?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=2251&q=80
---

My dev team recently had an internal conversation that went like this: 

**Coworker1:** what is everyone’s appetite to exploring typescript as a standard on new stuff we do/create in the frontend?
**Me:** YASSSSS
**Coworker2:** I am on the fence on that.

After more conversation and consideration of the pros, cons, and challenges of migration and adoption into a living, breathing app, we decided to move forward with adding TypeScript to our 2-year-old app. I volunteered to lead the project since I was the only one on the team with on-the-job TS experience 😬.

What you should expect from this post: 
* The tech stack I started with
* What it took for me to convert one file to TypeScript, ensure nothing was broken, and ensure linting worked the app still ran
* Some of my favorite TypeScript "getting started" resources

---

## The Tech Stack

Our app is on `react-scripts@3.0.0` version for [Create React App](https://github.com/facebook/create-react-app). Thankfully this meant TypeScript was already supported by the app. 



## Adding TypeScript

### The easy parts

Well, if you use `create-react-app` this might be all you need:

```bash
$ yarn add typescript @types/node @types/react @types/react-dom @types/jest
```

While that command doesn't perform all the magic we need, it did set us on the right path. Adding this meant that `react-scripts` now knew we were using TypeScript. So the next time I ran the `yarn start` command to fire up the server, the `jsconfig.json` was removed and the server helpfully said something along the lines of "It looks like you're using TypeScript - we've made you a tsconfig". 

### The hard parts

Okay, so as easy as it was to make my app compatible with TS, it was not that easy to get it configured to work with my app. Here are just a few questions I ran into: 

* How do I get my app path aliases to still work?
```javascript
  import Component from 'components/Component' // this should still work
  import Component from 'src/shared/components/Component' // don't make me do this
```
* I only want to convert one file at a time - how can I import `.tsx` files inside `.js` files without needing to specify the file extension? 
* We had a lot of linting warnings that popped up as soon as I added a local `.eslintrc.js`. I don't blame TS for this, but you might run into a similarly frustrating cycle of having to resolve a lot of linting errors then seeing more, then fixing more, etc. 




## So what actually changed? 

The final PR ended up having an 8-file diff. But my first attempt had a 73-file diff. Why is that, you wonder? Well, I totally dove into the rabbit hole of trying to fix one thing which led me to feeling as though I had to upgrade a dependency to be compatible with TypeScript which then meant other dependencies needed to be upgraded. There might have also been some things that broke when I upgraded dependencies - I'm looking at you `react-scripts`.

Here's the list of my final files I needed to make TypeScript happen:
* Create `/frontend/.eslintrc.js`
* Delete the `jsconfig.json` that `create-react-app` used
* Add the `tsconfig.json` 
* `yarn.lock` changes
* `package.json` changes with new dependencies
* A new `/react-app-env.d.ts` file that `create-react-app` automatically adds
* The component file I was converting to TypeScript
* One component spec that had a linting error 

Alright, so let's walk through these changes. 

**Eslintrc** 
This file was pretty straightforward. I used most of the recommended settings and merged in the existing upper-level rules we had already in the codebase. 

The main thing I wanted to point out was the fix that allowed me to import a single `.tsx` file into a `.js` file without getting a compilation or linting warning? 

Two things made this work: 
```javascript
module.exports = {
  parser: '@typescript-eslint/parser',
  rules: {
    'import/extensions': ['.js', '.jsx', '.json', '.ts', '.tsx']
    ...
  },
  settings: {
    'import/resolver': {
      node: {
        paths: ['src'],
        extensions: ['.js', '.jsx', '.json', '.ts', '.tsx'],
      },
    },
  },
  ...
}
```

**tsconfig**
Since `create-react-app` generates this, it is challenging to alter it. I did add a few extra `compilerOptions` to fit the needs of our app, but did not change it in any way worth pointing out. 

**Package Changes**
Most hanges in `package.lock` were to add new type definitions or new dependencies. 

I also updated our linting script to include new `.tsx` files:

```javascript
"lint": "eslint './src/**/*.js' './src/**/*.tsx'",
```

I did run into an issue where our `eslint-plugin-jsx-a11y` version was throwing a false-positive linting error. That was resolved by upgrading to: `"eslint-plugin-jsx-a11y": "6.1.2",`

**The New Component**

So what does a newly-converted component look like? I strategically picked the furthest leaf of a component node I could find - that is to say this component is only used in one place by one other component and renders one input. So it was simple to alter and had minimal impact on the app. 

Here's a _very_ generalized version of the component before TS: 
```react
import * as React from 'react'
import { Field } from 'formik'

export default function ComponentField({ prop1, prop2 }) {
  return (
    <div className={s.className}>
      <Field
        type="number"
        name={name}
        render={({ field }) => <input {...field} />}
      />
    </div>
  )
}
```

And here's what it looked like after TypeScript: 
```react

import * as React from 'react'
import { Field, FieldProps } from 'formik'

interface ComponentProps {
  prop1: boolean
  prop2: string
}

export default function ComponentField({
  prop1,
  prop2,
}: ComponentProps): JSX.Element {
  return (
    <div className={s.className}>
      <Field
        type="number"
        name={name}
        render={({ field }: FieldProps) => <input {...field} />}
      />
    </div>
  )
}
```



## Resources I Found Helpful

* [This cheatsheet](https://github.com/typescript-cheatsheets/react-typescript-cheatsheet) is extremely popular and even has a section on migrating!
* Microsoft has [a migration guide](https://github.com/microsoft/TypeScript-React-Conversion-Guide) that might be helpful for you and has a dummy app you can follow along with
* This [Twitter thread](https://twitter.com/jsjoeio/status/1193605374768603138?s=20) about what challenges people faced while using React + TypeScript. And read the comments, too!

---

## Conclusion

See?! Not so bad! This approach works well for our small team with devs who are unfamiliar with TypeScript. We'll be able to add and convert files as we go without the pressure of changing everything at once. 

This also made a low-risk implementation for us - it was one file that we could test in isolation as opposed to renaming every file to `.tsx`, adding `any` all over the place and worrying about compilation errors or build issues. 

I'm no expert, and implementing TypeScript into a legacy codebase totally depends on the setup of your app - but stick with it! You can figure it out.