ts-migrate使ってみた

今回は、airbnbが提供するjs→tsに移行できるツール`ts-migrate`を触ってみるという記事になります。
試してみたいと思って結構経ってしまいましたが、、、

簡単に触ってみたので記事にしてみました。

airbnbによると同社のプロジェクトで１日に50,000行以上のコードを変換できたとか。。。。


### インストール

```sh
$ yarn add ts-migrate -D
```
これだけ！

### 実行

今回は手元で簡単にどういうものか知りたかったので、
下記のコンポーネントに対してts-migrateを使用すると、どのようなマイグレート処理が行われるのか確認してみたいと思います。

```jsx:components/Sample.jsx
import React from 'react';

const Sample = () => {

  const [num, setNum] = React.useState(0);

  const handleAdd = () => {
    setNum((n) => n++)
  }

  return (
    <div>
      <div>{num}</div>
      <button onClick={handleAdd} />
    </div>)

}

export default Sample;

```

helpでts-migrateコマンドを確認しましょう

```sh
$ npx ts-migrate -- --help

npm run ts-migrate -- <command> [options]

Commands:
  npm run ts-migrate -- init <folder>       Initialize tsconfig.json file in <folder>
  npm run ts-migrate -- rename <folder>     Rename files in folder from JS/JSX to TS/TSX
  npm run ts-migrate -- migrate <folder>    Fix TypeScript errors, using codemods
  npm run ts-migrate -- reignore <folder>   Re-run ts-ignore on a project

Options:
  -h,  -- help      Show help
  -i,  -- init      Initialize TypeScript (tsconfig.json) in <folder>
  -m,  -- migrate   Fix TypeScript errors, using codemods
  -rn, -- rename    Rename files in <folder> from JS/JSX to TS/TSX
  -ri, -- reignore  Re-run ts-ignore on a project

Examples:
  npm run ts-migrate -- --help                Show help
  npm run ts-migrate -- init frontend/foo     Create tsconfig.json file at frontend/foo/tsconfig.json
  npm run ts-migrate -- rename frontend/foo   Rename files in frontend/foo from JS/JSX to TS/TSX

```

ざっとみると主要な機能としては二つ
- js→tsにリネーム
- `any` `@ts-expect-error`でコンパイルエラーの出る箇所を抑え込む

なるほどなるほど！

この二つをいっぺんにやってくれるのが下記のコマンドらしい

```sh
npx ts-migrate-full <folder>
```

早速やってみよう

```
$ npx ts-migrate-full components
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 ts-migrate` if you're migrating several folders or
   `git checkout -b ts-migrate-components` if you're just migrating components.

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)
```

わぁーこの確認はありがたいね！

1~4全て準備できていればyで実行

```
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.):
```
コンパイラーの読み込み先が問題なければEnterでスキップ！

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

fatal: not a git repository (or any of the parent directories): .git
/Users/xxxx/ts-migrate-sample

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

No JS/JSX files to rename.
fatal: not a git repository (or any of the parent directories): .git
/Users/xxxx/ts-migrate-sample

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

forkTSServer
Logs in /var/folders/g2/_1xblgd90hn43qscs_s7mhkxw987nf/T/ts-migrate-log-098qt8
TypeScript version: 4.0.2
Initialized tsserver project in 171.789ms.
Start...
[strip-ts-ignore] Plugin 1 of 13. Start...
[strip-ts-ignore] Finished in 1276.098ms.
[hoist-class-statics] Plugin 2 of 13. Start...
[hoist-class-statics] Finished in 1.526ms.
[react-props] Plugin 3 of 13. Start...
[react-props] Finished in 2.641ms.
[react-class-state] Plugin 4 of 13. Start...
[react-class-state] Finished in 0.191ms.
[react-class-lifecycle-methods] Plugin 5 of 13. Start...
[react-class-lifecycle-methods] Finished in 1.264ms.
[react-default-props] Plugin 6 of 13. Start...
[react-default-props] Finished in 0.322ms.
[react-shape] Plugin 7 of 13. Start...
[react-shape] Finished in 0.341ms.
[declare-missing-class-properties] Plugin 8 of 13. Start...
[declare-missing-class-properties] Finished in 66.787ms.
[member-accessibility] Plugin 9 of 13. Start...
[member-accessibility] Finished in 1.573ms.
[explicit-any] Plugin 10 of 13. Start...
[explicit-any] Finished in 11.987ms.
[eslint-fix] Plugin 11 of 13. Start...
[eslint-fix] Finished in 83.200ms.
[ts-ignore] Plugin 12 of 13. Start...
(node:91046) [ESLINT_PERSONAL_CONFIG_SUPPRESS] DeprecationWarning: '~/.eslintrc.*' config files have been deprecated. Please remove it or add 'root:true' to the config files in your projects in order to avoid loading '~/.eslintrc.*' accidentally. (found in "../../.eslintrc.js")
[ts-ignore] Finished in 7.478ms.
[eslint-fix] Plugin 13 of 13. Start...
[eslint-fix] Finished in 1.183ms.
Finished in 1458.891ms, for 13 plugin(s).
Writing 1 updated file(s)...
Wrote 1 updated file(s) in 0.555ms.
fatal: not a git repository (or any of the parent directories): .git
/Users/xxxx/ts-migrate-sample

[Step 4 of 4] Checking for TS compilation errors (there shouldn't be any).

./node_modules/.bin/tsc -p components/tsconfig.json

---
All done!

The recommended next steps are...

1. Sanity check your changes locally by inspecting the commits and loading the affected pages.

2. Push your changes with `git push`.

3. Open a PR!
```

Done!

さぁファイルを見てみましょう

```tsx:components/Sample.tsx
// @ts-expect-error ts-migrate(7016) FIXME: Try `npm install @types/react` if it exists or add... Remove this comment to see the full error message
import React from 'react';

const Sample = () => {

  const [num, setNum] = React.useState(0);

  const handleAdd = () => {
    setNum((n: any) => n++)
  }

  return (
    // @ts-expect-error ts-migrate(7026) FIXME: JSX element implicitly has type 'any' because no i... Remove this comment to see the full error message
    <div>
      {/* @ts-expect-error ts-migrate(7026) FIXME: JSX element implicitly has type 'any' because no i... Remove this comment to see the full error message */}
      <div>{num}</div>
      {/* @ts-expect-error ts-migrate(7026) FIXME: JSX element implicitly has type 'any' because no i... Remove this comment to see the full error message */}
      <button onClick={handleAdd} />
    {/* @ts-expect-error ts-migrate(7026) FIXME: JSX element implicitly has type 'any' because no i... Remove this comment to see the full error message */}
    </div>
  )
}

export default Sample;

```

`codemodを使ってのany型に置換え`、`FIXME: Try npm install @types/react`のような丁寧なコメントなども追加されていますね！

ちなみにts-migrateを実行すると指定したディレクトリ直下に `tsconfig.json`が作成されていました。


### まとめ
大まかな移行はものすごく簡単にできたので感動しました。
ですが、完全な移行まではもう少し調整が必要な感じがしました！
コンパイルまわり(webpack), tsconfigの調整それから各ファイルでマイグレート時についたコメントに対しての修正が必要かぁ＞＜

ファイル自体に書き換え対象がコメントでわかるという点は移行作業がよりスピーディーに進められるし、
大規模プロジェクトでも負担が少なく済みそうな気がしました。