メインコンテンツまでスキップ

TypeScriptによるアプリケーションの開発環境

Sato Taichi

このエントリーは pyspa Advent Calendar 2019 の 11 日目の記事です。昨日は @chezou の「Vein の iOS ショートカット複数 URL 対応しました」でした。

はじめに#

TypeScript は大変に素晴らしい言語で、僕の手によくなじむ。そのせいか最近はめっきり TypeScript ばかり書いている。

今回のエントリでは、僕がこの一年くらいの間に磨いた TypeScript のテンプレートプロジェクトについて説明する。かなり何度も使って必要十分なものだけを含めるようにしている。

しかし、僕の知識の偏りがそのままになっているので、万人に合うというわけではないだろう。

とはいえ、開発環境の初期構築はかなり面倒な作業なので参考にして貰えれば嬉しい。

細かい説明なんかよりもコードを見た方が早いってハードコアな方は、こちらへどうぞ。

OS とハードウェア#

テンプレートプロジェクトの説明をする前に、まずは僕の使っているハードウェアと OS について説明しておく。

Thinkpad X1 Carbon 2016 年モデルに Windows10 をインストールしてある。ハードウェアスペックは、こうだ。

  • CPU i7 6600U @ 2.6GHz
  • メモリ 16GB
  • ストレージ SAMSUNG NVMe SSD 950 PRO 512GB

流石に三年も経つと同等のスペックのものがかなり安価に購入できるようになってきていると感じる。

この環境で、PC 版の LINE と Slack と VS Code と GitKraken を動かしているが十分に快適だ。

事前準備#

このテンプレートプロジェクトでは、事前に必要なソフトウェアとして、

  • git
  • Node.js
  • yarn
  • Visual Studio Code (VS Code)

が必要だ。それぞれインストールしておいて欲しい。

僕と同じように OS として Windows を使っているなら scoop を使って簡単にインストールできる。

尚、scoop については以前に書いたので、そちらを参考にどうぞ。cf. Scoop で Windows における開発環境構築を最適化しよう

ついでに git の GUI クライアントとしては、GitKraken がおすすめだ。

テンプレートプロジェクトについて#

それでは、早速プロジェクトの内容について説明していこう。

エディタ#

まずは、みんなが大好きなエディタの話題から取り上げるとしよう。

今、TypeScript の開発環境として一番強力なのは VSCode だ。

何せ VSCode 自体が TypeScript で書かれているので、機能不足を感じたら TypeScript で拡張が書ける。

加えて、入力補完やシンタックスハイライトも大変に優れている。

一部の機能については language server protocol 経由で他のエディタでも使えるというのも大変に素晴らしい。

また、開発も非常に活発で毎月のように最新版がリリースされる。リビジョン番号が上がってすぐはバギーで、一週間もしないうちに毎回パッチが降ってくるのがまた可愛い。

マイクロソフトらしく互換性に気を使ったリリースをしてくれるので、お気に入りの拡張がいきなり動かなくなることもほとんどない。

VSCode といえば拡張だけども、僕は別に拡張を山盛りして使っているわけではない。どんどん新しいものを入れてはいるが、使わないやつはどんどん消している。

なので、僕のローカルで長い時間生き延びた拡張だけをテンプレプロジェクトには推奨拡張として設定してある。

丁度いいので僕のおススメ拡張を軽く説明しておこう。

TSLint#

VS Code の TSLint 拡張は、エディタ用に作ったメモリ上の AST を流用してコード検査を行うので、コンピュータ資源を効率的に使える。

なので、動作が非常に高速で快適だ。これについては、後でも取り上げる。

REST Client#

ウェブアプリケーションやウェブに対して何か要求を送信するようなアプリケーションを作っているなら、こいつはマジで最高の拡張だ。

UI がシンプル過ぎるので一見すると、とっつきにくさはあるけども、分かってしまえばどうということはない。

どう使うのかを説明する。まずは、.http とか.rest みたいな拡張子のファイルを作って中に HTTP リクエストを書く。HTTP リクエストというのは、こういう感じだ。

GET https://www.google.com/

その行の上に Send Request というリンクが出てくるので、それをクリックする。

そうするとその HTTP リクエストが送信されて、結果を隣のタブで見られる。

ただ、それだけのものだ。HTTP ヘッダや POST ボディを付けたりもできる。

最近の HTTP リクエストは、GraphQL みたいに改行を伴う HTTP リクエストや Authorization ヘッダーをちゃんとつけないといけなかったりで、curl だけだとちょいとつらい。

以前から Chrome 拡張のPostmanを愛用しているが、REST Client が非常にお手軽なので気が付いたらこちらばかり使っている。

Regex Previewer#

正規表現を使うコードはソフトウェア開発において、ある種の油断というか矛盾というか、もやっとしたものが集約されやすい部分だ。

小規模な正規表現をチラっと使っているうちは特に問題がないのだけども、その小規模というのが曖昧で感じ方に大きな個人差がある。

正直言って、Regex Previewer を使って動作確認しないといけないような正規表現は小規模であるとは言えないだろう。

書いた本人は簡単なつもりで書いていても受け取った人間が、それを簡単だと感じるとは限らない。

そういう時、Regex Previewer はマジで便利だ。二週間前の自分は他人って話もある。

ノリノリで書いた正規表現のどこかに抜け漏れがあるんだけどヤバ過ぎてよく分からない、なんて愉快な事に遭遇したことは無いかな?僕はある。

EditorConfig for VS Code#

改行コードとタブやスペースをエディタ関係なく同じ結果になるよう調整できるツールが EditorConfig で、それの VSCode 版だ。

特に Windows ユーザと Mac ユーザが混ざっている開発チームでは、ちゃんと調整しておかないと地獄のようなことが起きるので、こういうツールで統一するのが望ましい。

ただし、改行コードやインデントを何にするかは、EditorConfig が決めてくれる訳ではないので十分に揉めてくれたまえ。

gitignore#

VSCode のエクスプローラ機能には、なぜ gitignore するメニューが標準でついて無いのかよくわからない。

頻繁に使うわけではないけど、無いと困る。これは、そういうちょっとしたツールだ。

パッケージ管理ツール#

パッケージ管理ツールとしては、デフォルトの npm もひところに比べると随分早くなったように感じる。しかし、やっぱりまだ yarn の方がキビキビ動く。

特にプロキシの内側にいるとそれを強く感じる。npm だとそもそもモジュールのダウンロードが終わらないことすらあるんだよね。

最近は、pnpm という新しいやつも出てきたけど、yarn ほど早くもないし便利でもないので使っていない。

蛇足だが、yarn は npm scripts を短いコマンドで実行できるところも気に入っている。

モジュールバンドラ#

モジュールをくっ付けたり圧縮したりするのに何を使うのか?というのは比較的ややこしい問題だよね。

一番人気なのは恐らく webpack だろう。ただ、こいつは凄い勢いで破壊的変更が入ってくるのでついていくのが大分しんどい。

二回メジャーバージョンアップに付き合ったが、正直もうやりたくない。はっきり言って、出来ることが同じままで名前を変えるみたいなことを頻繁にやらないで欲しい。 僕らはアプリケーションを開発しているのだから、ビルドスクリプトのメンテナンスに大きな工数をかけたくない。

どうしても webpack を使うなら、Next.js みたいに内部に webpack を抱え込んでいるツールにお任せしてしまうのが良いんだろうなぁと今は考えている。

設定ファイルをゴリゴリ書かなくてもそれなりに上手くバンドルしてくれるツールとして、Parcel が今はお気に入りだ。

Parcel 自体には設定ファイルが無く、設定が必要な時はその依存するツールの設定ファイルを読み込んで使うという動きをするのが大変に優れている。(例えば、TypeScript をビルドするときには、tsconfig.json を勝手に見つけて使う)

webpack に比べるとコード量も少ないしやっていることもシンプルなので、一度分かってしまえばどうということもない。

まぁ、シンプルであってイージーではないので、一度分かるまでが大変なのは間違いない。

適用範囲も広くウェブのフロントエンドから、AWS の Lambda 関数、バッチ処理と色んなケースで使える。一回分かれば色々使えるというのは、大変にありがたい。

破壊的変更の少ないバージョンアップをしてくれるので、ビルドスクリプト自体を延々とメンテナンスする必要もない。

Lint ツール#

TypeScript で真っ当に使える Lint ツールは3つある。

まずは、コンパイラのオプションを記述できる tsconfig.json で、新規のプロジェクトでこれに strict: true を必ず設定する。これによって、TypeScript を使ったプログラミングにおけるメリットを最大限に享受できる。

悩ましいのが、typescript-eslint を使うか、TSLint を使うかだ。

将来的に JavaScript の Lint は、eslint に集約されていくという事には同意する。しかし、それは今すぐという訳ではない。というのが僕の考えだ。

僕が TSLint を使い続けている理由はいくつかある。

tslint-microsoft-contrib が最高すぎる#

一番大きい理由はこれだ。僕のテンプレートプロジェクトでは、この巨大なルールセットから不要なものを無効化する形で TSLint のルールを構成してある。

TypeScript には、歴史的経緯により沢山の落とし穴があり、それらに対する十分な知識を僕は持っていないのだ。

なので、自動的に検出できるプログラミング上のミスは出来る限り沢山見つけてほしい。

例えば、僕は Promise のインスタンスを await し忘れるみたいな単純なミスも結構やってしまう。このルールセットなら即座に Lint エラーになるので不要なドはまりを避けられる。

また、tslint-microsoft-contrib は宣言の省略を基本的に許していない。つまり、これに従ってコードを書いていると、コード全量の三割から四割くらいが型宣言になってしまうことさえある。

慣れないうちは、冗長に感じるかもしれないがコードベースが大きくなるに従って、型の情報がコードを理解する強力な助けになることを実感できるだろう。

VS Code の TSLint 拡張が最高#

VS Code の TSLint 拡張は非常に高速に動作する。Lint ツールがエディタ上で迅速に動作するというのは開発作業における必須条件だ。

何故なら、Lint エラーは出力されてから出来る限り短い時間内に対処しなければ、結果的に対処されないことが多いからだ。CI サーバでエラーにしてから対処するのでは遅すぎる。

加えて、僕は自動的に解消できるエラーは全てファイル保存時に解決するという方針でコードを書いている。 セミコロンの自動入力や import 文の並び替え、タブからスペースへの変換などは頻繁に実行されるので、ちょっとでも引っ掛かりがあると、それぞれは短くても積み重なってストレスの原因になる。 なお、ファイルの保存は 15 秒に 1 回よりも早いペースで行っている。

TSLint 拡張は極めて迅速に動作するが、eslint 拡張は同じ速度感で動作しない。

僕としては、Lint エラーは基本的に全て手元で出来る限り早く手元で解消すべきだと考えているので、手元での動作が悪いツールは避けている。 もし、eslint 拡張が今の TSLint 拡張と同様の速度で動くようになったら、乗り換えるだろう。

テスティングフレームワーク#

テスティングフレームワークとしては、依然として AVA を使っている。

今からテスティングフレームワークを選ぶなら大方 Jest だろう。テスティングフレームワークとして求められる機能が全部入っているのだから選び易い。

好みの問題だが、僕は expect タイプのアサーションメソッドが本当に嫌いだ。これを使ったテストコードは英文のように読めなくもないが、実際には英文ではない。

また、やたらめったら比較のためのメソッドを覚えなければテストコードが書けないのも辛い。

加えて、 describe によるテストの構造化はスローテスト問題の発症を早めると僕は考えている。

TypeScript で用もないのに関数やラムダ式を延々と入れ子にするようなコードを気楽に書かせるべきではない。

そして、僕が Jest を使いたくない決定的な理由は、テストの実行が AVA に比べて数倍遅いからだ。

AVA については、以前のエントリにも書いたので、気になる方はそちらを呼んで欲しい。cf. Modern JavaScript 概観、そして Electron へ

便利ライブラリ#

次は、最近気に入ってよく使っている便利なライブラリを紹介しよう。

アプリケーション設定を記述する#

アプリケーションの設定を読み出すためのコードを書くのは正直面倒だ。

とはいえ、 process.env から直接読み出してしまうと、全部が文字列になってしまって型システムもへったくれもない。加えて、その設定を使う時まで設定エラーを見つけられない。

要は、設定を構造化して読みやすくまとめた上で、アプリケーション起動時に設定情報が全て正しく構成されているのかチェックしたいのだ。

こういう要件を満たす便利なライブラリが convict だ。型定義ファイルがよく出来ているので型付けされた設定を記述できる。ただ、これ単独だと環境変数の定義が若干面倒なので、dotenv を組み合わせて環境変数への設定をファイルにしている。

convict と dotenv を組み合わせて使う方法については、少し長くなりそうなのでここでは説明しない。もしどうしても説明が欲しいということであれば、twitter なり対面なりで、要望して貰えればエントリを書くかもしれない。

環境変数を積極的に使うやり方は便利だが、少し気を付けて使って欲しい。

例えば、CloudFormation なんかでアプリケーションをデプロイする際には、環境変数の中身をテンプレートに記述しなければいけなくなる。

そういう状況では、環境変数から設定情報を取り出して使うのは望ましくない。Secrets Manager みたいな場所から読んできた値を設定するようにした方がいいだろう。

convict では load メソッドを使うと、そういう状況に対応できる。

複雑な非同期処理を簡単に書く#

Node.js のランタイム上で動作するコードにおいて非同期処理を免れるのは非常に難しい。

とはいえ、async/await だけを使ってあらゆる非同期処理を書くのは大分つらい。

例えば、50 件ずつバッファリングされたデータが非同期で複数回送信されてくるのを一件ずつ処理する。みたいなコードを想像して欲しい。GitHub API の Paginationみたいなやつだ。

こういう時に使うべきライブラリとして知られているのは RxJS だ。しかし、RxJS を一たび採用するとあらゆる部分に影響を及ぼす。

RxJS を使ったコードの動作を最適化するには、RxJS を出来る限り広く使わざるを得ないのだ。

そして、RxJS の学習コストは極めて高い。

これに対して、出来る限り少ない学習コストで複雑な非同期処理を記述できるライブラリがstreaming-iterablesだ。RxJS ほどのボキャブラリは無いけども、それなりに難しいこともできる。

ライブラリに定義されている関数は generator はふんだんに使っていることを除けば単純なので理解し易い。

そういうわけで、RxJS に大きな学習コストを今すぐにはかけたくないが、複雑な非同期処理を書く必要があるなら streaming-iterables を試してみて欲しい。

ロギング#

ロギングライブラリというのは、作りやすいので氾濫し易い。

ただ、作りやすいこととちゃんと使えるものが作れることには余り関係がない。というか、まともなロギングライブラリを書くのは本当に難しい。

つまり、まともに動くロギングライブラリを見つけるのは難しい。だからといって、自前で実装するのは望ましくない、

というわけで、今の一押しロギングライブラリはpino だ。

こいつは、ログのデフォルト出力が JSON になってるあたりクラウドサービスを組み合わせた運用監視を想定した作りになっており大変に素晴らしい。

いざとなれば手を入れて使える程度に処理はコンパクトにまとまっているので、他のロギングライブラリに乗り換えるのも難しくはない。

少し前まで winston をせっせと使っていたが、内部で使っている logform で実装されている奇妙な遅延ロードの仕組みにドはまりして以来使うのをやめた。

なお、その問題に関する Issue はこれ。https://github.com/winstonjs/logform/issues/66

開発関連の SaaS#

最後に、テンプレートプロジェクトが利用を前提としている開発用 SaaS を軽く紹介する。

GitHub Actions#

以前はビルドが失敗した OS に SSH や RDP 接続できるので、CircleCI や AppVeyor を使っていたが、今は GitHub Actions に移行しつつある。

移行における最初のモチベーションは、マルチ OS 対応だ。GitHub Actions は裏で Azure Pipelines が動いているので、Linux と Mac と Windows を気兼ねなく使える。

特に、node のランタイムと OS の組み合わせでマトリクスビルドが超簡単に構成できるのには感動した。

Linux と Windows の両方でビルドするために、複数の CI 用設定ファイルを書いていたのだから、本当にありがたい。

また、イベント毎にワークフローの設定ファイルを分割するという方針も大変に良い。

更に、複数の構成で共通的に使う処理は、Action という形でモジュール化できるので、ワークフロー定義が肥大化するのを防げるようになっている。

Action は Docker を使えばなんでも出来るし、気軽に書くなら JavaScript や TypeScript でも書ける。リポジトリ内に Action を配置して動かせるので泥臭い処理の共通化を気兼ねなくできる。

ワークフロー定義の中で使える変数や、 if 文は少々奇妙なところもあるが、そこは我慢のしどころだ。

つい最近自分のプロジェクトで使うために TypeScript で書いた Action はこれだ。このコードを読めば、TypeScript を使って Action を作るとどういう感じになるのか概観できるだろう。

Renovate#

依存ライブラリを自動的に更新するツールは、僕も以前に作ったことがある。しかし、Renovate はそれらを大きく上回る便利さだ。

特に自動マージ機能や、検査対象ライブラリのフィルタ機能、設定を他のリポジトリから読み出す機能なんかが大変に素晴らしい。

モジュール毎に分けてプルリクエストが飛んでくるというのもマージし易くて良い。

勿論、CI をちゃんとセットアップしておけば、その結果を見てマージするのか判断してくれる。

類似するサービスをいくつか試してはみたが、結局 Renovate が一番最高ということになった。

それとは別に、GitHub が提供してくれるManaging security vulnerabilities の機能は有効化している。

加えて、yarn audit をデイリービルドの中で実行している。

まとめ#

今回は、僕が作った TypeScript のテンプレートプロジェクトについて長々と説明してきた。

最近は、ユーザインターフェースを作るようなコードは余り書いていないので、そちらの要素技術には言及できなかったのが少し悔やまれる。

テンプレートプロジェクトには入れていないが、aws-cdk はマジでお気に入りだ。

例えば、@aws/dynamodb-data-mapperでアノテーションしたオブジェクトをそのまま aws-cdk の construct として扱えるようなちょっとしたコードを書く程度には使い込んでいる。

内容に関する提案や、誤りの指摘、要望などがあるなら、Twitter などで連絡してくれると大変に嬉しいので気軽にメッセージを送信して欲しい。

TypeScript に限らず JavaScript 関連のフロントエンド界隈は、流れが随分と速いので余り無理せずについていきたい。そういう皆さんの助けに少しでもなれば良いなと考えています。

明日は、@drillbits です。