JSからTSへの移行ツール、ts-migrateを試してみた


airbnbからjs→ts移行のツールが出ましたね…！

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

早速使ってみたので、その手順と使用感について書きたいと思います。

コード量によるのですがかなり簡単にできるように感じました。
TSにしたいけど面倒やなぁって感じてる方に是非:eyes:

## 移行前のファイル
簡単なテンプレを使って試してみました！
これをts化させていきます。

```実行前.
├── src
│   ├── img
│   │   └── common
│   │       └── favicon.png
│   ├── js
│   │   └── app.js
│   ├── jsx
│   │   ├── components
│   │   │   └── styledComponent
│   │   │       └── GlobalStyle.jsx
│   │   ├── containers
│   │   │   ├── Hoge.jsx
│   │   │   └── Top.jsx
│   │   └── index.jsx
│   └── pug
│       └── index.pug
└── webpack.config.js
```

##インストール→実行まで

###インストール

```
$ npm install --save-dev ts-migrate
```

###実行の前に

実行してみようとしたら以下のような注意文が出てきます！
（個人的にこの親切さにちょっと感動でした）

```
$ 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 ts-migrate` if you're migrating several folders or
   `git checkout -b 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)
```

なので以下の手順で準備します。

##### ・ 現在の変更分をpushしておく
##### ・ ts-migration用にブランチを作って、移動する
`git checkout -b ts-migrate`
##### ・ 新しく作成したブランチを最新の状態にする
`git fetch origin master && git reset --hard origin/master`
##### ・ node_modulesも最新の状態にしておく
`npm install` or `yarn install`

これで実行の準備ができました！

### 実行

```
$ npx ts-migrate-full src
Welcome to TS Migrate! 
:
:
Continue? (y/N) y

```

こんなの出てきます。ts-compilerの読み込み先を変更することができます。
必要なければエンター！

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

```
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/***/private/ts-migrate-test/src/tsconfig.json
fatal: not a git repository (or any of the parent directories): .git
/Users/***/private/ts-migrate-test/ts-migration

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

Renaming 5 JS/JSX files in /Users/***/private/ts-migrate-test/src...
Done.
fatal: not a git repository (or any of the parent directories): .git
/Users/***/private/ts-migrate-test

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

forkTSServer
Logs in /var/folders/cm/9m82j_0n7dj6k0lgkkwvfc700000gn/T/ts-migrate-log-fgvI9i
TypeScript version: 3.9.7
Initialized tsserver project in 234.291ms.
Start...
[strip-ts-ignore] Plugin 1 of 12. Start...
[strip-ts-ignore] Finished in 40.226ms.
[hoist-class-statics] Plugin 2 of 12. Start...
[hoist-class-statics] Finished in 17.350ms.
[react-props] Plugin 3 of 12. Start...
[react-props] Finished in 4.123ms.
[react-class-state] Plugin 4 of 12. Start...
[react-class-state] Finished in 0.567ms.
[react-class-lifecycle-methods] Plugin 5 of 12. Start...
[react-class-lifecycle-methods] Finished in 11.313ms.
[react-default-props] Plugin 6 of 12. Start...
[react-default-props] Finished in 3.017ms.
[react-shape] Plugin 7 of 12. Start...
[react-shape] Finished in 1.363ms.
[declare-missing-class-properties] Plugin 8 of 12. Start...
[declare-missing-class-properties] Finished in 2260.641ms.
[explicit-any] Plugin 9 of 12. Start...
[explicit-any] Finished in 89.735ms.
[eslint-fix] Plugin 10 of 12. Start...
[eslint-fix] Finished in 1898.320ms.
[ts-ignore] Plugin 11 of 12. Start...
[ts-ignore] Finished in 293.852ms.
[eslint-fix] Plugin 12 of 12. Start...
[eslint-fix] Finished in 83.437ms.
Finished in 4706.702ms, for 12 plugin(s).
Writing 4 updated file(s)...
Wrote 4 updated file(s) in 1.213ms.
fatal: not a git repository (or any of the parent directories): .git
/Users/***/private/ts-migrate-test

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

./node_modules/.bin/tsc -p src/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!
```

完了！！

このようになりました:point_down:
jsファイルがtsに、jsxファイルがtsxになってます。
また、指定したディレクトリ直下に `tsconfig.json`が設置されます。

```実行後.
├── src
│   ├── img
│   │   └── common
│   │       └── favicon.png
│   ├── js
│   │   └── app.ts
│   ├── jsx(→ディレクトリ名は変わらないので自分で変更必要です）
│   │   ├── components
│   │   │   └── styledComponent
│   │   │       └── GlobalStyle.tsx
│   │   ├── containers
│   │   │   ├── Hoge.tsx
│   │   │   └── Top.tsx
│   │   └── index.tsx
│   ├── pug
│   │   └── index.pug
│   └── tsconfig.json
└── webpack.config.js

```



##調整

大まかな移行は完了しましたが、まだまだ調整は必要です！
設定になるので人それぞれではありますが、今回使用したコードで調整していきます。

###調整方法について
こちらのサイトが非常に参考になります！
[TypeScript Deep Dive](https://typescript-jp.gitbook.io/deep-dive/type-system/migrating)

> - tsconfig.jsonを追加する
> - ソースコードの拡張子を .jsから.tsに変更する。any型を使ってエラーを抑制する
> - 新しいコードはTypeScriptで記述し、できるだけanyを使わないようにする
> - 古いコードに戻り、型アノテーションを追加し、バグを修正する
> - サードパーティ製JavaScriptコードの型定義を使用する

ts-migrateでは2番目(拡張子 .jsから.tsに変更 / any型を追加して抑制)まで自動でやってくれます！
既存のコードにはcodemodを使って基本的にはanyがつくようになっているみたいです！（すご）

### typescript , tsloaderのinstall
```
$ npm i -D typescript ts-loader
```

### コンパイルの調整
####webpack.config.js

```【調整前】webpack.config.js
mode: 'development',
  entry: {
    user: [`${path.join(__dirname, 'src')}/jsx/index.jsx`]
  },
  output: {
    path: `${__dirname}/${options.mode === 'development' ? 'dist' : 'build'}/`,
    filename: 'js/[name].js',
    publicPath: '/'
  },
  module: {
    rules: [
      {
        test: /\.js[x]?/,
        exclude: /node_modules/,
        use: {
          loader: 'babel-loader'
        }
      },
resolve: {
    modules: [`${__dirname}/src`, 'node_modules'],
    extensions: ['.js', '.jsx']
  },
```

以下のポイントを変更していきます

・エントリーポイント (index.jsx→index.tsxに)
・読み込まれるファイル(js,jsx→ts,tsxに)
・loader(babel-loader→ts-loaderに)
・resolve/extensions(ts,tsxの追加)

```【調整後】webpack.config.js
mode: 'development',
  entry: {
    user: [`${path.join(__dirname, 'src')}/jsx/index.jsx`]
  },
  output: {
    path: `${__dirname}/${options.mode === 'development' ? 'dist' : 'build'}/`,
    filename: 'js/[name].js',
    publicPath: '/'
  },
  module: {
    rules: [
      {
        test: /\.ts[x]?/,
        exclude: /node_modules/,
        use: {
          loader: 'babel-loader'
        }
      },
resolve: {
    modules: [`${__dirname}/src`, 'node_modules'],
    extensions: ['.js', '.ts', '.tsx']
  },
```

####tsconfig.json
とりあえず以下のように設定しました！(ほぼデフォルトです）


```tsconfig.json
{
  "compilerOptions": {
    "target": "es5",
    "module": "commonjs",
    "jsx": "react",
    "strict": true,
    "noImplicitAny": true,
    "moduleResolution": "node",
    "esModuleInterop": true,
    "skipLibCheck": true,
    "forceConsistentCasingInFileNames": true
  }
}

```

設定に関しては詳しくはこちらの方の記事がすごくわかりやすいです！
https://qiita.com/filunK/items/429a060da20613a1dd9e

上記のように設定しましたが"noImplicitAny"に関してはfalseにしておいてもいいのでは？と思っています。
(↓緩いTSのススメ/参考記事です)
https://employment.en-japan.com/engineerhub/entry/2019/04/16/103000


### 各ファイルの調整

このようにここの部分でこんなエラー出るよ〜って教えてくれます！（例えば@type/**のインストールしなさいよ、とか）
なのでそれに沿ってエラーを潰していく作業です。

![スクリーンショット 2020-08-25 10.59.21.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/325594/d7983cf9-1758-e878-138c-4ec6c64ae244.png)

今回のファイルだと`npm install @types/react @types/react-dom @types/react-router-dom`などなど。

ちなみに
`@ts-expect-error`はts3.7からの[新機能](https://www.typescriptlang.org/docs/handbook/release-notes/typescript-3-9.html)で
- コンパイルエラーを無視させる
- 主にテスト時に不正な使い方をしたいときなどに使える
- `@ts-expect-error` はunusedだと怒られる

なのでエラーを消したら`@ts-expect-error`の部分もまとめて消していけばOKです。


全ファイルエラーつぶせば移行完了です！

[やってみたときのGitHub](https://github.com/Sotq17/ts-migrate-test)

## 追記（2021/12/27)

実際動いている中規模のプロジェクトで試してみると、@type/** のインストールが大変でした。
[typesync](https://github.com/jeffijoe/typesync)というライブラリを使うとすごく楽に出来たため追記しておきます！

[こちらの方の記事](https://qiita.com/yamadashy/items/225f287a25cd3f6ec151)がすごくわかりやすいです！

##感想

少ないファイルではありますが迷いながらも30分もかからずに終わりました。
自分で書き換えなきゃいけない箇所が一目でわかるので、大きいプロジェクトだともちろん時間かかりますが、あまりストレスなく移行できそうです。

これでTS化の波にブーストがかかるのでは…！



## 参照

[ts-migration 公式](https://github.com/airbnb/ts-migrate)
[ts-migration 公式使い方](https://github.com/airbnb/ts-migrate/tree/master/packages/ts-migrate)
[ts-migration 公式BLOG](https://medium.com/airbnb-engineering/ts-migrate-a-tool-for-migrating-to-typescript-at-scale-cd23bfeb5cc)