LINE NEWSをTypeScript化

Akinori Inoue氏(以下、Inoue):ご紹介に預かりました井上と申します。「LINE NEWSをTypeScript化したい!」ということで、発表していきたいと思います。よろしくお願いします。

僕自身の紹介なんですけど、去年の2019年の4月にLINEに新卒で入社して、フロントエンドエンジニアとしてUIT室に所属いたしました。趣味は、フロントエンドエンジニアなんですけどゲームを作るのが好きで、ゲームのアプリとかをよく作ったりしています。

LINEに入ってから僕自身が携わったプロダクトとしては「LINE NEWS」や「LINE スケジュール」なんですけど、今回の話では、このLINE NEWSのTypeScript化をした話をしていきたいと思います。

そもそもみなさんLINE NEWSをご存知でしょうか? LINE NEWSは、LINEのアプリのタブのところにニュースというものがあるんですけど、そちらでニュースのサービスを展開していまして、月間アクティブユーザ数が6,800万ユーザ、PV数は120億と、ものすごく大規模なサービスとなっています。

僕自身は去年にこのプロダクトに配属されたので、あまりにも数字が大きくて実感が湧かないんですけど、とにかくリリースのときだけすごくハラハラしています。

LINEのアプリ内にあるLINE NEWSなんですけど、ネイティブのように見えて、実はWebでできています。実際にはJavaScriptとReactで実装されていて、今回はJavaScriptのところをTypeScript化していくという話になります。

TypeScript化に至る経緯

先ほど申した通り、LINE NEWSは非常に大きいサービスとなっているんですが、開発側も非常に大規模に開発をしていまして、開発人数や行動量も非常に大きいです。2週に1度という頻度の高いリリースをしていて、ユーザ数が非常に多いので、少しの変更で大勢のユーザに影響を与えるということで、ABテストや機能の追加・削除が盛んに行われています。

僕自身は去年入ったばかりなので、新しく入るということでもキャッチアップが困難なんですけど、開発が並行で進んでいくので、自分自身が触ったところでも1ヶ月後には別のものになっていて、その点でもキャッチアップが非常に困難なものとなっています。

また、LINE NEWSのフロントエンドでよく聞く言葉がこの「影響範囲」という言葉です。

リリースのサイクルとして、開発側がコードを編集した場合にはQAという組織に品質保証をしてもらうということで、基本的に少しのコードの変更でもQAの確認というサイクルが走ります。もし不具合が出た場合にすぐに直してもまたQA確認というフローになります。

これはちょっとしたミスの例なんですけど、現場の実際の声で、TypeScript化していればこういった簡単なミスは防げたりします。簡単なミスによってQA確認のコストが掛かるので、こういったところはTypeScript化して抑止できたらなという気持ちでいました。

まとめますと、とにかくキャッチアップが困難で、影響範囲のコントロールも難しいということです。また、他の言語機能みたいなものもJavaScriptにはないという不満もありまして、チーム全体でTypeScript化をしようという動きになりました。

TypeScript化の導入にあたって実際にやったこと

実際にやったことです。TypeScript化の導入にあたっては、影響範囲がすべてということで、他のところもバージョンアップをしようということになりました。Reactのバージョンだったりテストの整備だったり、あとはコードフォーマッティングの整備だったりというところで、開発環境全体のモダン化も一緒に行いました。

LINE NEWSはReactで作られていると言ったんですけど、Create React Appを使っていまして、こちらのCreate React Appとはどういうものかと言うと、自動的にReactの環境構築を行ってくれるツールで、スピーディーに開発できるので使っていたんです。

ですが、こちらは逆に柔軟に設定できるのが難しいというところがあって、ejectというものを利用するとCreate React Appで自動的に設定されている設定ファイルみたいなものを吐き出してくれて、これでまた新たに設定しなおすことができるというものがありまして、まずはこれを検討していました。

ただ、ejectしたファイルはけっこう量が多くて不要な部分も多かったんですね。なので、今回はejectした環境構築のファイルを参考にしながら、古いモジュールだったらアップデートしたり、必要のない機能だったら取ったりということを行いました。

設定ファイルの説明

いろいろな設定ファイルがあるんですけど、その中でも今回はこの5種類について軽く説明していきたいと思います。

まずwebpackのconfigについてです。Create React Appは基本的にデフォルトでwebpackを利用していて、そもそものビルド管理ツールとしては非常に優秀なものだったので、今後も使うことを検討してwebpack.config.jsを作りました。ejectしたものとしては非常に複雑なものだったんですけど、そこまでの機能はいらなくて、結局設定したのは一般的な設定のみとなり、けっこうシンプルなものになりました。

よくTypeScript化で設定する必要があるのが、やっぱりloaderの設定で、他にloaderを使わないケースもあるんですけど、今回はwebpackを使うのでts-loaderやbabel-loaderを使う予定で、いろいろな選択肢があると思うんですね。

tsだけのケースだとes5で吐き出せるので使えるんですけど、LINE NEWSはいろいろな端末に対応するということで、polyfillを入れる前提でbabel-loaderは使うということで、ts-loaderとbabel-loaderを使うという、けっこう歴史の長いほうを選択しました。これは好みの問題だと思います。

targetとライブラリの設定

tsconfigなんですけど、compilerOptionsもTypeScript化で非常に壁となる問題です。一個一個の設定は非常に重要なんですけど、理解が非常に難しいということで、今回はちょっと触れていきたいと思います。

ますtargetについてです。これはTSをJSにトランスパイルする際に出力するECMAScriptのバージョンの指定になります。今回はes5を指定しましたが、基本的にbabel-loaderで解釈するので、こちらはそこまでしっかりとした設定をする必要はないかなと思っています。

ですが、esnextなどにするとTSの最新のバージョンだとOptional Chainingなどが使えるので、そのあたりがbabelだと標準では対応していないみたいなものもあるようです。なので、低い水準にしておくのがいいのかなと思います。

次はライブラリなんですけど、こちらは利用する標準ライブラリの型のみを指定することができます。targetに対してのデフォルト値が入るので、指定はほとんどしなくていいと思うんですけど、今回は低いほうにtargetを落としたので新しい機能を使いたいということで、esnextをあえて指定しています。

ただ、ここで注意なのが、こちらは型なのでesnextを使ってトランスパイルした際にはesnextのコードが残るので、実行環境が対応していなければ実行できないという問題があります。なのでそういった際にはpolyfill等を入れる必要があります。

esnextは一体どんな機能が使えるのかということで、早見表というわけではないんですが、TypeScriptのlibのフォルダの中に型定義ファイルがあって、そちらでesnextに対してはes2020という参照が付いていて、es2020に対してはes2020のPromiseが付いているというかたちで、どんな機能が付いているのかを見ることができます。

es2020のPromiseだったらallSettledメソッドなどが使えますが、こういうものは新しくて実際にはブラウザでは動かないので、polyfillを入れる必要があると思います。

module、allowJsなどについて

次はmoduleについてです。こちらは出力するJSのモジュール管理方法になります。今回はes2015を指定していて、というのもbabel-loaderがモジュールはES6のモジュールのシンタックスで他のモジュールに変えると公式で書かれているので、基本的にはes2015でいいと思います。

次にallowJsなんですけど、こちらはJSからTSにマイグレートするにあたってtrueにしています。trueにすることによってJSの読み込みが可能になるのと、あとは型の推論をある程度してくれます。

ただ、ある程度なので、してくれない部分もあります。逆にundefinedで指定をしていてあとから値が変わる、undefinedまたはStringのUnion型の場合はundefined型が付いてしまうと問題なので、そういった場合は別途型定義ファイルが必要になります。

あとは、先ほどのある程度の推論なので、推論をしてくれない部分に関してはanyがついてしまう可能性があって、そういう場合はanyの汚染が発生してしまう可能性があるので注意が必要となっています。

その他でjsxの指定ではreactを指定しています。jsxで吐き出してbabelでpresetのreactを使って読み込む方法もあるかもしれませんが、今回はあまり設定を増やしたくなかったので、普通にreactにしてjsxからJSの出力にしています。

最後はallowSyntheticDefaultImportsについてです。こちらは普通のプロダクトだと問題ないと思うんですが、他のモジュール・ライブラリを利用している、例えばReactのケースだと、型定義はこのようにexport = Reactと書かれていまして、export assignmentを利用しているので、こちらをexport defaultとして解釈するといったケースではこちらをtrueにする必要があります。

.babelrcを書く

次にbabelの話ですね。babelのconfigは複雑にすることもできると思うんですけど、今回はpreset-envのみでシンプルな構成にできました。

preset-envを使ったときにどのようにpolyfillを入れるかの指定の仕方で、useBuiltInsをusageにしていて、これによってコード内にpolyfillが必要な箇所をbabelが自動的に入れてくれる仕組みになっています。

試しにこの@babel/cliで、左のコードの上の部分をビルドしてみると、forEachの部分にpolyfillが入ります。このimportの部分、例えば今回のforEachのケースでは入るんですけど、100パーセント入るという保証はされてないという感じはあります。

手動で入れた場合にはそのimportとusageが勝手に入れたimportが重複する可能性はあるんですけど、そこはwebpackが重複した部分を消してくれるので、不安な場合は手動でimportするというのもありだと思います。

eslintについて

次はeslintについてです。eslintはもともとLINE NEWSでも導入されていたんですけど、warningが許容されていまして。warningはけっこう曖昧で、実際に直すべきなのか直さないべきなのか、直すと影響範囲が……みたいな感じになるので、TSに入ってからは基本的にwarningというものは存在しなくて、エラーかそうでないかに落とし込んでいます。

ただ、既存のコードに関しては直せる部分は直すんですけど、直せない部分ですね。例えばdepricated機能を使っているケースだと直せないので、こういったケースはJSでは全てwarningにして基本的にwarningは許容するという設定にしています。

extendsのプロパティはこんな感じのものを使っていて、JSとTSはそれぞれこんな感じになっています。

warningは許容なんですけど、基本的にエラーかそうでないかに倒したので、最初の段階では4,000件ぐらいエラーが出ていました。

それをすべてfixして、それでも直らないケースはルールを追加してwarningに倒しきるというかたちになっています。

テストに関してなんですけど、テストはJestというフレームワークを利用しています。プリセットにTypeScriptとしてts-jestというのを指定していて、こちらはtsconfigを読み込むので、JSなども一緒にテストに組み込むことができるというかたちです。

TypeScript化したあとの話

TS化したあとの話なんですけど、QAの組織で確認をお願いして、実際にTS化によって出たバグはなんと何もなかったんですね。実際はけっこう不安で「どんなバグが出るんだろう」と思ったんですけど、意外にもそんなにバグは出ていませんでした。

環境構築のモダン化もしたので、そのあたりでいくつかバグがあったんですけど、TS化によるバグはなかったので、けっこうスムーズに進むことができました。

また、JSを今後どうTS化にしていくかというポリシーをチーム内で話し合いまして、基本的にJSのファイルを無理にTSに移行するということはしていません。こちらはやはり開発コストやQAコストが非常に高くなるので、JSはJSとしてそのまま残すという方法を取っています。

逆にモチベーションとしてはTS化したいということで、新規ファイルは必ずTS化すると決めています。ただ今の話はけっこう理想論で、実際は新しいファイルだけではないので、そういったケースでは以下のような3つの方法を取っています。

基本的にはどれくらいの影響範囲に及ぶか。「無理矢理TS化したことで増えてしまうのであれば今回はやめておこう」とか、「同じ範囲内だったらだいたいここまでTS化ができるだろう」というのがあってフルでTS化するケースだったり、部分的にTS化するケースだったり。あとはそれでも無理だったら型定義だけでも作るという感じですね。

型定義も網羅的に全部作るのではなく必要な範囲、QAが通る範囲内でどんどん追加していくかたちをとっていっています。もし型定義ファイルが間違っていると、今後バグが発生する可能性があるので、そういったところはQAが通る範囲で型定義を作るというかたちをとっています。

最後にまとめです。Create React AppでNEWSは作られていて、そちらをejectするかたちでコンフィグファイルが出るんですけど、それを参考にしつつ新しいファイルを作って、最後にJSを今後TSにしていく上でのポリシーを選定して今後チームではTS化をしていくぞ!

まだTS化の環境を構築しただけなので、今後どんどんTS化をしていく気持ちでいます。

以上です。ご清聴ありがとうございました。