Introduce TypeScript to react(js) project with ts-migrate😎
Tags: typescript, react
Published: 2020-09-08 14:15:44

TypeScript has been getting popular since it makes js codebase legit. 

Dropbox did a huge migration!!! This is worth reading.

The Great CoffeeScript to Typescript Migration of 2017
https://dropbox.tech/frontend/the-great-coffeescript-to-typescript-migration-of-2017

Last month Airbnb released a really interesting package.

<blockquote class="twitter-tweet"><p lang="en" dir="ltr">Learn about how we used codemods to accelerate migration from JavaScript to TypeScript at Airbnb. <a href="https://t.co/s1WihkfD1F">https://t.co/s1WihkfD1F</a></p>&mdash; Airbnb Engineering (@AirbnbEng) <a href="https://twitter.com/AirbnbEng/status/1295789793876381696?ref_src=twsrc%5Etfw">August 18, 2020</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>

In this article, I would like to introduce a small case that I tried. 

## Create a new branch
```zsh
git checkout -b feature/ts-migrate

# you can use `switch` if your git version is later 2.23
git switch feature/ts-migrate
```


## Install ts-migrate
```zsh
$ yarn add -D ts-migrate
# or
$ npm install --save-Dev ts-migrate
```


## Run ts-migrate
~~In this step, we need to avoid using `yarn` since there is an issue, so we need to use `npx`.~~
The issue has been fixed(https://github.com/airbnb/ts-migrate/commit/50f1a6cbc08c241c7f6ce822ca999cb197ae1e92)
So, `npx` and `yarn`, both of them are working.

In this article, the target folder is `src`


```zsh
$ npx ts-migrate-full src
Welcome to TS Migrate! :D

This script will migrate a frontend folder to a compiling (or almost compiling) TS project.

It is recommended that you take the following steps before continuing...

1. Make sure you have a clean git slate.
   Run `git status` to make sure you have no local changes that may get lost.
   Check in or stash your changes, then re-run this script.

2. Check out a new branch for the migration.
   For example, `git checkout -b koji--ts-migrate` if you're migrating several folders or
   `git checkout -b koji--ts-migrate-src` if you're just migrating src.

3. Make sure you're on the latest, clean master.
   `git fetch origin master && git reset --hard origin/master`

4. Make sure you have the latest npm modules installed.
   `npm install` or `yarn install`

If you need help or have feedback, please file an issue on Github!

Continue? (y/N)
```

### before
![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/93b5qjwrgqnsuy1qfinl.png)

```zsh
Set a custom path for the typescript compiler. (It's an optional step. Skip if you don't need it. Default path is ./node_modules/.bin/tsc.):
Your default tsc path is ./node_modules/.bin/tsc.

[Step 1 of 4] Initializing ts-config for the "src"...

Config file created at /Users/koji/Documents/github/React-Hooks-StarterFiles/react-hooks-videoplayer/src/tsconfig.json
[test/ts 2249a33] [ts-migrate][src] Init tsconfig.json file
 2 files changed, 69 insertions(+)
 create mode 100644 src/.eslintrc
 create mode 100644 src/tsconfig.json
/Users/koji/Documents/github/React-Hooks-StarterFiles/react-hooks-videoplayer

[Step 2 of 4] Renaming files from JS/JSX to TS/TSX and updating project.json\...

Renaming 21 JS/JSX files in /Users/koji/Documents/github/React-Hooks-StarterFiles/react-hooks-videoplayer/src...
Done.
[test/ts d5675b0] [ts-migrate][src] Rename files from JS/JSX to TS/TSX
 21 files changed, 0 insertions(+), 0 deletions(-)
 rename src/components/{Darkmode.js => Darkmode.tsx} (100%)
 rename src/components/{PlaylistHeader.js => PlaylistHeader.tsx} (100%)
 rename src/components/{PlaylistItem.js => PlaylistItem.tsx} (100%)
 rename src/components/{Video.js => Video.tsx} (100%)
 rename src/components/containers/{App.js => App.tsx} (100%)
 rename src/components/containers/{Playlist.js => Playlist.tsx} (100%)
 rename src/components/containers/{PlaylistItems.js => PlaylistItems.tsx} (100%)
 rename src/components/containers/{WbnPlayer.js => WbnPlayer.tsx} (100%)
 rename src/components/hoc/{withLink.js => withLink.tsx} (100%)
 rename src/components/styles/{GlobalStyle.js => GlobalStyle.ts} (100%)
 rename src/components/styles/{StyledDarkmode.js => StyledDarkmode.ts} (100%)
 rename src/components/styles/{StyledJourney.js => StyledJourney.ts} (100%)
 rename src/components/styles/{StyledPlaylist.js => StyledPlaylist.ts} (100%)
 rename src/components/styles/{StyledPlaylistHeader.js => StyledPlaylistHeader.ts} (100%)
 rename src/components/styles/{StyledPlaylistItem.js => StyledPlaylistItem.ts} (100%)
 rename src/components/styles/{StyledPlaylistitems.js => StyledPlaylistitems.ts} (100%)
 rename src/components/styles/{StyledVideo.js => StyledVideo.ts} (100%)
 rename src/components/styles/{StyledVideoWrapper.js => StyledVideoWrapper.ts} (100%)
 rename src/components/styles/{StyledWbnPlayer.js => StyledWbnPlayer.ts} (100%)
 rename src/{index.js => index.tsx} (100%)
 rename src/{serviceWorker.js => serviceWorker.ts} (100%)
/Users/koji/Documents/github/React-Hooks-StarterFiles/react-hooks-videoplayer

[Step 3 of 4] Fixing TypeScript errors...

forkTSServer
Logs in /var/folders/gj/x6v5vwdx1v7741fdfcxwmr100000gn/T/ts-migrate-log-iiAQGO
TypeScript version: 3.9.7
Initialized tsserver project in 207.623ms.
Start...
[strip-ts-ignore] Plugin 1 of 12. Start...
[strip-ts-ignore] Finished in 53.092ms.
[hoist-class-statics] Plugin 2 of 12. Start...
[hoist-class-statics] Finished in 25.675ms.
[react-props] Plugin 3 of 12. Start...
[react-props] Finished in 15.874ms.
[react-class-state] Plugin 4 of 12. Start...
[react-class-state] Finished in 1.483ms.
[react-class-lifecycle-methods] Plugin 5 of 12. Start...
[react-class-lifecycle-methods] Finished in 10.154ms.
[react-default-props] Plugin 6 of 12. Start...
[react-default-props] Finished in 0.768ms.
[react-shape] Plugin 7 of 12. Start...
[react-shape] Finished in 0.989ms.
[declare-missing-class-properties] Plugin 8 of 12. Start...
[declare-missing-class-properties] Finished in 1517.982ms.
[explicit-any] Plugin 9 of 12. Start...
[explicit-any] Finished in 363.076ms.
[eslint-fix] Plugin 10 of 12. Start...
[eslint-fix] Finished in 1148.936ms.
[ts-ignore] Plugin 11 of 12. Start...
[ts-ignore] Finished in 370.630ms.
[eslint-fix] Plugin 12 of 12. Start...
[eslint-fix] Finished in 193.324ms.
Finished in 3703.120ms, for 12 plugin(s).
Writing 21 updated file(s)...
Wrote 21 updated file(s) in 3.488ms.
rm: src/.eslintrc.*: No such file or directory
```

If everything goes well, `ts-migrate` will convert `.js` files to `.ts` and `.tsx`. In addition, create `tsconfig.json` and `.eslintrc.js`

### after
![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/a9o548olt0ho7gxw7emk.png)

**Note:`tsconfig.json` and `.eslintrc.js` are generated under `src`.**

## Install typescript and @types files
If `ts-migrate` did this step automatically, that would be amazing, but actually, right now it doesn't. We need to install `typescript` and `@types` files.
If there is no @type file, you will need to create `global.d.ts` for packages that don't support typescript.
```zsh
$ yarn add -D typescript
```

## Install ts-loaderwebpack.config.js
The react app I used in this article uses webpack to bundle `.js` files. The project needs `ts-loader` to support typescript.

Now, it's time to open `src` folder with an editor. I use Visual Studio Code.


## Remove ts-something
`ts-migrate` has put `ts-error` and `ts-migrate(7016) FIXME` to avoid errors.

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/8wa8frvjjm4vmvm56wh5.png)

Most cases will be solved by installing @types files for npm packages so what we need to do is to remove comments.

## Modify webpack.config.js
This project is using webpack to bundle js files, but now files are `.ts`, or `.tsx` so need to modify webpack configurations a little bit.
The most important thing is to switch from `.js` to `.tsx` or `.ts`

For example, `entry`, `extensions` and using `ts-loader` etc.

```js
/* eslint-disable @typescript-eslint/no-var-requires */
const path = require('path');
const autoprefixer = require('autoprefixer');
const HtmlWebpackPlugin = require('html-webpack-plugin');

module.exports = {
  mode: 'development',
  entry: './src/index.tsx',
  output: {
    path: path.resolve(__dirname, 'dist'),
    filename: 'bundle.js',
    chunkFilename: '[id].js',
    publicPath: '',
  },
  devtool: 'inline-source-map',
  devServer: {
    contentBase: './',
    open: true,
  },
  module: {
    rules: [
      {
        test: /\.tsx?$/,
        use: 'ts-loader',
      },
      // {
      //     test: /\.(gif|png|svg|jpg)$/,
      //     loader: "url-loader"
      // },
      {
        test: /\.js$/,
        loader: 'babel-loader',
        exclude: /node_modules/,
      },
      {
        test: /\.css$/,
        exclude: /node_modules/,
        use: [
          { loader: 'style-loader' },
          {
            loader: 'css-loader',
            options: {
              modules: {
                localIdentName: '[name]__[local]___[hash:base64:5]',
              },
              sourceMap: true,
            },
          },
          {
            loader: 'postcss-loader',
            options: {
              ident: 'postcss',
              plugins: () => [autoprefixer({})],
            },
          },
        ],
      },
      {
        test: /\.(svg|png|jpe?g|gif)$/,
        loader: 'url-loader?limit=10000&name=img/[name].[ext]',
      },
    ],
  },
  resolve: {
    extensions: ['.ts', '.tsx', '.js', '.json'],
  },
  plugins: [
    new HtmlWebpackPlugin({
      template: __dirname + '/public/index.html',
      filename: 'index.html',
      inject: 'body',
    }),
  ],
};
```

## Update npm script
I just added `.ts` and `.tsx` as a target file like below.

```json
  "scripts": {
    ...
    "lint": "eslint 'src/**/*.{ts,tsx}'",
    "lint:prettier": "prettier --check './src/**/*.{js,ts,tsx}'"
  }
```

## Small modification
If you see some `.tsx` or `.ts` code, you notice that `ts-migrate` just use `any` for all variable which isn't good but it totally makes sense as a migration tool's behavior since `ts-migrate` doesn't know about your project.

In this case, I know the project itself since I wrote this code recently which means I still remember the variables' types. So I updated `any` to a proper data-type.


## Conclusion
In my opinion, `ts-migrate` is very useful. It will make a codebase legit and reduce the migration cost a little bit.


I think if you don't think that it's not time to do the migration, still trying `ts-migrate` can be useful. 

For example, I tried `ts-migrate` for an open-source library which is written in javascript and saw a bug that a function defines 2 parameters, but it is given 3 parameters(of course, the extra one isn't used). You may find out this kind of issue on your codebase then you can back to js to fix the issues you detected lol.