未経験でも大丈夫！FlowからstrictなTypeScriptへ移行

本稿は [TypeScript Advent Calendar 2020](https://qiita.com/advent-calendar/2020/typescript) 15日目の記事です！

## はじめに
TypeScriptはJavaScriptを用いたアプリケーションをゼロから構築する際には選択しない手はないといっても過言ではありません！
ところが既存のアプリケーションにTypeScriptを導入したい場合、且つ[Flow](https://flow.org/)を利用しているアプリケーションのケースではどのように移行していくべきか？

なかなかFlowや素のJSからTypeScriptへ移行できずにいるという方の為に、実際にJavaScript + Flow のアプリケーションで約300ファイルをTypeScriptに移行した手法を共有したいと思います！

タイトルには「Flowから」とありますが、プレーンなJavaScriptからの移行にも通ずる部分は多くあるはずです！

※移行を実施したのは2019年後半頃になります
※本稿はFlowを否定するものではありません

### 話さないこと
 - TypeScriptの基本的な導入方法
 - TypeScriptの書き方
 - 具体的なリプレイス前後のコード比較

## TL;DR
 - 最初からany禁止にせずに段階的に禁止設定を有効にしていくことで素早く移行する
 - リファクタはせずに型定義の変更のみすることで既存機能への影響を与えないようにする
 - 一括置換やツールを使わずに手動で書き換えていくことで理解を深めていった

## 移行の方針
### 極力、型定義の変更のみに注力する！
既存のFlowによる型定義をTypeScriptでの型の表現に置き換えていくことだけを基本としました。

 - 型定義の変更のみであれば既存の機能への影響は最小限のはずである
 - コード全体を見直すとついリファクタしたい衝動に駆られますが、上記の理由からグッと堪える・・・！
 - 後でリファクタしたい箇所や気になって点はコメントで残したり、チームメンバーへ別途共有
 - まずはTypeScriptへの移行を素早く完了させる

### コードレビューでTypeScriptを学んでもらう
自分を含めTypeScript未経験のメンバーもいる環境だったので、教育も兼ねたコードレビューを通して徐々に理解していってもらいました。

 - 一度に大量の差分を依頼するのではなくある程度キリのいい単位で区切る
 - 一つのプルリクに対して2名のレビュアーをアサイン
 - フロントエンドのメンバー全員が満遍なくコードレビューできるように

## 移行の方法
ソースコードは基本的に**全て手動**で変更していきました・・・！

CLIやIDEの機能で置換したり、FlowからTypeScriptへのマイグレーションツールもある中、敢えて手作業で実施した理由は
前述した通りTypeScriptはほぼ未経験なので自分の手で書き換えていく方がTypeScriptへの理解が深まると考えたからです。

TypeScriptへの理解は充分でサクッと移行してしまいたい場合はわざわざ手動で疲弊する必要もないのかなとは思います！

### 手順
TypeScriptの移行と並行して機能の開発が進んでいるので、一気に完全TypeScript化するのではなく以下の手順で進めました！

1. TS, JSが共存できる状態でmasterへマージ
2. 各開発ブランチでmasterブランチの取り込み -> TS化対応
3. 共存用の設定を破棄し、TSに完全移行
4. any型の駆逐

### tsconfig.jsonの設定
まずはtsconfig.jsonの設定ですが、TSとJSを共存させる為に以下の様にしました。

 - TSからJSをimportすることを許容させるためにtsconfig.jsonでは`allowJs`を`true`に
 - Flowで明示的な型指定がされていない部分が多々あったため、`noImplicitAny`を`false`に
   - 型指定がなく暗黙的にanyに推論されてしまう箇所を全て対応していくと非常にコストがかかる
   - まずはTSへの移行を優先し、後にanyを駆逐していく方針を取った
 - `strict`は`true`に
   - **強くおすすめします！**
   - noImplicitAnyはこの時点では移行の速度重視で止むを得ずfalseにしていますが、他の設定を後からtrueにしてくのはなかなか厳しいかと・・・

```jsonc:tsconfig.json
{
  "compilerOptions": {
    "allowJs": true,
    "strict": true,
    "noImplicitAny": false, // strictをtrueにするとnoImplicitAnyもtrueに設定されるので明示的にfalseを指定
    // ...
  }
}
```

参考: [TypeScript: TSConfig Reference - Docs on every TSConfig option](https://www.typescriptlang.org/tsconfig)

### ESLintの設定
Flow用のESLintルールとTypeScript用のESLintルールを`overrides`設定を利用し
拡張子`.js`と`.ts`でそれぞれで対応するPlugin等の適用を行いました。

```js:.eslintrc.js
module.exports = {
  // ここまで .js, .ts共通のルール
  overrides: [
    {
      files: ['*.js'],
      extends: [
        'plugin:flowtype/recommended',
      ],
      plugins: [
        'flowtype-errors',
      ],
    },
    {
      files: ['*.ts'],
      extends: [
        'plugin:@typescript-eslint/recommended',
      ],
      plugins: [
        '@typescript-eslint',
      ],
      parser: '@typescript-eslint/parser',
      // ...
    },
  ],
};
```

※overridesでのextends指定はESLint v6から可能です！

### エラーが解決できない場合
繰り返しになりますが、初期段階ではまずはTypeScriptへの移行を優先させたかったという背景があるのと
TypeScriptにも慣れ、型パズル力が向上したときに向き合うことを前提として一時的にエラーを回避しました。

これから紹介するエラー回避手段は決して推奨されるものではありません！

#### noImplicitAny
前項のtsconfig.jsonの設定にて記述した通り、暗黙的なany型を一時的に許容

```ts
// 型アノテーションがないのでargはanyで推論される
function foo(arg) {
  bar(arg);
}
```

#### 明示的なany型の許容
[@typescript-eslint/no-explicit-any](https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/docs/rules/no-explicit-any.md)

 - 明示的なany型の指定に関するESLintのルール
 - 無効化しておくことでany型を使ってもESLintエラーにならない
 - どうしても上手く型を定義できないときはanyを指定して逃げる

```ts
function foo(arg: any) {
  bar(arg);
}
```

#### TSエラーの許容
`@ts-ignore`

 - TypeScriptの型エラーを許容させる
 - 利用しているライブラリ側の型定義と整合性が取れない場合など（それはそれで問題なのですが・・・）
 - [@typescript-eslint/ban-ts-comment](https://github.com/typescript-eslint/typescript-eslint/blob/v4.8.2/packages/eslint-plugin/docs/rules/ban-ts-comment.md)を利用し、オプションで`'ts-ignore': 'allow-with-description'`を指定しておくと同一行内にコメントがある場合のみ許容させることができる

```ts
function foo(arg: string) {
  // @ts-ignore barはstringを許容していない
  bar(arg);
}
```

#### Double assertion
[Double assertion](https://basarat.gitbook.io/typescript/type-system/type-assertion#double-assertion)

 - TypeScriptにはアサーションという機能があり、ある型をコンパイラに対して明示的に別の型として認識させることができる機能
 - アサーションは基本的に型の互換性のある時にのみ使えるテクニックですが、Double assertionを使うことでどんな型でも任意の型として指定することができてしまう
 - `@ts-ignore`が次の行に対する型エラーを全て許容させるのに対し、こちらはあくまで型アノテーションとして部分的に型エラーを回避させることが可能

```ts
function foo(arg: string) {
  bar(arg as unknown as number);
}
```

## 移行対応完了後
### Flowの設定削除
今までお世話になったFlow関連ファイルや設定をおもむろに削除していきましょう！
eslintrc.jsのoverridesからFlowの設定を削除するのと合わせて、非有用に応じて`.ts`用の設定はoverridesの外に移動しましょう。

### any型の駆逐
駆逐すべきany型は二種類です！

 - 暗黙的なany
   - tsconfig.jsonのnoImplicitAnyオプションを有効化（strictがtrueの場合はnoImplicitAnyの行ごと削除）
 - 明示的なany
   - @typescript-eslint/no-explicit-anyルールを有効化 

それぞれIssueを立て、ディレクトリ単位などで各メンバーが自主的に取り組めるようにしました。
ここでも**型以外は修正しないこと**というルールを厳守することで機能への影響が出ないようにしました。

楽しく対応できたらいいなーと↓の様なIssueを立てて対応していましたｗ

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/151404/6876e26b-fd44-b34e-5d68-89a41bf60481.png)

## TypeScriptへ移行して
 - 型表現がFlowに比べて非常に柔軟に定義でき、TypeScriptが使える環境であれば今後Flowを使うことはなさそう
 - 先日発表されたGitHubの[Octoverse](https://octoverse.github.com/)でもTypeScriptが4位に急上昇するなど、非常に勢いのある言語を利用できているという開発者体験・満足感
 - 厳格な型チェックによるコードの安心感を得ることができた
 - TypeScriptなしでは生きていけない！

## おわりに
Flowからの移行というタイトルで執筆しましたが、今回お伝えしたかった点としては
一度に全てをやりきるのではなく段階的に移行することでメンバーの言語に対する理解もエンハンスしつつ
素早くアプリケーションに適用していくことができたという内容でした！

少しでもTypeScriptへの移行を検討するきっかけと手助けになればと思います！