自作のOSSライブラリをFlowからTypeScriptに全面移行した理由と所感

2019-01-21

パラメーター調整用のJavaScriptライブラリ「Tweakpane」について、FlowからTypeScriptに全面移行したので、その理由と所感を。

3行まとめ

  • 業界におけるFlowのマイナーさ・不人気を憂い、調査も兼ねてTypeScriptへ移行してみた
  • 型の表現力自体はそれほど変わらず、周辺ツールのサポートも特に問題なし
  • 複雑だったパッケージの依存関係がシンプルになり、minify後のサイズも小さくなっていい感じ

JavaScriptと型

JavaScriptは明示的な型を持たないプログラミング言語です。コードを気軽に書き捨てられる寿命の短い案件ならそれでも問題ないのですが、大規模かつ長期的に保守運用するプロジェクトには正直不向きでしょう。

JavaScriptに型を導入するための選択肢として挙がるのがFlowあるいはTypeScriptですが、それぞれアプローチが異なっています。

Flow

FlowはFacebookの主導するプロジェクト。素のJavaScriptをベースに、型情報を付加できるように拡張した記法です。

“Flow is a static type checker for JavaScript” と謳うとおり、基本的にはJavaScriptであり、公式のツールを利用すれば型情報を取り除いてプレーンなJavaScriptに戻せるのがポイントです。

TypeScript

もう一方のTypeScriptは、Microsoftが主導するプロジェクト。

“TypeScript is a typed superset of JavaScript that compiles to plain JavaScript” とあるとおり、JavaScriptを拡張した別言語という位置づけで、コンパイルした結果としてJSファイルを得ることができます。

業界におけるFlowの立ち位置と不安な先行き

個人的には、TypeScriptは数年前に触れたあとしばらく縁がなく、最近業務でお手伝いしているプロジェクトではFlowを本格的に利用している状況でした。Flowを辞めるほどの致命的な理由もなくここまで使ってきましたが(小さな問題点はたくさんある)、ある日流れてきたアンケート結果を見て先行きに不安を感じはじめます。2枚目の画像に注目。

Flowの利用者がそれほど多くないことは承知していたものの、既存ユーザーの評判もこれほどまでに悪いとは…。

試しにGoogleのトレンドを調べてみると、こちらも定常的にTypeScriptが優位のようで、前述した印象とも合致します。

TypeScriptは昔使っていたときにいろいろと苦しめられた思い出があるのですが、過去の体験に固執していてはあっという間に老害です。あれから数年、FlowとTypeScriptの違いは現状どんなもんだろう?

進化したであろうTypeScriptもしっかりキャッチアップしておかねばと思い、個人のOSSプロジェクトでFlow→TypeScriptの乗り換えを試してみることにしました。

移行メモ

涙ぐましい移行の痕跡がこちら。

先にまとめておくと、既存のコードおいてはFlowもTypeScriptも大差なく、頭を使うような書き換えはほとんどなかったものの、純粋に変更量が多く物理(筋肉)的な理由でとても疲れました。

主な書き換え

書き換え対象 Flow TypeScript
構造的型の定義 type Foobar = {...}; interface Foobar {...}
null許容型 ?string string | null | undefined
不明な型(型安全) mixed unknown
読み取り専用のフィールド +field: string; readonly field: string;
明示的な型指定 (foobar: any) (foobar as any)
文字列リテラルの共用体型をキーとするオブジェクト {[Foobars]: string} {[key in Foobars]: string}
関数の戻り値の型 $Call<...> ReturnValue<...>

ざっくり所感

よほどディープな使いかたをしていなければ、型の表現力自体はそれほど変わらないように感じました。

昔のTypeScriptを振り返ると、nullの扱いが緩かったり、テスト実行やカバレッジ計測の際には一時的にJavaScriptに変換する必要があったりと、面倒な落とし穴が散在していました。しかしながら、その後のアップデートでnullチェックも強化されていますし、実行に関してはts-nodeを使えば一時ファイルも不要になっており、結果的には大きな面倒なく移行できました。

むしろ依存関係が簡素化されたこと、minify後のファイルサイズも小さくなった(こちらは意図せぬ効果であり一般的かは疑問)こともあり、当プロジェクトに関してはひとまず移行してよかったという結論になりました。

今回の移行の所感をまとめると…

Flow・TypeScriptで大差なし

  • 型の表現力
  • 素のJavaScriptから移行する際の手間
  • 周辺ツールとの連携(Webpack, Prettier, Mocha, nyc, ESLint/TSLint)

Flowの利点

  • 公式ツールでいつでも素のJavaScriptに戻せるという安心感がある
  • コンパイルが高速(型情報を取り除くのみなので)

Flowの欠点

  • 利用者が少ないため対応パッケージも少なく、将来性にやや不安を感じる
  • JavaScriptの新しめの記法を使うには別途Babelと関連パッケージが必要になり、依存関係が複雑になりがち
  • 型情報の別途書き出し機能(npmパッケージ公開の際に使いたかった)がいつまで経ってもベータ版で、けっきょく動かなかった
    https://github.com/facebook/flow/issues/5871

TypeScriptの利点

  • 利用者が多く対応パッケージも多い
  • 依存関係が少なくて済み、設定地獄から解放される(JSの新記法に対応するためのBabel系プラグインがいらなくなる)
  • Microsoft製なので、同社製のVisual Studio Codeによるサポートが手厚い(当方Vimユーザー、Atom派なので正直くやしい…)
  • minify後のサイズが105KB→85KBと小さくなった(依存関係が少ないから最適化しやすいのかも?他の事例も見てみたい)

TypeScriptの欠点

  • JavaScriptとは別言語なので、どっぷりはまってしまうと将来的な移行が難しくなりそう

その他の雑多な情報

TypeScriptの型情報を公開する

せっかく型を導入した世界でコードを書いているので、npmのパッケージとして広く公開する場合は、利用者側にも型情報を活用してほしいものです。TypeScriptにはそのための仕組みが用意されており、具体的には以下の手順で実現できました。

  1. tsconfig.json の compilerOptionsdeclaration: true を追加して、型情報ファイル(d.ts)を出力させる
    https://github.com/cocopon/tweakpane/commit/4d5dd5a1def0871536e0e7d76b6eff1b9f33a7e1#diff-94499430afe8fc994a6c9447e75d403bR4
  2. package.json に types を追加し、メインとなるd.tsのパスを指定する
    https://github.com/cocopon/tweakpane/commit/4d5dd5a1def0871536e0e7d76b6eff1b9f33a7e1#diff-b9cfc7f2cdf78a7f4b91a753d10865a2R9

詳細は、公式ドキュメントの「Publishing · TypeScript」に記載されています。

参考:他のFlow↔︎TypeScript論争

書いている人:cocopon

Developer/Designer. Web/iOSなどのフロントエンドを主軸に、UIデザインから開発全般まで手がける。

趣味が高じて、ドット絵やジェネラティブアートが仕事になりつつある。