みなさんこんにちは。ミツモアでエンジニアをしている坂本(@ryusaka)です。
現在ミツモアではバックエンド、フロントエンド、アプリのコードのメイン部分は全てのファイル拡張子が.ts(x)
です
全てがTypeScriptで書かれているわけではなく、拡張子が.ts
です。
というのもミツモアのプロダクトは2019年の夏までは全てJavaScriptで書かれていて、そこからTypeScriptへの移行を進め、2020年の年末に全てのファイルの型チェックが効くようになりました。まだanyがたくさんあります・・・
初めから導入されていなかったが故にTypeScriptのコンパイラの設定は緩めです。今回はどうやって緩く、あまり苦しまずにTypeScriptを導入できたのかを紹介できればと思います。
まず最初に言いたいのは、部分的にもTypeScriptは入れられるということです。もうJavaScriptで書いちゃってるし諦めてた・・・という人は必見です
どんな人向けの記事?
- JavaScriptをTypeScriptに移行したい人
- TypeScriptを全てに導入するにはコストが勝つけど、一部の重要な部分には導入したい人
大きく分けてフロントエンド、バックエンドへの2段階の導入を行いました。 同じJavaScriptではありますが、フロントエンドはwebpackを使っていたり環境の違いがあるため移行のやり方にも差異がありました。
- バックエンドへのTypeScript導入←今回の対象
- フロントエンドへのTypeScript導入
- 導入後(webpack + babel-typescript, ts-migrate, tsc-alias, estraverse)
今回は1ファイルずつ順番に移行していったバックエンドのお話をしたいと思います。バックエンドにTypeScriptを導入した約1年後にフロントエンドにも導入したのですが、フロントエンドでは一括で変換しました。この話や導入後についてはまたの機会にお話しさせていただければと思います。
一括で変換すると良さそうですが、コメントだらけ&移行してないファイルがわかりづらくなるため、どちらもメリットデメリットある気がします。(一括変換についてはts-migrate
等で検索すると出てきます)
移行の手順
早速TypeScriptへ移行していく工程を順を追って説明していきます
1. TypeScriptをインストールする
当たり前ですがまずはTypeScriptをインストールして動く状態にする必要があります。
ここではプロジェクトにTypeScriptが入っていて、トランスパイルが通るだけの状態にします。
やることは
- yarn add --dev typescript @types/node
(必要に応じて@types/xxx
はインストールしてください)
- tsconfig.jsonを書く
- Node.jsが実行するファイルを必要に応じて変更
以上です。このステップはとても簡単です。tsc
コマンドは全てが.jsでもトランスパイルしてくれます
最初の段階では全てのファイルの拡張子は.js
だと思うので、エラーにはならず、ただtsconfig.json
でoutDir
に指定したディレクトリにファイルが吐き出されると思います。
tsconfig.json
詳しくは公式ドキュメントなどに譲りますが、JavaScriptからTypeScriptへ緩く移行していく上で入れておきたいおすすめ設定がいくつかあります
設定 | 値 | 説明 |
---|---|---|
allowJs | true | .jsもトランスパイルしてくれます |
checkJs | 必要に応じてtrue | JSDocを読み込んで型チェックしてくれます。ミツモアではfalseです |
strictNullChecks | false | これがONだと型だけではなくてコード自体の修正が必要になったりします |
noImplicitAny noImplicitReturns noImplicitThis |
false | これをtrueにしておくとエラーだらけで正直きついです |
2. 1ファイルずつ.tsに変換していく
tsc
が動くようになればあとは根気強くやっていくだけです。
インポートのツリーの一番下のファイルから変換していくのがおすすめです
以下のようなa.jsとb.jsがある場合はa.jsを先に変換します。
b.jsから変換すると、a.jsにあるものを利用しているファイルがたくさんあったとき、a.jsを.tsにした途端にエラーが盛りだくさんになる可能性があるためです
a.js
const doSomething = () => { console.log('something') } module.exports = { doSomething }
b.js
const { doSomething } = require('./a') doSomething()
ただ、TypeScriptを入れる目的としてより安全に実装を行うためという側面もあるはずなので、重要なファイルから順番に.tsから変更していくのもありだと思います。 ミツモアではハイブリッドで変換されていったと思います。重要な部分は複雑だったりもして、しばらく変換されなかったりもしました。誰かがそのファイルを編集したときに.tsに書き換えるということを行うと自然と必要なファイルから修正されるかなと思います。
移行していく上で出会った問題
最後に、移行してく段階で行きあたった問題をいくつかピックアップしてお伝えしたいと思います
JavaScript的にはOKだけどTypeScript的にはNG
let a = 'xxx' a = 100
例えばこんなコードがある場合は、TypeScriptではany
を使わない限りはエラーになってしまいます。
こういったケースではどうしても実装の変更が必要になります。残念ながら複雑なコードに限ってこういったことが起こっていたりするため、例のように簡単ではない場合が多いです
.js.ts以外のファイル
ミツモアのバックエンドではbabelやwebpackなどは用いずに純正のtsc
を使ってトランスパイルしています。このtsc
ですが、拡張子が.ts``.js``.json
しかビルド先のディレクトリに吐き出してくれません。
もし全てのファイルが.ts
で書かれているプロジェクトであれば同じディレクトリに.js
を吐き出してしまうこともありだとは思いますが、徐々に移行する以上そうはいきません。
これは現状どうしようもないため、js,ts以外のファイルを全てビルドファイルのディレクトリにコピーするスクリプトを用意して、tsc -p ./src/api/tsconfig.json; ./copy_files.sh
のように実行しています。
ビルド/型チェックに時間がかかる
移行し始めた頃は.tsで書かれたファイルが少ないためトランスパイルにかかる時間は短いと思います。 しかし、段々と移行し始め.tsのファイルが増え始めると型解決の対象が増えて時間がかかるようになってきます。これもどうしようもない部分ではあるので、我慢するかCI等でマシンスペックを上げるしかなさそうです。 一応型の書き方を意識したりでパフォーマンスを上げたりはできるようですが、そこまで大きな効果はないと思います。(誰かご存知でしたら教えてください)
まとめ
今回はJavaScriptで書かれたプロジェクトを段々とTypeScriptへと変換していった話について書きました。 TypeScriptを利用することはたとえ一部分だったとしてもバグを未然に防いだり開発効率を向上させる上でとても大きなメリットがあります。 webpackと組み合わせてフロントエンドにもに導入したりの話も続編としてそのうちできればと思います。
現在、事業拡大を進めておりエンジニア・デザイナー・PdMを積極採用中です。ぜひWantedlyのリンクからカジュアル面談をしましょう。TwitterのDMでもお待ちしております。