読者です 読者をやめる 読者になる 読者になる

$shibayu36->blog;

株式会社はてなでエンジニアをしています。プログラミングや読書のことなどについて書いています。

TypeScriptでのフロントエンド開発環境作成総まとめ

これまで自分のブログで、TypeScriptを使ったフロントエンド開発環境についてブログをいくつか書いてきた。ひとまずこの辺りで、TypeScriptでフロントエンドを開発するための最低限の環境を構築できるようになったので、総まとめとしてブログエントリを書いておく。

今回のサンプルコードは https://github.com/shibayu36/typescript-project-sample/tree/4653cd002eef3ee1946a2ca1da344e0076b2844f に置いたので参考に。

TypeScriptを使ったフロントエンド開発に最低限何が必要か

TypeScriptを使ってフロントエンドをスムーズに開発をするためには、以下の様な環境を最初に作っておくと良いと感じている。

  • 依存ライブラリの管理 & 型定義の管理
  • TypeScriptをビルドし、ブラウザ向けのJSを作る環境
  • ユニットテスト環境

既に昔書いたエントリで触れられていることではあるが、それぞれ軽くまとめていく。

今回の前提とディレクトリ構成

今回はTypeScript等で実装したものをビルドし、1ファイルに結合したものをJSとして配信するという前提の環境を作る。

理解しやすくするために、先に今回の構成でどのようなディレクトリ構成になるか書いておく。

.
├── gulpfile.js   # ビルドツール用設定
├── karma.conf.js # テストランナー用設定
├── node_modules  # 依存モジュールが入っているディレクトリ
├── package.json  # 依存モジュール用設定
├── src           # src以下に自分で実装とテストを書く
│   └── ts
│       ├── app.ts
│       ├── module1.ts
│       ├── module2.ts
│       └── test # ユニットテスト置き場
│           ├── module1.ts
│           └── module2.ts
├── static # static以下を配信(本当はnginxとかで配信するが、今回はそこまではしない)
│   ├── html
│   │   └── index.html
│   └── js
│       └── app.js   # ビルド後のJS
├── tsconfig.json     # TypeScriptのコンパイルオプションなど
├── typings           # 型定義ファイルの置き場
│   ├── browser
│   │   └── ...
│   └── browser.d.ts # TypeScriptファイルでreferenceするための型定義ファイル
└── typings.json      # 型定義ツール用設定

依存ライブラリの管理 & 型定義の管理

結論としては、

  • 依存ライブラリの管理に npmを利用
  • 型定義ファイルの管理に typings

を使う。


全てのものを自分で実装したくはないので、依存ライブラリの追加・管理は必須である。昔はブラウザで利用したいライブラリ(例: jQuery等)は、scriptタグを使って読み込み、その上で自分で実装することが多かった。しかし最近はnpmモジュールとしていろいろなライブラリが公開されていて、またnpmを使えば依存管理もできる。以前はbowerというツールもあったが、npmで最近は事足りている。これらのことから、依存ライブラリの管理はnpmで行う。

しかしnpmに公開されているのはJSで書かれたファイルなので、TypeScriptでそのまま使うことは出来ず、そのライブラリ用の型定義ファイルを用意しなければならない。そこで依存ライブラリの管理ツールに加えて型定義ファイルの管理ツールも使う必要がある。これも管理ツールはいろいろあるが、最近はtsdの後継であるtypingsを利用するのが良い。


npmとtypingsを使えば依存の追加は簡単。例えばjQuery をインストールしたければ以下のようにすれば良い。

$ npm install jquery --save-dev
$ $(npm bin)/typings install jquery --ambient --save

npmのコマンドにより、package.jsonに依存が追加され、node_modules/以下にjQueryがインストールされる。typingsのコマンドによりtypings/以下にjQueryの型定義ファイルがインストールされる。あとはTypeScriptで実装をするファイルでtypingsで管理された型定義ファイルにreferenceして、jQueryをimportするだけである。

src/ts/app.ts

/// <reference path="../../typings/browser.d.ts" />
import * as $ from "jquery";
$().ready(() => { ... }


依存ライブラリの管理には bower というものもあるし、型定義ファイルの管理にはdtsm というのもある。この辺を使ってもやりたいことはできるのでお好みで使ったら良いとは思う。


詳しくは以下の記事にまとめてあるので参考にしてほしい。

blog.shibayu36.org

TypeScriptをビルドし、ブラウザ向けのJSを作る環境

TypeScriptはそのままではブラウザで解釈できないので、何らかの形でブラウザに解釈できる形式にコンパイルしておく必要がある。この時ブラウザ向けに配信するために自分がビルド時に最低限やっておきたいことは以下のことである。

  • TypeScriptをJSにコンパイルする
  • 依存を解決して配信用に一つのファイルにまとめておく
  • TypeScriptファイルに書かれたコメントくらいは消しておきたい
    • ただしnpmに公開されているモジュールのライセンスコメントは残しておきたい

これらを満たすために、自分はgulp + browserify + tsify + licensify + gulp-uglifyを利用した。

これらのツールを使って上記のやっておきたいことをするためには以下のようなgulpfile.jsを書いておくだけである。

gulpfile.js

var gulp = require('gulp');
var browserify = require('browserify');
var source = require('vinyl-source-stream');
var buffer = require('vinyl-buffer');
var uglify = require('gulp-uglify');

gulp.task('build-ts', function () {
    return browserify({
        entries: './src/ts/app.ts'
    }).plugin('tsify')
        .plugin('licensify')
        .bundle()
        .pipe(source('app.js'))
        .pipe(buffer())
        .pipe(uglify({
            preserveComments: 'license'
        }))
        .pipe(gulp.dest('./static/js'));
});

gulp.task('watch', function () {
    gulp.watch('src/ts/**/*.ts', ['build-ts']);
});

この設定だけで、$(npm bin)/gulp build-ts をすれば、src/ts/app.tsをエントリポイントとして、上記やりたいことを全部行ったファイルをstatic/js/app.jsに出力してくれる*1。HTMLファイルではこれをscriptタグで読み込むだけで良い。

<script src="../js/app.js"></script>

また、TypeScriptのファイル変更時に自動でコンパイルしたいなら、$(npm bin)/gulp watchを起動しておけば良い。


使っているツールについては正直なんでも良い。ビルドツールtscとnpmがあったら十分とか、ブラウザ向けコンパイルはwebpackを使いたいとか、そういう意見も無限にある。まあ他に同じようなことができる組み合わせはいくらでもあると思うし、自分の好きなようにやったら良いと思う。ただし、シンプルさはできる限り追求したほうが良さそう。今回の環境は非常にシンプルとはいえないが、自分の思う利便性とシンプルさの妥協点を探ってこんな感じにしている。もちろん今後のデファクトに従って変わるかもしれない。


今回の説明ではTypeScriptのコンパイルオプションや各ツールの説明に触れていないし、詳しいところは以下の記事に書いてあるので参考にどうぞ。

blog.shibayu36.org
blog.shibayu36.org

ユニットテスト環境

昨今プログラムを書くにあたって、ユニットテストを行う環境を作っておくのは必須だと感じている。もちろんテスト自体もTypeScriptで書いておきたい。

これを行うためには、今のところ、テストランナー・テストフレームワークアサーションライブラリを組み合わせて行うようにする。

自分は以下の様なツールを利用して、このような環境を作った。

これらも別のツールに置き換え可能である。karmaは使わずにmochaのテストランナーの機能を使うだけでも良いし、mochaを使わずにjasmineを使う、chaiを使わずにassertやpower-assertを使うなど。なので、これは単なる参考例であり、やりたいことに準じて自分が好きなツールを使えば良い。


これらを使ってどうやってユニットテスト環境を作るかは、以下の記事にまとめてあるので参考にして欲しい。

blog.shibayu36.org

あとは自分で実装する

ここまで環境を整えられれば、あとはテストを書きながら実装を書いていくだけである。僕だったら

  • src/ts/以下にうまくクラス分割などをしながらファイルを増やしていく
  • それぞれの実装に対応するファイルをsrc/ts/test/以下に作っていく
  • それをsrc/ts/app.tsにimportしながら大本の実装を作る

というような手順でフロントエンドを開発していきそう。

まとめ

これまで自分が書いた記事の総まとめとして、TypeScriptでフロントエンド開発を開始するために最初にやっておきたいことについて書いてみた。フロントエンド開発プロジェクトを始めるにあたって、一つの例として参考になれば嬉しい。


どうやってこれらのツールを選んだかと言えば、いろんな視点はあるけど以下のようなところを見ながら選んだ。

  • 今のデファクトは何か
    • npmのダウンロード数とか、コミュニティの活発さ具合とか
  • ググった時に出てくる情報は多いか
  • 可能な限りシンプルに、ただし利便性を損なわず


さて少し話は変わるけど、よくフロントエンドは学習コストが高い、速度が速すぎる、とかいう話がある。確かにいろいろな過去のしがらみとか、GUIアプリという本質的な難しさとか、なんかいろいろあってフロントエンドを開発するための課題は多い。課題が多くなるとそれを解決するためのツールが増えていくのは仕方ないかなと思う。このことから確かに最初の学習コストは高いかもしれない。確かにTypeScriptの環境をここまでまとめていくのはかなり大変だった。

ただし、その時にツールの覚え方を単にググッて分かった気になって終わりにすると今後の動きにはついていけないとも思う。最初に学習する時にこのツールがどういう課題を解決するのか、またこのツール以外の同じ課題を解決するための選択肢はないのか、このツールが持つ概念はなんなのか、他のツールとはどのような関係なのかなどと自分なりに調べておけば、次に別のツールが出てきたとしてもそれがどのレイヤーに位置して何のためのものなのかすんなり理解できると思う。

なので結局は「ツールの使い方」を勉強するのではなくて、「それが出てきた背景や概念」を勉強するということを繰り返すことが大事なのだなと改めて感じた。

*1:ついでにminifyもしてくれている。