40万行のコードをFlowからTypeScriptに移行した

公開:2020/12/17
更新:2020/12/18
2 min読了の目安(約2400字TECH技術記事

この記事は TeamSpirit Advent Calendar 2020 の 18日目の記事です。

私が携わっているプロダクトで約40万行のコードをFlowからTypeScriptに移行しました。
移行の背景や手順を書いていこうと思います。

背景

3rd Party Library における Flow の型定義の資産の少なさ

Flow の型定義レポジトリである flow-typed には、約 850 のライブラリの型定義があります。
一方、 TypeScript の型定義レポジトリである DefinitelyTyped には約 7500 のライブラリの型定義があります。
TypeScript ではこれらの資産を使い、自前で型定義をする工数を削減しながらより型安全の恩恵を享受することができます。

Flow の破壊的変更の多さ

Flow はまだ 0.x.x 系 なため、バージョンアップの時に Breaking Change が頻発します。
Breaking Change の度に数百のエラーが発生し、それに追従するのはかなり辛かったです。

TypeScript のコミュニティの勢い


NPM trends を見ても TypeScript のコミュニティの勢いは圧倒的です。
各所で言われているように ModenJavaScript ≒ TypeScript という状態になってきています。
コミュニティが大きいというのは上記2つのメリットの所以ともなっています。
他にも開発が活発であったり、情報が得やすいなどの利点があります。
また、エンジニアの採用においても TypeScript の仕事というのは有利になるだろうと考えました。

Flow のパフォーマンスの低さ

ブランチの切替の度に平均 100sec 程の Language Server の起動を待たないといけませんでした。

TypeScript の型の表現力の高さ

TypeScript と Flow では基本的な型の表現力は変わりませんが、 TypeScript には Conditional Types や Mapped Types や TypeScript 4.1 で追加された Template Literal Types などの高度な機能があります。
それらを使うことでより正確なインターフェイスを表現することができます。

どのように移行するか

プロダクトは4つのドメインチームで開発しています。
移行期間中に全チームが機能開発を止められるわけではないので、一度にすべてのコードを移行するという手段は取れませんでした。
そのため、 TypeScript と Flow のコードを共存できる環境を構築し、各チームのタイミングで担当のコードを移行をしてもらうようにしました。
また、 Production コードに影響する修正をしないというルールにしました。
3rd party library の型定義が増えるなどの理由により今まで露呈していなかったミスが発見できたとしても、移行中にロジックの修正してしまうとデグレードしてしまう可能性があるためです。

共存環境の構築

TypeScript の build を tsc で行うか babel の plugin で行うかで違うので両方の例を書きます。

tsc で行う場合:

// webpack.config.js
module.exports = {
  module: {
    rules: [
      {
        test: /\.jsx?$/,
        include: /src/,
        loader: 'babel-loader',
      },
      {
        test: /\.tsx?$/,
        include: /src/,
        loader: 'ts-loader',
      },
    ],
  },
}

babelで行う場合:

//babel.config.js
module.exports = {
  presets: [
    '@babel/preset-flow',
  ],
  overrides: [
    {
      test: [
        './src/**/*.ts',
      ],
      presets: [
        '@babel/preset-typescript',
      ],
    },
  ],
};

コード変換

Flow から TypeScript への変換は https://github.com/Khan/flow-to-ts を使いました。
試しに使ってみるとほとんどのコードはこれで変換可能だったのでこれにしました。
変換できないコードのパターンがあればライブラリを拡張するか、人力で変換します。

これから

Flow から TypeScript への移行は一旦完了しましたが、これで終わりではありません。
移行をスムーズに行うために現在は "noImplicitAny" などのオプションが無効になっていたり、危険な型アサーションが残っているのでそれらを消して改善していく必要があります。
それらの作業が終わってようやく型安全の恩恵をフルで享受できると思うのですが、この作業は地道にやっていくしかないので先は長そうですね...。