開発・技術選定

既存の JavaScript に TypeScript を後から入れる — 止めずに移行する手順

既存プロジェクトに TypeScript を入れたい。ただし開発は止められない
このときに使っている段階的な移行手順です。

原則:一度に厳しくしない

最初から strict: true にすると、数百件のエラーが出て手が止まります。
エラーゼロの状態を保ったまま、少しずつ締めていきます。

{
  "compilerOptions": {
    "allowJs": true,        // .js を許可(これが最重要)
    "checkJs": false,       // まだ .js はチェックしない
    "strict": false,
    "noEmit": true
  }
}

allowJs により、既存の .js を1行も触らずに TypeScript の設定を導入できます。

段階1:新しく書くファイルだけ .ts にする

既存コードには触りません。新規ファイルのみ .ts で書きます。
これだけで、数か月後には型のあるコードが自然に増えていきます。

段階2:よく使われる型を先に定義する

APIレスポンスなど、多くの場所から参照されるものから型を作ります。

export interface Order {
  id: number;
  status: "pending" | "paid" | "cancelled";
  total: number;
  createdAt: string;   // ISO8601
}

ステータスをユニオン型にしておくと、
タイプミスがコンパイル時に落ちるようになります。効果が大きい割に安価です。

段階3:影響範囲の小さいファイルから .ts に変える

依存されていない末端のファイル(ユーティリティなど)から変換します。
中心にあるファイルを最初に変えると、影響が全体に波及して止まります。

# どこから参照されているか調べてから決める
grep -rn "from './utils/format'" src/ | wc -l

段階4:徐々に厳しくする

エラーがゼロになったら、1つずつ有効化します。

"noImplicitAny": true,          // まずこれ
"strictNullChecks": true,       // 次にこれ(効果が最大)

strictNullChecks は最も多くのバグを見つけますが、
最も多くのエラーも出ます。他が片付いてからにします。

any を許容する場所を決める

現実には any を残さざるを得ない箇所が出ます。
放置ではなく、意図を書いて残すようにしています。

// TODO(2023-03): レガシーAPIのレスポンス。型定義を起こす
const legacy = res.data as any;

検索できる印を付けておくと、後で棚卸しできます。

まとめ

  1. allowJs で既存コードを触らずに導入する
  2. 新規ファイルだけ .ts にする
  3. 共通の型(APIレスポンス)から定義する
  4. 末端のファイルから変換する
  5. noImplicitAnystrictNullChecks の順で締める

「エラーゼロの状態を維持したまま進む」のが、途中で挫折しないための条件でした。