次の方法で共有



December 2015

Volume 30 Number 13

Visual Studio - Web 開発用最新ツール: Grunt と Gulp

Adam Tuliper | December 2015

最近の Web 開発者はさまざまなツールを利用します。この頃の Web プロジェクトでよく目にするのは、Grunt と Gulp という 2 つの JavaScript タスク ランナーです。タスクの実行に JavaScript を使用するのは、これまでに経験がない開発者や、ごく一般的な Visual Studio Web 開発に慣れている開発者は違和感を覚えるかもしれませんが、試しておく価値はあります。JavaScript タスク ランナーはブラウザーの外部で機能し、一般的にはコマンド ラインで Node.js を使用します。このようなタスクランナーにより、フロントエンド開発に関連するタスクを容易に実行できるようになります。たとえば、ファイルの縮小、複数ファイルの結合、スクリプトの依存関係の判断と HTML ページへのスクリプト参照の正しい順序での挿入、単体テスト ハーネスの作成、TypeScript や CoffeeScript のようなフロントエンド ビルド スクリプトの処理など、さまざまなタスクを実行できます。

Grunt と Gulp のどちらを選択するか

タスク ランナーの選択は、ほとんどの場合、個人の好みやプロジェクトとの相性で決まります。ただし、特定のタスク ランナーだけをサポートするプラグインを個人的に使用したい場合は除きます。主な違いは、Grunt が JSON 構成設定に従って動作し、通常各 Grunt タスクがタスク間のやり取りに際し、情報を受け渡すための中間ファイルを作成しなければならないのに対し、Gulp は 実行可能な JavaScript コード (単なる JSON ではありません) に従って動作し、一時ファイルを使用しなくても結果をタスク間でストリーミングできる点です。Gulp は比較的新しいツールなので、比較的新しいプロジェクトで使われていることが多いようです。これに対して Grunt は jQuery など著名なサポーターが多く、jQuery のビルドにも Grunt が使用されています。Grunt と Gulp はどちらもプラグインを使って機能します。このプラグインは、特定のタスクを処理するためにインストールするモジュールです。利用可能なプラグインのエコシステムは広大なもので、多くのタスク パッケージが Grunt と Gulp の両方をサポートしています。つまり、繰り返しになりますが、Grunt と Gulp のどちらを選択するかは個人の好みです。

Grunt のインストールと使用

Grunt と Gulp のインストーラーは、どちらもノード パッケージ マネージャー (npm) です。ノード パッケージ マネージャーについては、10 月のコラム (msdn.com/magazine/mt573714) で簡単に紹介しました。Grunt をインストールするコマンドは、実質的に 2 つの部分に分かれています。最初の部分は、Grunt コマンド ライン インターフェイスのワンタイム インストール、2 番目の部分はプロジェクト フォルダーへの Grunt のインストールです。この 2 つの部分のインストールにより、システムで複数バージョンの Grunt を使用することや、任意のパスから Grunt コマンドライン インターフェイスを使用することが可能になります。

#only do this once to globally install the grunt command line runner
npm install –g grunt-cli
#run within your project folder one time to create package.json
#this file will track your installed npm dependencies
#like grunt and the grunt plug-ins, similar to bower.json (see the last article)
npm init
#install grunt as a dev dependency into your project (again see last article)
#we will still need a gruntfile.js though to configure grunt
npm install grunt –save-dev

Grunt の構成

Grunt 構成ファイルは、構成、プラグインの読み込み、タスクの定義を含むラッパー関数が格納される単なる JavaScript ファイルです (図 1 参照)。

図 1 Grunt 構成ファイル

module.exports = function (grunt) {
  // Where does uglify come from? Installing it via:
  // npm install grunt-contrib-uglify --save-dev
  grunt.initConfig({
    uglify: {
      my_target: {
        files: {
          'dest/output.min.js': '*.js'
        }
      }
    }
  });
  // Warning: make sure you load your tasks from the
  // packages you've installed!
  grunt.loadNpmTasks('grunt-contrib-uglify');
  // When running Grunt at cmd line with no params,
  // you need a default task registered, so use uglify
  grunt.registerTask('default', ['uglify']);
  // You can include custom code right inside your own task,
  // as well as use the above plug-ins
  grunt.registerTask('customtask', function () {
    console.log("\r\nRunning a custom task");
  });
};

コマンドラインから次のように呼び出すだけで図 1 のタスクを実行できます。

#no params means choose the 'default' task (which happens to be uglify)
grunt
#or you can specify a particular task by name
grunt customtask

このようにタスクを実行すると、難読化 (縮小) され、連結された結果が wwwroot/output-min.js に書き込まれます。ASP.NET による縮小とバンドルを使用したことがあれば、このプロセスの違いがおわかりいただけるでしょう。プロセスはアプリの実行や、コンパイルにも結び付けられず、難読化のようなタスクを実行する際にかなり多くのオプションを選択できるようになっています。個人的には、こちらのワークフローの方が単純で理解しやすいように感じます。

Grunt を使って複数のタスクを相互に連鎖させると、タスクに依存関係を持たせることができます。このように依存関係にあるタスクを同期をとって実行し、1 つのタスクが完了しなければ、次のタスクには移れないようにすることができます。

#Specify uglify must run first and then concat. Because grunt works off
#temp files, many tasks will need to wait until a prior one is done
grunt.registerTask('default', ['uglify', 'concat']);

Gulp のインストールと使用

Gulp のインストールは Grunt と同様です。Gulp について少し詳しく取り上げますが、これは重複する内容が多くなるのを防ぐためで、どちらでも同じことが可能です。Gulp には、システムの任意のパスから Gulp を使用できるグローバル インストールと、特定のプロジェクトに使用するバージョンが管理されるプロジェクト フォルダーへのローカル インストールの 2 つがあります。グローバルにインストールした Gulp が、ローカル プロジェクトにインストールした Gulp を検出すると、その Gulp に制御を渡し、プロジェクトのバージョンの Gulp が優先されるようにします。

#Only do this once to globally install gulp
npm install –g gulp
#Run within your project folder one time to create package.json
#this file will track your installed npm dependencies
#like gulp and the gulp plug-ins
npm init
#Install gulp as a dev dependency into your project
#we will still need a gulpfile.js to configure gulp
npm install gulp --save-dev

Gulp の構成と API

Gulp の構成は、Grunt とは大きく異なります。gulpfile.js 構成ファイル (図 2 は代表的な構造) には、プラグインを読み込み、タスクを定義するための「必需品」を含みます。JSON 構成設定は使用しておらず、タスクはコードに従って動作します。

図 2 Gulp 構成ファイル

// All the 'requires' to load your
// various plug-ins and gulp itself
var gulp = require('gulp');
var concat = require('gulp-concat');
// A custom task, run via: gulp customtask
gulp.task('customtask', function(){
  // Some custom task
});
// Define a default task, run simply via: gulp
gulp.task('default', function () {
  gulp.src('./lib/scripts/*.js')
    .pipe(concat('all-scripts.js'))
    .pipe(gulp.dest('./wwwroot/scripts'));
});

Gulp は、いくつかの重要な API と 概念 (src、dest、pipe、task、および glob) を使って機能します。gulp.src API は、操作のために開くファイルを Gulp に指示します。通常、一時ファイルを作成する代わりに、このように指定したファイルを他の関数にパイプします。これが、Grunt との大きな違いです。以下に、結果をパイプしない gulp.src の基本的な例をいくつか示し、その内容を説明します。この API 呼び出しは、いわゆる glob をパラメーターとして受け取ります。glob とは、基本的には開発者が入力できるパターン (正規表現のようなもの) で、1 つ以上のファイルへのパスを指定します (glob の詳細については、github.com/isaacs/node-glob (英語) を参照してください)。

#Tell gulp about some files to work with
gulp.src('./input1.js');
#This will represent every html file in the wwwroot folder
gulp.src('./wwwroot/*.html')
#You can specify multiple expressions
gulp.src(['./app/**/*.js', './app/*.css']

dest (destination) API は、文字通り出力先を指定するもので、こちらも glob を受け取ります。glob にはかなり柔軟にパスを定義でき、それぞれ個別のファイルに出力することも、1 つのフォルダーに出力することもできます。

#Tell dest you'll be using a file as your output
gulp.dest ('./myfile.js');
#Or maybe you'll write out results to a folder
gulp.dest ('./wwwroot');

Gulp のタスクとは、なんらかの操作を実行するために記述する単なるコードです。タスクの形式はきわめてシンプルですが、タスクはさまざまな方法で使用できます。最も単純なのは、既定のタスクを 1 つと、その他に 1 つ以上のタスクを用意する方法です。

gulp.task('customtask', function(){
  // Some custom task to ex. read files, add headers, concat
});
gulp.task('default', function () {
  // Some default task that runs when you call gulp with no params
});

タスクは並列に実行することも、相互に依存させることもできます。順序が問題にならなければ、以下のように、単純にタスクを連鎖させます。

gulp.task('default', ['concatjs', 'compileLess'], function(){});

上記の例は、既定のタスクを定義します。ここで定義している既定のタスクは何も行いませんが、別のタスクを実行して、JavaScript ファイルを連結し、LESS ファイル (このファイルにタスクのコードが含まれているものとします) をコンパイルします。あるタスクが完了してから他のタスクを実行するという要件が定められている場合は、それらのタスクどうしの依存関係を設定してから、それらのタスクを実行する必要があります。以下のコードでは、既定のタスクは concat の完了を待機し、concat は uglify の完了を待機します。

gulp.task('default', ['concat']);
gulp.task('concat', ['uglify'], function(){
  // Concat
});
gulp.task('uglify', function(){
  // Uglify
});

pipe API は、Node.js Stream API を使用して、ある関数の結果を別の関数にパイプします。一般的なワークフローは、src を読み取り、タスクにパイプして、結果を dest にパイプするという流れになります。gulpfile.js の例では、/scripts 内のすべての JavaScript ファイルを読み取り、その JavaScript ファイルを 1 つのファイルに連結して、その出力を別のフォルダーに書き込んで完了します。

// Define plug-ins – must first run: npm install gulp-concat --save-dev
var gulp = require('gulp');
var concat = require('gulp-concat');
gulp.task('default', function () {
  #Get all .js files in /scripts, pipe to concatenate, and write to folder
  gulp.src('./lib/scripts/*.js')
    .pipe(concat('all-scripts.js'))
    .pipe(gulp.dest('./wwwroot/scripts'));
}

ここで、実践的で現実に即した例を考えます。ファイルを連結し、情報を格納したヘッダーをソース コード ファイルに追加することがよくあります。この操作は、いくつかのファイルを開発中の Web サイトのルートに追加するわずかな手順で簡単に実行できます (このタスクをすべてコードで行うこともできます。これについては、Gulp ヘッダーに関するドキュメントを参照してください)。まず、追加するヘッダーを含む copyright.txt というファイルを作成します。

/*
MyWebSite Version <%= version %>
https://twitter.com/adamtuliper
Copyright 2015, licensing, etc
*/

次に、現在のバージョン番号を含む version.txt というファイルを作成します (バージョン番号をインクリメントする gulp-bump や grunt-bump のようなプラグインもあります)。

1.0.0

ここで、gulp-header rプラグインと gulp-concat プラグインをプロジェクトのルートにインストールします。

npm install gulp-concat gulp-header --save-dev

また、これらのプラグインを手作業で package.json ファイルに追加すると、Visual Studio が自動的にパッケージを復元します。

最後に、gulpfile.js を用意して、実行する操作を Gulp に指示します (図 3 参照)。すべてのスクリプトを連結するのではなく、それぞれのファイルにヘッダーを追加するだけであれば、pipe (concat) 行をコメントにします。実にシンプルです。

図 3 Gulpfile.js

var gulp = require('gulp');
var fs = require('fs');
var concat = require("gulp-concat");
var header = require("gulp-header");
// Read *.js, concat to one file, write headers, output to /processed
gulp.task('concat-header', function () {
  var appVersion = fs.readFileSync('version.txt');
  var copyright =fs.readFileSync('copyright.txt');
  gulp.src('./scripts/*.js')
  .pipe(concat('all-scripts.js'))
  .pipe(header(copyright, {version: appVersion}))
  .pipe(gulp.dest('./scripts/processed'));
});

以下のコマンドを使用してタスクを実行するだけで、すべての .js ファイルが連結され、カスタム ヘッダーが追加されて、./scripts/processed フォルダーに出力が書き込まれます。

gulp concat-header

Task Runner Explorer

Visual Studio では、Task Runner Explorer を通じて Gulp と Grunt のサポートが提供されます。Task Runner Explorer は Visual Studio 2015 に含まれ、Visual Studio 拡張機能として使用できます。Task Runner Explorer を起動するには、[表示] をクリックし、[その他のウィンドウ] をポイントして [Task Runner Explorer] をクリックします。Task Runner Explorer は実に有能で、プロジェクトに gulpfile.js または gruntfile.js 含まれているかどうかを検出し、タスクを解析して、見つけたタスクを実行するための UI を提供します (図 4 参照)。さらに、プロジェクトで定義済みのアクションが発生した場合に実行するタスクを定義できます。これについては、ASP.NET 5 の既定のテンプレートでこの機能が使用されているため、後ほど説明します。タスクを右クリックするだけで、タスクを実行したり、特定のアクション (開いているプロジェクトに対してタスクを実行するなど) にバインドすることができます。

Grunt と Gulp の両方のタスクとオプションを表示する Task Runner Explorer
図 4 Grunt と Gulp の両方のタスクとオプションを表示する Task Runner Explorer

図 5 からわかるように、Grunt の場合、タスクを選択すると Gulp にはないオプションが表示されます。それは、Force オプション (警告を無視するオプション) と Verbose オプション (左上の [F] と [V]) です。これらは、Grunt コマンドラインに渡す単なるパラメーターです。Task Runner Explorer が優れているのは、Grunt と Gulp のコマンドラインに渡されるコマンドを (タスクの出力ウィンドウに) 表示する点です。そのため、背後で実行される操作もすべて把握できます。

Grunt の追加オプションとコマンドライン
図 5 Grunt の追加オプションとコマンドライン

Gulp と ASP.NET 5

Visual Studio 2015 に含まれる ASP.NET 5 テンプレートは Gulp を使用します。Gulp はプロジェクトの node_components フォルダーにインストールされるため、プロジェクトで Gulp を使用する準備は万全です。もちろん、ASP.NET 5 プロジェクトでは Grunt も使用できます。ただし、npm を使用するか、devDependencies 内の packages.json に Grunt を追加して Visual Studio のパッケージの自動復元機能を実行することで、Grunt をプロジェクトにインス トールする必要があります。重要なのは、好みに応じてコマンドラインと Visual Studio のどちらを使用しても、これらの操作をすべて実行できる点です。

本稿執筆時点の ASP.NET 5 テンプレートには、.css ファイルと .js ファイルを縮小して連結するタスクがいくつか含まれています。以前のバージョンの ASP.NET では、これらのタスクは実行時にコンパイル済みコードで処理されていました。この方法は、タスクを実行する場所としても、タイミングとしても理想的とはいえませんでした。図 6 に示すように、clean タスクと min タスクは、それぞれ css メソッドと js メソッドを呼び出して、これらのファイルを縮小したり、以前に縮小したファイルをクリーンアップします。

ASP.NET 5 プレビュー テンプレートに含まれるタスク
図 6 ASP.NET 5 プレビュー テンプレートに含まれるタスク

以下の Gruntfile.js コード行は、複数のタスクを同時に実行するもう 1 つの例です。

gulp.task("clean", ["clean:js", "clean:css"]);

Visual Studio には、Grunt タスクと Gulp タスクを 4 つの異なるアクションにバインドするオプションがあります。MSBuild では、複数のタスクを実行する場合、ビルド前に実行するコマンド行とビルド後に実行するコマンド行を定義するのが普通でした。Task Runner Explorer では、Before Build、After Build、Clean、Project Open といったイベントを定義して、同様のタスクを実行できます。これにより、コメントが単純に gulpfile.js ファイルや gruntfile.js ファイルに追加されます。これらのコメントは実行には影響を与えませんが、Task Runner Explorer によって検索されます。ASP.NET 5 gulpfile.js の "clean" バインドを確認するには、ファイルの先頭にある次の行を参照してください。

// <binding Clean='clean' />

イベントのフックに必要なのはこれだけです。

まとめ

Grunt と Gulp は、どちらも開発者の Web ツールとして手元に置いておきたいツールです。どちらも十分なサポートが用意されていて、使用可能なプラグインには幅広いエコシステムがあります。どのような Web プロジェクトでも Grunt と Gulp を使うメリットがあります。詳細については、以下の資料を参考にしてください。

  • Grunt の「Getting started」(概要) ドキュメント (bit.ly/1dvvDWq、英語)
  • Gulp のレシピ (bit.ly/1L8SkUC、英語)
  • パッケージ管理とワークフローの自動化: マシン パッケージ マネージャー (bit.ly/1FLwGW8、英語)
  • Node.js モジュールとパッケージの習得 (bit.ly/1N8UKon、英語)
  • Adam’s Garage (Adam のガレージ、bit.ly/1NSAYxK、英語)

Adam Tuliper は、南カリフォルニア在住のマイクロソフトのシニア テクニカル エバンジェリストです。Web 開発者、ゲーム開発者、兼 Pluralsight の執筆者で、すべての技術に愛着を持っています。Twitter (@AdamTuliper、英語) で彼をフォローできます。Adam’s Garage (Adam のガレージ、bit.ly/1NSAYxK、英語) で読むことができます。

この記事のレビューに協力してくれたマイクロソフト技術スタッフの Michael Palermo に心より感謝いたします。