Incrementally migrating a CRA application to TypeScript without ejecting
Tags: react, typescript, javascript
Published: 2020-02-26 19:45:58

**[Cover Art](https://www.pexels.com/photo/airplane-near-blue-and-grey-building-747079/) by [周 康](https://www.pexels.com/@137666)**

**Update (29.02.2020):**

I was notified by [whatwhatwhatwhatwhut on Reddit
](https://www.reddit.com/r/typescript/comments/fat1nz/incrementally_migrating_a_cra_application_to/fj1i5vu/) that it is way easier to add TypeScript support! We will just have to follow these steps mentioned in the [documentation](https://create-react-app.dev/docs/adding-typescript/).

**TL;DR:**


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

Rename any file from `.js` to `.ts` or `.tsx` (and adjust types).

Restart the development server.


This is definitely an improvement of the somehow cumbersome method below. So far I did not notice any drawbacks!

**Original Content:**

**JavaScript Tooling is constantly improving.** The power of [CRA](https://github.com/facebook/create-react-app) makes scaffolding a new React project easier than ever before. Just a few years ago the modern JavaScript developer had to fiddle around with Webpack configs and whatnot. Nowadays higher-level abstractions such as CRA made all the configuring obsolete.

Another tooling that has made a buss in the community is TypeScript. Inevitable, CRA added TypeScript support without having to eject (and therefore avoid dealing with Webpack configuration again).

There is nothing wrong with learning how to configure Webpack (it even got way easier by providing better defaults in more recent versions), but still, each second spent on worrying about Webpack is stealing our precious time that could rather be spent building the product. But let's get back to the topic 😅.

**Unfortunately, it is still hard to incrementally adopt TypeScript in an existing "vanilla" CRA project without having to eject or renaming all the files which will result in a giant pull request and conflict with everything other people are working on right now.**

For an existing project, I tried a new approach that I want to share with you!

## Bootstrapping the TypeScript Config

The basic idea is to have a new folder `ts_src` that holds all our new TypeScript code. When we compile that code we want the output files built into our `src` directory so that our CRA development server will pick up the changes and reload our application running inside the browser.

```bash
mkdir -p ts_src
touch ts_src/tsconfig.json
echo '
{
  "compilerOptions": {
    "module": "ESNext",
    "target": "esnext",
    "lib": [
      "dom",
      "dom.iterable",
      "esnext"
    ],
    "noImplicitAny": false,
    "outDir": "./../src",
    "rootDir": "./",
    "sourceMap": true,
    "declaration": true,
    "strict": true,
    "allowSyntheticDefaultImports": true,
    "esModuleInterop": true,
    "strictNullChecks": true,
    "jsx": "react",
    "skipLibCheck": true,
    "moduleResolution": "node",
    "noEmit": true,
  }
}' > ts_src/tsconfig.json
```

## Add TypeScript Build Script to `package.json`

We add one for building the whole application as well as one for development.

```diff
-  "build": "react-scripts build",
+  "build": "yarn build:ts && react-scripts build",
+  "build:ts": "yarn tsc --build src_ts/tsconfig.json",
+  "build:ts:watch": "yarn build:ts --watch",
```

## Moving our file to `ts_src`

Then we can start moving existing files over to our new `ts_src` folder. We will keep the same folder structure as inside the `src` directory.

```bash
# ensure directory exists
mkdir -p $(dirname src_ts/path/to/file.js)
# move file to new path
mv src/path/to/file.js src_ts/path/to/file.ts
```

## Add path `src/path/to/file.js` to `.gitignore` and `.eslintignore`

The file is now being built to `src/path/to/file.js` and therefore a build artifact, so there is no reason left for tracking the build artifact inside git.

We also must  add the file to the `.eslintignore` because [the react-scripts does also apply eslint on files listed in the `.gitignore`](https://github.com/facebook/create-react-app/issues/8545) and the build artifacts could trigger eslint warnings/errors that will let our CI build fail.

```bash
echo 'src/path/to/file.js' >> .gitignore
echo 'src/path/to/file.js' >> .eslintignore
git rm --cached src/path/to/file.js
```

## Repeat step for every file that is imported by `src_ts/path/to/file.ts`

Now we need to repeat the steps above for every file that is imported by the file we just moved over, otherwise, the TypeScript compiler will complain 😅. Ideally, we would start with migrating smaller files, that do not have many imports.

## Add typings

Then we can finally add our types! I already caught some nasty bugs in some of the code I migrated by just adding types! **Let me know what you encountered along the way in the comments 😉**


## Committing the changes changes

Nice, we just migrated a part of our application over to TypeScript.

For faster development iteration we can use the TypeScript compiler in watch mode (besides the `yarn start` command that spins up the CRA development server):

```bash
yarn build:ts:watch
```

## Adding new functionality

We can now add new features in TypeScript inside our `src_ts` folder. 

Before we create a new file we should also make sure the corresponding path inside `src` is not already taken by an existing file!


## Someday in the future: the `src` folder is empty 🎉

Depending on the project size this could take a while, but hopefully, that day will finally come!

The migration is now almost over, there are no more JavaScript source files available and everything is now typed!

We can now delete the empty `src` folder and rename the `ts_src` to `src`.

```bash
rmdir src # ensure it is empty ;)
mv ts_src src
```

Next, we can move the TypeScript config to the root of the repository.

```bash
mv src/tsconfig.json tsconfig.json
```

And also apply some adjustments:

```diff
  {
    "compilerOptions": {
      "module": "ESNext",
      "target": "esnext",
      "lib": [
        "dom",
        "dom.iterable",
        "esnext"
      ],
       "noImplicitAny": false,
-      "outDir": "./../src",
-      "rootDir": "./",
       "declaration": true,
       "strict": true,
       "allowSyntheticDefaultImports": true,
       "esModuleInterop": true,
       "strictNullChecks": true,
       "jsx": "react",
       "skipLibCheck": true,
       "moduleResolution": "node",
       "noEmit": true,
-    }
+    },
+    "include": [
+     "src"
+    ]
  }

```

Then we can also finally remove all this `.eslintignore` and `.gitignore` entries we collected while migrating existing `.js` files (or adding new `.ts` files).


## We are done 👌

This alternative approach might have some drawbacks such as adding entries to `.eslintignore` and `.gitignore` or having to migrate all files that depend on each other at once, but I think this is a great alternative towards traditional ejecting.

Many of the tasks could be enhanced further e.g. by creating a handy CLI that will automatically run the commands required for moving a file from `src` to `src_ts` (along with its imported files) while also adding the corresponding entries to the `.eslintignore` and `.gitignore` files. 

In case I further explore such ideas I will definitely write about it or even publish it as an npm package.



**Do you know a better method for migrating the project? Any other ideas on how the migration path can be improved further? Are you currently planning to migrate an application to TypeScript?**

Let me know, down in the comments ⬇ and thank you so much for reading!