はじめに
今関わっているプロジェクトで7万行以上のコードを、JavaScriptからTypeScriptに移行するというのを経験しました。
移行するにあたっての準備や手順、実際にやってみて感じたことなどを共有します。
プロジェクトの概要
- フルスタックWEBアプリケーション
- フロントエンド: JavaScript + React
- バックエンド: Node.js + Express.js + GraphQL
なぜそもそもTypeScriptを採用しなかったのか
事前に技術選定をした際に、TypeScriptを利用するかどうかの議論はありました。
当時は以下の理由から採用しないという選択に至りました。
- JavaScriptの開発に慣れていたため、TypeScript型定義=面倒というイメージが強かった
- 「スピードが命」と考えており、TypeScriptを使うことで開発効率が下がると思っていた
- プロジェクト開始当初は開発メンバーも私一人だったため、JavaScriptでも十分だと感じた
- 当時の見立てではプロジェクトはそこまでソース量は多くなると思っていなかった
なぜTypeScript移行を決意したのか
開発が進んで7万行のコードを書いたあたりで、以下の理由からTypeScriptに移行することに決めました。
- 開発の段階でバグがあまりにも多かった
- 特にundefined/nullにまつわるエラーが多かった(Cannot read property ○○ of undefined)
- シンプルな型エラーが多発していた
- ソースが肥大化するにつれて、処理の内容に頭がついていけなくなっていた
- Reactコンポーネントの
props
や関数の引数が明確に定義されていない - オブジェクト型になんでもかんでも入れてしまう
- Reactコンポーネントの
- 一人で開発していたところに他のメンバーが加わることになった
- ソースが乱れすぎて他のメンバーがキャッチアップがしにくい状態になっていた
- 逆に他のメンバーが書いたソースを読み解くのも大変だった
事前準備
ソースと画面から要件の洗い出し
JavaScriptのときと挙動が変わってしまってはいけないので、既存のソースや実際の画面から機能要件を洗い出しました。
元々の要件に書かれていないような細かな仕様についてもメモしておくと後々便利でした。
スケジュールの引き直し
まずはどのくらいのコード量を移行するのかを確かめる必要がありました。
色々方法はあると思いますが、私はVSCodeの拡張機能のVS Code Counterを使いました。
実行するだけで、各言語のソースのファイル数とコード行数をMarkdownで出力してくれます。
JavaScriptのコードがなくなることが理想なので、JavaScriptのファイルほぼすべてが移行対象となります。
ファイルの数やコードの行数に応じてスケジュールを引き直しました。
移行の手順
準備ができれば、いざTypeScript移行になります。
ts-migrateというTS移行ツールもあるみたいですが、私は恐ろしくて使えませんでした。
ゼロベースでプロジェクトを作る
ソースはGitHubで管理しているのでブランチを切り離すところから始めました。
今回のReactアプリについてはCreate React Appを使っているので、
npx create-react-app my-app --template typescript
でTypeScriptベースのプロジェクトを一から作ってファイルのみ移しました。
土台部分に関しては修正するよりも一から作った方が進めやすかったです。
徐々に移行していく
tsconfigでallowJs:true
を設定すること、JavaScriptとTypeScriptが両立できます。
共通部分のソースから徐々にTypeScriptに移行させていきました。
具体的な手順としては、
- 拡張子をts/tsxに変更する
- Reactコンポーネントを使う場合はtsx、それ以外はts
- 必要に応じて型を付与する
- tslintを使えばエラー箇所が赤くなる
- 型推論があるので必要以上に型を定義することはない
- どうしようもないときはとりあえず
any
型にして後からリファクタリング- どうしても型が定義しにくい場合のみ応急処置として
any
を許す -
as
やany
はバグの原因になりやすいので後で必ずリファクタリングが必要
- どうしても型が定義しにくい場合のみ応急処置として
JavaScriptのときは曖昧にしていたオブジェクトに対して型を考える必要があるため、
仕様についても改めて考えながら作っていく必要がありました。
TypeScriptに移行するにつれてプログラムが洗練されていくのを感じました。
テスト
開発が終わったら、移行前の画面と比較しながらテストをしていきました。
細かいところで微妙に挙動が変わっていたりもしたので、綿密なテストが必要になります。
リファクタリング
最後にas
やany
はバグの原因になるのでなくしていきます。
tsconfigのstrict:true
、allowJs:false
で動く状態が理想的です。
こちらの記事にもあるように、
書き方によってはTypeScriptの型安全性を破壊しかねないので注意が必要です。
やってみて感じたTypeScriptのいいところ
- 開発効率がかなりに向上した
- 入力補完が強力(スペルミスがなくなる)
- 入力中に型チェックがされる
- マウスオーバーで変数の型の確認ができる
- 関数の引数や返り値の型の確認ができる
- バグが未然に防げる
- コンパイル時に静的型チェックをしてくれる
- ソースが綺麗に書ける
- 型定義は簡易的なドキュメントとしても扱える
- 型の曖昧さがなくなり、誰がみてもそれなりに分かりやすい状態にできる
- 新しい開発メンバーのキャッチアップが楽(分かりやすいと言ってもらえた)
やればよかったこと
- Jestによる自動テストの仕組化
- 静的型チェックだけではすべてのバグは防げないので、Jestによるユニットテストを実装すればよかった
- 時間が足らずに実装まで至らなかった
- TypeScriptの勉強会
- TypeScript特有の書き方に戸惑うメンバーもいた
- TypeScript勉強会を開くことで全体の開発効率が上がったかもしれない
TypeScript移行時に合わせて採用したもの
-
GraphQL Code Generator
- GraphQLのスキーマからTypeScriptの型定義を生成するためのツール
- React+Apolloで型安全に使える関数を生成してくれる
- このツールがあるおかげでTypeScriptとGraphQLの相性は抜群になる
-
TypeORM
- TypeScriptのORマッパー
- SQLを書かずにDBからデータを取得できる
- 直接SQLを書くよりも型安全に実装することができる
-
routing-controllers
- ExpressのコントローラーをTypeScriptのクラスベースで書くことができるライブラリ
- バリデーションやサニタイズなどもできる
-
AWS CodeBuild
- TypeScriptにしたことにより、ビルドに時間がかかるようになったのでクラウドでビルドするように変更
- デプロイのミスを減らすためにもCodePipeLineで一連の流れを自動化した
つらかったこと
- とにかく時間がかかった
- 移行前のソースが雑だと綺麗に型定義を書けない
- 作り直しが必要なケースもあった
- TypeScriptの学習が大変だった
- 最初の方は意味不明なエラーをめっちゃ吐く
- どう作るのがベストプラクティスなのか、調べても見つけづらかった
- tsconfigの設定と各項目の理解が難しい
- decoratorとかクラスベースとかJavaScriptにない概念が出てくる
- 利用しているライブラリに型定義が用意されていなかった
- 型定義ファイル(d.ts)を自作する必要があった
結論
7万行のJavaScriptコードを移行することで、圧倒的に開発が捗るようになりました。
移行自体は大変でしたが、私はコードを書くのが好きなのでそれなりに楽しかったです。
「TypeScriptを採用すべきか」という議論には色々な考え方があるとは思いますが、
ある程度長く使うものであれば、最初からTypeScriptを採用しても損はないと考えています。
余談
別件でJavaScript+Vueで書かれたプロジェクトをTypeScriptに移行しようとして挫折しました。
Vueは元々の書き方に癖があり、TypeScriptにしても型推論が上手くいかなかったりなど、
Reactと比べてTypeScriptとの相性は良くない印象でした。
Vue3.xでTypeScriptが本格対応されたようなのでそちらに期待しています。
おわりに
思っている事をたくさん書いてしまいましたが、TypeScript大好き人間として少しでもその魅力が伝わると嬉しいです。
「TypeScriptは面倒」というイメージを持っている方も是非一度TypeScriptを触ってみてほしいです。
JavaScriptのプロジェクトが大きくなりすぎる前にTypeScriptへの移行を検討してみてはいかがでしょうか。