てくすた

ピクスタ株式会社のエンジニア・デザイナーがつづるよもやまテクニカルブログです

Flow から TypeScript に移行しました

こんにちは、開発部の id:yszk0123 です。最近、フロントエンドで使っていたツールを Flow から TypeScript に移行したので、そのお話をしたいと思います。

背景

一年半ほど前にとあるページを React に移行したのですが、その際に、型チェックツールとして Flow を採用しました。 採用の理由は、簡単に導入できて、いざとなれば簡単に捨てられるからです。 それからしばらく運用する中で、ある程度の規模であれば型の有用性を実感できたため、本格導入することになりましたが、次に挙げるような理由により、Flow をやめて TypeScript に移行しました。

理由

Flow の問題

1. 型定義ファイルの管理が複雑

ライブラリの型定義ファイルの管理がそこそこ複雑です。 flow-typed という型定義の管理ツールを使えば管理はできますが、インストールしたファイルを git で管理するため、レビューを行う際に余計な差分が入ってきます。 また、フロントエンド開発に慣れていない人もいるため、必要なツールの知識が増えるのも欠点でした。TypeScript のように npm install で完了だと、説明の手間が省けます。

2. 型定義ファイルが少ない

型定義の不足も懸念点でした。 TypeScript から Flow の型定義を生成するツールもありますが、実用にはまだ早いと感じました。1

3. エラーメッセージが難しい

エラーメッセージが難しいのも難点でした。TypeScript のエラーはそこそこ分かりやすいのですが、Flow のエラーを読み解くには時間がかかりました。2

4. コミュニティの規模

規模はやはり TypeScript の方が大きいです。 社内用のドキュメントはある程度まとめているものの、フロントエンドは変化が早いため、網羅することはできません。 必要な情報は大抵日本語でも手に入るので、新しい人が入ってきたときも便利です。

TypeScript の改善

1. 型チェックの改善

型チェックの厳しさも Flow と遜色なくなってきました。

2. 段階的に導入できる仕組みも整ってきた (JavaScript と共存可能)

はじめに Flow を選んだ一番の理由です。Flow を導入した当時は、TypeScript を使おうとすると一気に置き換える必要があり、部分的に導入するのは困難でした。 現在では、allowjscheckjs といったオプションを付けることで、段階的な導入が行いやすくなっています。 strict のように、チェックを段階的に厳しくするオプションもできるため、時間のあるときに少しずつ強化していくこともできます。

3. 開発速度

開発速度が早く、ここ一年で驚くほど使いやすくなっていました。 コンパイラ自体が TypeScript で書かれているということもあり、Flow に比べて開発に参加するハードルが低いのも良いところだと思います。

その他

1. 手段が整った

日頃の改善活動により、フロントエンドのテストや Storybook による手軽な確認環境が整備されたため、多少大きな変更でも、自信を持って行える準備ができました。

2. エディタのサポート

普段からガリガリとフロントエンドの開発を行うのであれば、WebStorm などを使ってもらうのが良いのでしょうが、時々触る程度の人に購入してもらうのはためらわれます。VS Code を入れてもらえば、設定無しで最低限の環境はサクッと整うのは TypeScript の利点でした。3

3. タイミング

これは置き換える理由ではないですが、遅かれ早かれ移行するのであれば、Flow を使っている箇所が少ないうちに行った方が負担が少ないです。

移行手順

特別工夫したところもなく、手作業 (grep) で地道に修正しました。 自動変換ツールは探したものの、実用的なツールはまだありませんでした4。 テストを実行しつつ、足りないところは Storybook を見ながら進めたため、スムーズに進めることができました。 とはいえ、いくつか面倒だった部分もあります。

面倒だったところ

default import

TypeScript のコンパイラオプション (tsconfig.json のcompilerOptions.module) で commonjs を使う場合は、default import の挙動の違いにも気をつける必要があります。5

import styles from './Button.module.css'; // NG
import * as styles from './Button.module.css'; // OK

イベント・イベントハンドラの型

イベントハンドラの型定義が結構違うので迷いました。 例えば、Flow では import なしに SyntethicMouseEvent という型が使えますが、TypeScript では明示的な import が必要です。名前の違いも、ある程度推測できるものの、やや面倒でした。

// Flow
interface Props {
  onClick: SyntheticMouseEvent<HTMLElement>
}

// TypeScript
import * as React from 'react';

interface Props {
  onClick: React.MouseEventHandler<HTMLElement>;
}

他のツールとの組み合わせ

特に ESLint との組み合わせが少々面倒でした。 今から新規で導入するなら Prettier + TSLint だけでも大丈夫かもしれません。 Babel を使っている場合は、やはり Flow の方が導入は簡単です。6 7 まだ面倒な部分も多いですが、以前と比べると格段に楽になってきたと思います。

差分とコンフリクト

リポジトリが小さく分割されている場合・モジュールが独立している場合は、段階的に置き換えられるため問題ないのですが、そうでない場合は気をつける必要がありました。 この場合は、以下のような構成に直しておくのが良いと思います。

src/
  index.js (moduleA.ts, moduleB.js を import)
  moduleA.ts (TypeScript に置換済み)
  moduleB.js (Flow を使っているコード)

感想

様々な観点で比較した上で、今回は Flow から TypeScript に移行することにしましたが、常に TypeScript が良い選択肢だとは限りません。 また、必ずしも型が有用とは限らないので、要件やチーム構成に応じて使い分けることが重要だと思います。

すべてを型でガチガチに固めてしまう必要もないですし、不要と分かれば JavaScript に戻るのも簡単です。必要に応じて気楽に導入してみてはいかがでしょうか。 似たような記事は他の方も書いていると思いますが、TypeScript の導入・移行に迷っている方の参考になれば幸いです。

* * * * *

ピクスタではエンジニアを募集しています!

recruit.pixta.co.jp

recruit.pixta.co.jp


  1. 互いにカバーしている範囲も違うため、型定義の相互変換の実現は難しいと思いました。

  2. まだ TypeScript の方が読みやすい印象ですが、Flow も 0.66.0 からエラーメッセージが大幅に改善されています。

  3. Flow でも Atom + Nuclide のような組み合わせはありますが、VS Code と比べると動作がもっさりする印象でした。また、Nuclide は Atom の使い勝手を大きく変えてしまうため、普通の Atom と併用したい人にとってはやや不便だと感じました。

  4. https://github.com/bcherny/flow-to-typescript はまだ実験段階で、開発も止まっているようです。自分でツールを作っても良かったのですが、構文以外に React の型定義の変換なども含めると、労力に見合わないと思いやめました。

  5. https://qiita.com/terrierscript/items/56d2cc15f76df50dfee7

  6. 今回は Babel 7 がベータ版のため採用を見送りましたが、Babel 7 であれば @babel/preset-typescript が使えるので、導入はさらに楽になると思います。

  7. TypeScript により Babel の必要性は薄れたものの、AST を触る場合はやはり Babel の周辺ツールが便利です。Babel がいい感じで TypeScript と他のツールをつないでくれることを期待しています。