Many years ago, Flow was all the rage. It added these mysterious things called ‘types’ to JavaScript. Wow!
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).
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