自動タスク実行で快適プログラミング学習

こんにちは。年度が替わりましてスマートパス開発部からインフラ部へ異動した子安です。最近すっかり暖かくなって、日本人で良かったなと思っています。

sakura

さて、近年いろいろな動画学習サービスが提供されていて楽しいですね。動画学習といえば、ブラウザで動画を見ながらエディタでコードを書いてコンソールで実行、をコツコツやるわけですが、3つのことを同時に進めていくのは大変面倒です。面倒なので書いたコードを保存したら自動的に実行されるようにしたいと考え始めました。そこで、タスクランナーとしてよく聞くGulpとかGruntとかをこの機会に試してみます。

そもそもGulpもGruntも今まで触った経験がないのですが、どっちがナウいんでしょうか。社内で聞いてみました。

talk

え、オワコン・・・!? そうなのか・・・まあでも、実際に自分でも触ってみないとわかりませんよね。

初めに実現したいことをまとめておきます。

  • 今は自分的にGoが流行ってるのでGoの動画を見て勉強するつもり
  • goファイルはプロジェクト配下のsrc/ディレクトリに格納する
  • コードを編集・保存したら、そのファイルに対して自動的にgo runを実行する
  • 実行結果はコンソールに表示する。実行する度にコンソールをクリアする
  • 環境はMac OS X(10.11.4)で、Go・Node.jsをインストール済み

Gulp

まずはロゴがかっこいいGulpから。 npmを初期化してGulpをインストールします。Gulpの設定ファイルはせっかくなのでCoffeeScriptで書きましょう。CoffeeScriptもインストールしておきます。

npm init
npm install --save-dev gulp coffee-script

ファイルの変更監視はgulp.watchで行います。設定ファイル:gulpfile.coffeeはこんな感じになります。

# gulpfile.coffee
gulp = require 'gulp'
{spawn} = require 'child_process'

gulp.task 'watch', () ->
    gulp.watch 'src/**/*.go', (event) ->
        if event.type in ['added', 'changed']
            console.log '\x1b[2J'
            spawn 'go', ['run', event.path], {stdio: 'inherit'}

gulp.task 'default', ['watch']

これを下記のコマンドで実行します。

$(npm bin)/gulp

Grunt

次はGruntでやってみましょう。npmを初期化してGruntをインストールします。こちらも設定ファイルはCoffeeScriptで書くことにしますが、依存関係に含まれるためCoffeeScriptを個別にインストールする必要はありません。代わりにファイルの変更監視のためのプラグインが必要になります。

npm init
npm install --save-dev grunt grunt-contrib-watch

watchプラグインを設定し、イベントを捕まえてgo runコマンドを実行するようにします。設定ファイル:Gruntfile.coffeeはこんな感じになります。

# Gruntfile.coffee
{spawn} = require 'child_process'

module.exports = (grunt) ->
    grunt.initConfig
        watch:
            files: ['src/**/*.go']
            options:
                event: ['added', 'changed']

    grunt.event.on 'watch', (action, filepath, target) ->
        console.log '\x1b[2J'
        spawn 'go', ['run', filepath], {stdio: 'inherit'}

    grunt.loadNpmTasks 'grunt-contrib-watch'

    grunt.registerTask 'default', ['watch']

これを下記のコマンドで実行します。

$(npm bin)/grunt

npm-scripts

最近はGulpやGruntを使わずにnpm-scriptsを使うんだという話なので、その方法も試してみたいと思います。例によってnpmを初期化して、CoffeeScriptとファイルの変更を監視してくれるchokidarというパッケージをインストールします。

npm init
npm install --save-dev coffee-script chokidar

package.json(抜粋)はこんな感じで

"scripts": {
    "watch": "coffee watch.coffee"
},

呼び出すwatch.coffeeはこうなります。

# watch.coffee
chokidar = require 'chokidar'
{spawn} = require 'child_process'

chokidar.watch './src/**/*.go', ignoreInitial: true
    .on 'all', (event, path) ->
        if event in ['add', 'change']
            console.log '\x1b[2J'
            spawn 'go', ['run', path], {stdio: 'inherit'}

これを下記のコマンドで実行します。

npm run watch

まとめ

Gulp・Grunt・npm-scriptsのいずれの方法でも、実現したかったことを実現できました。

出来上がった設定ファイルを見てみると、GruntはGulpに比べて決まり文句的な記述が多く、設定を宣言的に行うようになっているため柔軟性に欠けるという印象です。Gulpとnpm-scriptsに関しては、記述内容自体にほとんど差がないですね。

それよりもそれぞれのプラグインを探すことや、Gulp・Gruntの書き方を調べることに時間がかかりました。使うために調べることが多くなってしまうのはツールとして本末転倒なので、熟れるまで使い込めるかどうかを判断する必要がありそうです。

使ったもの

  • Node.js: v5.10.0
  • npm: v3.8.3
  • Gulp: v3.9.1
  • Grunt: v1.0.0
  • CoffeeScript: 1.10.0

コードはGitHubに置いています。

おまけ

というようなわけで、今回はnpmでどうやるかということを調べてきたのですが、ここまで来るともうnpmである必要もない気がしてきました。 探してみたところ、Homebrewにfswatchというコマンドがありました。

brew install fswatch

このコマンドは監視対象のファイルにイベントがあると、標準出力にファイルパス等を書き出してくれます。つまり、

fswatch -0 --include='\.go$' --exclude='.*' --event=Created --event=Updated ./src | \
while read -d '' file; do
  echo $'\e[2J'
  go run "${file}"
done

おやおや、一番簡単でした!これが正解だったみたいですね。