Blog

Converting 100,000 lines of React Native to TypeScript

Many years ago, Flow was all the rage. It added these mysterious things called ‘types’ to JavaScript. Wow!

Flow: A Static Type Checker for JavaScript

JavaScript was no longer the dumpster fire it’s always been. It was becoming a real language.

Flow was built by Facebook – the same team that builds React Native. So when deciding what type-checker to use for our brand new React Native app (Second Nature) in 2017, Flow was the natural choice.

Fast forward to 2021 and the years have not been kind to Flow. TypeScript fast took over as the type-checker of choice for new projects – even React Native (who even went so far as to write a top-level guide for how to use it in React Native in their docs).

Typescript and Javascript : ProgrammerHumor

100,000 lines of code later in to our React Native app, Flow was causing problems. It was crashing on our team’s machines and producing unreproducible errors. None of the libraries we used had type checking that was compatible with Flow. Plus, we’d recently starting using TypeScript in our server-side code.

So we made a decision to switch from Flow to TypeScript. Here follows the painstaking process that we followed.

Prerequisites

Follow the React Native guide to installing TypeScript here!

Once you’re set up – then I followed a 9 step process – that I did on loop – for 100,000 files.

You wouldn’t want to do this 9 step process with your entire codebase – I recommend converting a block of files in one go – so a directory or folder structure at a time, make a PR, then start again with Step 1 with the next block. I’d try to avoid converting more than 20 files or so in one go.

1. Rename the files using git mv

First, we use git mv to rename the files from .js to .ts/.tsx. This helps the git history stay intact when changing the filetype.

This bash command will git mv all .js files in the /src/components/Chat directory to be renamed .ts.

ls ./src/components/Chat/**/*.js |
    while read line; do git mv -- $line ${line%.js}.ts; done;

Then we need to make sure that if a file uses JSX, then the file is renamed .tsx .

This bash command will search for import React and rename the file to be .tsx.

find ./src/components/Chat -type f -name "*.ts" |
    xargs grep 'import React[ ,]' |
    cut -d: -f1 |
    uniq |
    while read line; do git mv -- $line ${line%.ts}.tsx; done;

I recommend committing at this point – otherwise git can be funny if you make further changes before committing and the git history may be lost.

2. Convert the Flow types to TypeScript

You can use the npm package @khanacademy/flow-to-ts to automatically convert the files from Flow to TypeScript types.

npx @khanacademy/flow-to-ts --write  ./src/components/Chat/**/*.{ts,tsx}

This does a lot of the heavy lifting – but bear in mind you’ll still have to fix a lot of the types yourself.

3. Run Prettier

Using flow-to-ts makes the files ugly. Run prettier in ‘write’ mode to just automatically format the files before you go on to make manual type adjustments.

npx prettier --list-different 'src/**/*.{ts,tsx}' --write

4. Fix TypeScript errors!

Now to manually fix all the TypeScript errors. The aim here is that you can run npm run typescript and have zero errors.

When you get to zero – I recommend committing at this point.

5. Fix linting errors!

If you use eslint, the conversion may have produced some errors. Check eslint and get to zero errors.

6. Check Flow still works

Sometimes flow can have broken – especially if there was an import that used to incorrectly reference the full filename with the ‘.js’ extension (i.e. import { Component } from '@ui/Chat/ChatWindow.js' .

Run npm run flow and fix any errors that may have come up.

7. Remove any $FlowFixMe’s

We don’t need them anymore! Hurray! Feel free to delete them.

8. Build the app

As a final sense check, build the app and make sure everything still works.

9. Check how many lines of code are .js vs .ts

Hey it helps to see progress! It’s a lot to convert.

# Count the number of TypeScript LOC
(git ls-files "src/*.ts" && git ls-files "src/*.tsx") | xargs cat | wc -l

# Count the number of JavaScript LOC
(git ls-files "src/*.js" && git ls-files "src/*.jsx") | xargs cat | wc -l

We ended up writing a GitHub actions bot to write on each PR how many LOC we’d progressed. Here’s the full code if you want to build your own!

name: Count lines of code!

on: push

jobs:
count_loc:
runs-on: ubuntu-latest
steps:
  - name: Checkout
    uses: actions/checkout@v2

  - name: Count TypeScript lines of code
    run: |
      echo "TYPESCRIPT_LOC=$((git ls-files "src/*.ts" && git ls-files "src/*.tsx") | xargs cat | wc -l)" >> $GITHUB_ENV
  - name: Count JavaScript lines of code
    run: |
      echo "JAVASCRIPT_LOC=$(git ls-files "src/*.js" | xargs cat | wc -l)" >> $GITHUB_ENV
  - name: Count Total lines of code
    run: |
      echo "TOTAL_LOC=$((git ls-files "src/*.js" && git ls-files "src/*.ts" && git ls-files "src/*.tsx") | xargs cat | wc -l)" >> $GITHUB_ENV
  - name: Calculate percentage TypeScript
    run: |
      echo "PERCENTAGE_TYPESCRIPT=$(( (${{ env.TYPESCRIPT_LOC }} * 100) / ${{ env.TOTAL_LOC }}))" >> $GITHUB_ENV
  - name: Print LOC
    run: |
      echo "TypeScript LOC: "${{ env.TYPESCRIPT_LOC }}
      echo "JavaScript LOC: "${{ env.JAVASCRIPT_LOC }}
      echo "Total LOC: "${{ env.TOTAL_LOC }}
      echo "Percentage TypeScript: "${{ env.PERCENTAGE_TYPESCRIPT }}
  - name: Add comment to PR
    uses: unsplash/comment-on-pr@master
    env:
      GITHUB_TOKEN: ${{ secrets.DANGER_GITHUB_API_TOKEN }}
    with:
      msg: 'This PR brings our coverage to ${{ env.PERCENTAGE_TYPESCRIPT }}% TypeScript! Only ${{ env.JAVASCRIPT_LOC }} out of ${{ env.TOTAL_LOC }} lines to go.'
    check_for_duplicate_msg: true

Get your free 5 day plan today

Write a response