【翻訳】Web世代のデベロッパーのためのmake

Make for the Web Generation

Casper Beyer Original:Make for the Web Generation )by Casper Beyer

イントロ

JavaScriptの普及に伴いビルドツールが盛んだ。人気なものをいくつか挙げれば、gruntgulpslushbroccolibrunchなどがあるが、結局、名前をつけただけにすぎない。

多かれ少なかれ、これらのツールはファイルコピーからzipファイル作成のようなシンプルなタスク処理でさえ、すべてプラグインに依存しているので、それらのタスクを実行するためにプラグインを必要とするだろう。

これらのツールは理想論的には大きな柔軟性をもたらすものとされているが、実際はUNIXのエコシステムをただ複製しているだけにすぎない。このために君のプロジェクトは早々に、大きな開発依存性のバンドルを持つことであろう、そして、やっているタスクは単なる普通のコピー、バンドルやミニファイだけだ。

makeの導入

makeはとても古いツールであり、ネイティブ開発畑出身のほとんどのデベロッパーにとって古き良き友であるが、コンピューターサイエンス出身ではない多くのWebデベロッパーにとっては、それを使おうとさえ思いもしないだろう。しかし、だからといって、それはmakeが良いツールではないということを意味していない、事実、最近はあまりにも過小評価されているように思う。

君はすでにUNIXのエコシステムを持っている。パイプ、ストリーム、ユーティリティなどがすべてそこにある。ビルドする必要性のあるのツールのほとんどは、すでにそのエコシステムに存在している。もし、Windowsのような非UNIXのマシンで開発しているのなら、悪いことを言わないから適切なシェルとgowのようなバンドルをインストールするべきだろう。あるいは、cmd.exeも同様にcmderのような、よりよいターミナル端末に置き換えべきだろう。

makeは一般的にMakefileと呼ばれるファイルで定義、使用し、拡張子は必要ない、大文字に気をつけること。ルールはターゲットと依存するファイルから成り、シェルコマンドも有しており、普通それらはレシピと呼ばれる。

一般的なルールは以下のような感じ。

ターゲット: 依存するファイル
    レシピ
# 注意!スペースじゃなくてタブ!

では、以上の知識を使って簡単な例をやってみよう。依存するファイルであるinput.txtをターゲットであるoutput.txtにコピーしたい場合、レシピは以下の様なシンプルなコマンドラインとなるだろう。

output.txt: input.txt
    cp $< $@

とってもシンプルだろう?cp(1)の起動といくつかの自動変数である、$@はターゲットのファイル名を保持し、$<は最初の依存するファイル名を保持する。

では、次はもっと実践的な例をやってみよう。

コードのコンパイル

Coffeescript、Typescriptや、babelのようなJavaScriptトランスパイラーは最近では普通に使われているので、モダンなJavaScriptから現在のブラウザで解釈できるようなスクリプトにコンパイルする必要がある。

このルールを定義するには、srcディレクトリにあるファイルを変換し、トランスパイルしたファイルをlibディレクトリに保存することだ。

# まず、JavaScriptコンパイラを変数として割り当てる
# この場合babelで、厳密には必要ないが
# あったほうがメンテナンス性が少し良くなるだろう
JC            = babel

# looseトランスパイルを有効にする
JCFLAGS       = --loose

# 次にsrcディレクトリからソースとなるファイルを見つける
SRC           = $(shell find src -name "*.js")

# それから取得した文字列を置き換え、
# ソースディレクトリとライブラリディレクトリでマップする
LIB           = $(SRC:src/%.js=lib/%.js)

# 最後に、ソースからライブラリにコンバートするルールを定義する
# これはコンパイラをそのオプションとその依存ファイルで起動させるためである
# ようやく定義したルールで、ターゲットを出力する
$(LIB): $(SRC)
  mkdir -p $(@D)
  $(JC) $(JCFLAGS) $< -o $@

すべてbashスクリプトに似ていると思う、 $(VARIABLE) は変数を展開し、$(@D) は別の自動変数で、ターゲットファイル名のディレクトリ部分を保持する。

コードのバンドル

Browserify、Webpackまたは単なるコンキャットもまたブラウザ環境にコードをディストリビュートするためには必要な作業と言える。このため、すべてのソースファイルを依存ファイルとし、1つのターゲットを持ち、それらをバンドラーに渡すレシピを持つルールを定義しなければならない。

# 以前と同様に、バンドラーを変数として持つ
BUNDLE        = browserify

# 加えて、起動フラグも設定する
# これは変数を使って簡単にする例である
# すでに変数を定義してあるので、
# babelとbabelify transformで同じフラグを共有したい
BUNDLEFLAGS   = --transform babelify [$(JCFLAGS)]

# 次にバンドルファイル名を定義する
DIST          = dist.js

# ようやく、ルールを定義する、要は全てのを依存ファイルを
# バンドラーに渡し、ターゲットに出力する
# 既にコンパイルしたライブラリファイルがあるが
# コンパイラーはよくヘルパーを生成するので、
# 一般的にこの成果物はいくぶんか大きいファイルになる
$(DIST): $(SRC)
  $(BUNDLE) $(BUNDLEFLAGS) -o $@ $^

コードの最適化

ミニファイと不必要なコードの除去もまた一般的なビルドステップだ。今回のケースの場合、以前に定義したバンドルターゲットを依存ファイルとしてuglifyするだけだ。あとはミニファイしていないバンドル名をもとにミニファイしたバンドル名を置換するだけだ。

DIST_MIN            = dist.min.js

# ミニファイツールとしてuglifyjsを使用
MIN                 = uglifyjs

# 今のところ、フラッグは必要としない
MINFLAGS            = 

# このルールはとてもシンプルである
# 1つのターゲットと依存ファイルををオプティマイザーに通すだけだ
$(DIST_MIN): $(DIST)
  $(MIN) $(MINFLAGS) $< -o $@

ビルドクリーン

makeは.PHONYと呼ばれる特別なターゲットを持っており、この依存ファイルは偽りのターゲットとみなされる。makeは、その名前のファイルが存在しているかどうか、最終更新時刻がどうかに関わらず、無条件にこのレシピを実行する。これはファイルが生成されたかどうかに関係なくスクリプトを実行したいケースで役立つだろう。ビルドをクリーンしたい場合の例は以下。

clean:
  rm $(LIB)
  rm $(DIST)
  rm $(DIST_MIN)

まとめ

makeは難しいためか少し不当な評価をされているように思えるが、実際はシェルスクリプトとたいして変わりはない。私の考えでは、シンタックスはとても明解で簡潔であり、ストリームであり、パイプでありunixと呼ばれる大きなエコシステムとの大きな相互運用性も手に入る。make万歳!Makefileを書こう!

記事に対する意見はご自由に@caspervonb on Twitterに。


私はWebデザイナー出身なので、おっさんだけどmakeを知らない方のおっさん。そうゆう人も多いのではないかな。まぁプロジェクトメンバーによってビルドツールを選べば良いと思うけど、makeも憶えておいて損はないと思う、ってじっちゃんが言ってた。

関連エントリ