開発者は、限りある大事な時間をコーディングに費やします。たとえ面倒な会議をさぼっても、仕事時間の大半は地味な作業に奪われています。
- テンプレートとコンテンツファイルからHTMLを生成する
- 新しい画像と変更された画像を圧縮する
- SassをCSSコードにコンパイルする
- スクリプトからconsoleとdebuggerのステートメントを削除する
- ES6をブラウザーとの互換性が高いES5に変換する
- コードの埋め込みと検証
- CSSとJavaScriptファイルの連結と縮小
- 開発サーバー、ステージングサーバー、運用サーバーにファイルを展開する
なにか変更があるたびに、作業は繰り返されます。作業を繰り返すうちに、一番優秀なエンジニアでさえ画像のひとつやふたつ圧縮し忘れたりするものです。年々、試作段階のタスクは面倒で時間のかかるものになってきています。避けようがないコンテンツやテンプレートの変更も恐ろしいものです。退屈で、同じことの繰り返し。もっと実りのある仕事に時間を費やせたらいいと思いませんか?
そう考えている人には、タスクランナーあるいはビルドプロセスが必要です。
すごく面倒くさそう!
ビルドプロセスを構築するには時間がかかります。たしかに各タスクを手動で処理するよりも複雑ですが、長い目で見ると何時間も節約できて人的ミスも減り、自分自身も発狂せずに済むのです。ぜひ自動化というアプローチを考えましょう。
- 最初に一番面倒なタスクを自動化する
- ビルドプロセスを複雑にしすぎない。最初のセットアップは数時間で十分
- タスクランナーを選んだら、しばらく使ってみること。気まぐれでほかに目移りしないようにする
紹介するツールや概念にはまったく知らないものもあるかもしれませんが、深呼吸して、1つずつ片付けていきましょう。
タスクランナーの選択肢
10年以上前からGNU Makeといったビルドツールはありましたが、Web開発に特化したタスクランナーが登場したのは最近です。最初にクリティカルマス(物事が一気に普及する臨界点)を突破したのはGruntでした。Gruntは、JSON形式のファイルで設定できるプラグインを使った、Node.jsのタスクランナーです。Gruntは大成功しましたが、問題も多くありました。
- ファイル監視などの基本的な機能でもプラグインが必要
- Gruntプラグインは1つで複数のタスクを実行するものがあり、カスタマイズが難しい
- JSON形式による設定は、ごく基本的なタスク以上のことをしたいときには扱いづらい
- 各プロセスを実行するたびにファイルを保存するので、パフォーマンスが落ちることがある
多くの課題はGruntの後発のバージョンで修正されたものの、当初からこれらの課題の多くを改良して登場したのが、gulpです。
- ファイル監視などの機能は最初から組み込まれている
- gulpプラグインの大半は、1つのタスクだけ実行するように作られている
- gulpはJavaScriptのコードで設定できるので、より簡潔で読みやすく、改変がしやすく、柔軟である
- gulpは一連の処理で使う複数プラグイン間のデータ受け渡しにNode.js streamsを使うため、より動作が速い。ファイルはタスク終了時にのみ保存される
もちろんgulp自体も完璧ではないため、Broccoli.js、Brunch、webpackといったほかの新しいタスクランナーも開発者の関心を引くために争っています。もっとも最近ではnpm自体がよりシンプルな選択という主張もあります。どの方法にもそれぞれ良し悪しがありますが、gulpは現在、Web開発者の40%以上に使用されている一番人気のツールです。
gulpはNode.jsが必要で、JavaScriptの知識があれば越したことはないものの、どのプログラミング言語を愛用していても便利に使えるはずです。
gulp 4は使えるか?
この記事では、執筆時点での最新版であるgulp 3の使い方について説明します。gulp 4も開発が続いていますが、現在もなおベータ版のままです。gulp 4への乗り換えもできますが、正式版のリリースまではgulp 3の使用をおすすめます。
ステップ1:Node.jsをインストールする
Node.jsはWindows、Mac、Linuxで、nodejs.org/download/からダウンロードできます(日本版編注:Node.jsのトップからもインストーラー付きパッケージをダウンロードできます)。インストール方法も、バイナリ、パッケージマネージャー、Dockerイメージがあり、それぞれ詳しい説明が用意されています。
インストールしたら、コマンドプロンプトを開いて次のように入力してください。
node -v
これでバージョン番号が分かります。これからNode.jsパッケージマネージャーnpmを、モジュールのインストールなどでフル活用するので、バージョン番号を調べてください。
npm -v
Linuxユーザーへ:Node.jsモジュールはグローバルインストールされ、システム全体で使用可能です。しかし多くのユーザーの場合、npmコマンドにsudoプレフィックスが付いていない限り、グローバルディレクトリに書き込む権限がありません。多くのnpmのパーミッションへの対策やnvmを使う方法がありますが、私の場合はデフォルトのディレクトリを変更しています。たとえば、Ubuntu/Debianベースのプラットホームであれば、次のようにしています。
cd ~
mkdir .node_modules_global
npm config set prefix=$HOME/.node_modules_global
npm install npm -g
そして以下のコマンドを~/.bashrcの後ろに加えます。
export PATH="$HOME/.node_modules_global/bin:$PATH"
これで更新します。
source ~/.bashrc
ステップ2:gulpをグローバルインストールする
gulpのコマンドライン機能をグローバルインストールして、どのプロジェクトフォルダーからでもgulpコマンドが使えるようにします(日本版編注:権限がない場合はsudo npmのようにsudoつけてプレフィックスを付けると実行できます)。
npm install gulp-cli -g
gulpのインストールを検証します。
gulp -v
ステップ3:プロジェクトの設定
Node.jsプロジェクトについて:もし、すでに設定ファイルpackage.jsonがあるならば、このステップは飛ばしてかまいません。
この記事ではproject1フォルダーに、新規または従来からのプロジェクトが入っていることを想定しています。フォルダーを開いて、npmで初期化します。
cd project1
npm init
npmからいくつか尋ねられるので、値を入力するかあるいは初期値で良いならEnterキーを押してください。完了したら、npmの設定を保存するpackage.jsonファイルが生成されます。
以降この記事では、プロジェクトフォルダーの中に、以下に示すサブフォルダーがあるという想定で作業します。
srcフォルダー:ソースファイルを入れる
srcフォルダーにはさらに以下のサブフォルダーがあります。
- html:HTMLソースファイルとテンプレート
- images:圧縮されていない元の画像
- js:スクリプトファイル
- scss:Sassの.scssファイル
buildフォルダー:コンパイル済み・処理済みのファイルを入れる
gulpは必要に応じ、buildフォルダーにファイルやサブフォルダーを生成します。
- html:コンパイルされた静的なHTMLファイル
- images:圧縮された画像
- js:1つに連結されて最小化されたJavaScriptファイル
- css:1ファイルにコンパイルされて最小化されたCSSファイル
プロジェクトによって異なりますが、記事では上のような構造になっています。
ヒント:Unixベースのシステムを使っていて、記事に合わせたフォルダー構造を構築する場合、以下のようにします。
mkdir -p src/{html,images,js,scss} build/{html,images,js,css}
ステップ4:gulpをローカルインストールする
以下のコマンドを使ってプロジェクトフォルダーにgulpをインストールします。
npm install gulp --save-dev
gulpが開発用の依存オブジェクトとしてインストールされ、package.jsonの中の"devDependencies"の欄はそれに合わせて自動更新されます。以降も、gulpとそのプラグインはすべて開発用の依存オブジェクトであると想定しています。
ほかのデプロイ方法
開発用の依存オブジェクトはOSの環境変数NODE_ENVがproductionにセットされていたらインストールできません。これは通常Live Serverで、Mac/Linuxの場合、次のコマンドで設定しています。
export NODE_ENV=production
Windowsならば次のコマンドです。
set NODE_ENV=production
この記事では、リソースはbuildフォルダーにコンパイルされ、Gitリポジトリにコミットされるかもしくはサーバーに直接アップロードされることを想定しています。しかし、ファイル生成も変更したいならLive Serverにビルドするのが良いでしょう(例:HTML、CSS、JavaScriptのファイルを開発用ではなく本番用の環境にコンパイルしたい)。この場合、gulpとプラグインに--saveオプションを使います。次のようにします。
npm install gulp --save
こうすれば、package.jsonの中の"dependencies"の欄で、gulpをアプリの依存先として設定できます。npm installコマンドでインストールして、どこにデプロイされたプロジェクトでも実行できます。また必要に応じて、どのプラットホームにでも一連のファイルを生成できるため、リポジトリからbuildフォルダーを削除してかまいません。
ステップ5:gulpの設定ファイルを作る
プロジェクトのルートフォルダーに、設定ファイルgulpfile.jsを新規作成します。始めに基本的なコードを加えてください。
// gulp.js configuration
var
// modules
gulp = require('gulp'),
// development mode?
devBuild = (process.env.NODE_ENV !== 'production'),
// folders
folder = {
src: 'src/',
build: 'build/'
}
;
これはgulpモジュールを参照するコードです。開発モード(developmentまたはnon-production)で走らせるならdevBuild変数の値をtrueにして、リソースフォルダーとビルド先フォルダーを指定します。
ES6についての注意:この記事はES5互換のJavaScriptコードで書いています。よって--harmonyフラグがあっても無くても、すべてのバージョンのgulpとNode.jsで動作します。ES6の大半の機能はNode6以上ならサポートされているので、新しいバージョンを使用しているならアロー関数、let、constといった変数も自由に使えます。
まだgulpfile.jsには手をつけていません。なぜならまだ必要なことがあるからです。
ステップ6:gulpタスクを作る
gulpそれ自体はなにもしません。使うには、次のようにします。
- gulpプラグインをインストールする
- プラグインを使えるようにして、なにか役に立つタスクを書く
自分でプラグインを作るのも不可能ではありません。しかし、3000個ものプラグインが揃っているので、ほぼ自作プラグインは必要ないでしょう。gulpのディレクトリで探したり、npmjs.comで探したり、あるいは強力なGoogleの力を利用して「gulp ○×△□」と検索すればよいのです。
gulpモジュールには3つの基本的なタスクメソッドがあります。
- gulp.task:新規タスクを定義する。その際、タスクの名前のほかにオプションで依存オブジェクトや関数をセットする
- gulp.src:リソースファイルを置いたフォルダーをセットする
- gulp.dest:生成されたビルドファイルを置くフォルダーをセットする
各プラグインの呼出しは、pipeメソッドを使って、.srcと.destの間にいくつでもセットできます。
画像に関するタスク
例で説明するのが一番分かりやすいので、画像を圧縮してbuildフォルダーにコピーする簡単なタスクを作ります。この処理には時間がかかるかもしれないので、新規および変更されたファイルだけを圧縮します。ここで2つのプラグインが役に立ちます。gulp-newerとgulp-imageminです。以下のようにコマンドラインでインストールします。
npm install gulp-newer gulp-imagemin --save-dev
gulpfile.jsファイルの先頭に、2つのモジュールへの参照を追加できます。
// gulp.js configuration
var
// modules
gulp = require('gulp'),
newer = require('gulp-newer'),
imagemin = require('gulp-imagemin'),
次はgulpfile.jsの末尾に、関数として画像処理のタスクを定義します。
// image processing
gulp.task('images', function() {
var out = folder.build + 'images/';
return gulp.src(folder.src + 'images/**/*')
.pipe(newer(out))
.pipe(imagemin({ optimizationLevel: 5 }))
.pipe(gulp.dest(out));
});
すべてのタスクは構文的には似ています。たとえば、次のようになります。
- imagesという名の新しいタスクを作る
- 戻り値付きの関数を定義する
- ビルド済みファイルを置くoutフォルダーを定義する
- gulpのsrcリソースフォルダーをセットする。/**/*と書けばサブフォルダー内の画像も処理する
- すべてのファイルをgulp-newerモジュールのpipe(パイプのように1つずつ順番に送る)にセットする。各ソースファイル(画像)が対応する変換先ファイルより新しければ、通過して次の処理へ行く。そうでなければ破棄される
- 残りの新規または変更のあったファイルは、gulp-imageminでpipe処理される。このときオプションで引数optimizationLevel(最適化のレベル)をセットできる
- 圧縮された画像は、outのところで指定した、gulpのdestフォルダーに出力される
gulpfile.jsを保存したら、タスクを実行する前に自分のプロジェクトのsrc/imagesフォルダーにいくつか画像を置いてください。コマンドラインからタスクを実行するには次のようにします。
gulp images
すべての画像が設定したとおりに圧縮され、次のように出力されます。
Using file gulpfile.js
Running 'imagemin'...
Finished 'imagemin' in 5.71 ms
gulp-imagemin: image1.png (saved 48.7 kB)
gulp-imagemin: image2.jpg (saved 36.2 kB)
gulp-imagemin: image3.svg (saved 12.8 kB)
試しに再度gulp imagesを実行しても、新しい画像が無いので今度はなにも起きません。
HTMLに関するタスク
同じように、今度はソースのHTMLフォルダーからファイルをコピーするタスクを作ってみます。gulp-htmlcleanプラグインを使えば、HTMLから必要のない空白や属性を取り除くことで安全にサイズを最小化できます。
npm install gulp-htmlclean --save-dev
gulpfile.jsの先頭で以下を参照します。
var
// modules
gulp = require('gulp'),
newer = require('gulp-newer'),
imagemin = require('gulp-imagemin'),
htmlclean = require('gulp-htmlclean'),
gulpfile.jsの末尾にhtmlタスクを作れます。
// HTML processing
gulp.task('html', ['images'], function() {
var
out = folder.build + 'html/',
page = gulp.src(folder.src + 'html/**/*')
.pipe(newer(out));
// minify production code
if (!devBuild) {
page = page.pipe(htmlclean());
}
return page.pipe(gulp.dest(out));
});
上のコードはgulp-newerを再利用していて、新しい考え方が2つ入っています。
- 引数[images]は、imagesタスクがHTML処理よりも先に動作しなければならないことを指定している(普通はHTMLは画像を参照するため)。依存しているタスクはいくつでもこの配列に入れられ、タスクの実行前に処理される
- 例は環境変数NODE_ENVがproduction(本番)にセットされているときだけHTMLがgulp-htmlcleanのpipeにセットされるようにした。つまり開発中はHTMLが圧縮されないのでデバッグに便利
gulpfile.jsを保存し、gulp htmlをコマンドラインから実行してください。htmlタスクとimagesタスクの両方が実行されます。
JavaScriptに関するタスク
簡単すぎるでしょうか。それでは基本的なモジュールを束ねて、すべてのJavaScriptファイルを処理します。
- gulp-deporderプラグインにより依存オブジェクトが読み込まれたかを確認する。このモジュールは、各スクリプトファイルの先頭のコメント(例:// requires: defaults.js lib.js)を分析して正しい順で読み込まれたかを確認する
- 全スクリプトファイルを、gulp-concatプラグインにより1つのmain.jsファイルにまとめる
- gulp-strip-debugプラグインでconsole文やdebug文をすべて削除し、gulp-uglifyプラグインでコードを最小化する。この処理はプロダクション(本番)モードでのみ実行される
プラグインをインストールします。
npm install gulp-deporder gulp-concat gulp-strip-debug gulp-uglify --save-dev
gulpfile.jsの先頭で参照します。
var
...
concat = require('gulp-concat'),
deporder = require('gulp-deporder'),
stripdebug = require('gulp-strip-debug'),
uglify = require('gulp-uglify'),
続いて、新規でjsタスクを追加します。
// JavaScript processing
gulp.task('js', function() {
var jsbuild = gulp.src(folder.src + 'js/**/*')
.pipe(deporder())
.pipe(concat('main.js'));
if (!devBuild) {
jsbuild = jsbuild
.pipe(stripdebug())
.pipe(uglify());
}
return jsbuild.pipe(gulp.dest(folder.build + 'js/'));
});
保存してgulp jsを実行すると、すごいことが起こります!
CSSに関するタスク
最後にCSS関係のタスクですが、gulp-sassプラグインを使ってSassの.scssファイルを1つの.cssファイルにコンパイルします。node-sass用のgulpプラグインで、爆速のSassエンジンのC/C++ポートLibSass(Rubyのインストールも不要です)をバインドします。記事ではメインのSassファイルscss/main.scssがそのほかの部分を読み込んでくるものと想定しています。
このタスクではgulp-postcssプラグインを通じて、あのすばらしいPostCSSも活用されています。PostCSSには独自プラグイン類が必要なので、インストールするものは次のようになります。
- postcss-assets:ファイルの管理。ファイルパスを解決するプロパティbackground: resolve('image.png');、エンコードされた画像をインライン化するプロパティbackground: inline('image.png');が使える
- autoprefixer:CSSプロパティにベンダープレフィックスを自動で付加する
- css-mqpacker:同じCSSメディアクエリーへの参照箇所が複数あるのを、ひとつにまとめる
- cssnano:production(本番)モードで実行する際にCSSコードを最小化する
最初にモジュール類をすべてインストールします。
npm install gulp-sass gulp-postcss postcss-assets autoprefixer css-mqpacker cssnano --save-dev
そうしたらgulpfile.jsの先頭で参照します。
var
...
sass = require('gulp-sass'),
postcss = require('gulp-postcss'),
assets = require('postcss-assets'),
autoprefixer = require('autoprefixer'),
mqpacker = require('css-mqpacker'),
cssnano = require('cssnano'),
これでgulpfile.jsの末尾に新規でcssタスクを作れます。imagesタスクが依存先として設定されていることに注意してください。postcss-assetsプラグインはビルドプロセスの間に画像を参照するからです。加えて、ほとんどのプラグインは引数を受け取れますので、詳しくはプラグインの説明を参照してください。
// CSS processing
gulp.task('css', ['images'], function() {
var postCssOpts = [
assets({ loadPaths: ['images/'] }),
autoprefixer({ browsers: ['last 2 versions', '> 2%'] }),
mqpacker
];
if (!devBuild) {
postCssOpts.push(cssnano);
}
return gulp.src(folder.src + 'scss/main.scss')
.pipe(sass({
outputStyle: 'nested',
imagePath: 'images/',
precision: 3,
errLogToConsole: true
}))
.pipe(postcss(postCssOpts))
.pipe(gulp.dest(folder.build + 'css/'));
});
ファイルを保存し、コマンドラインからタスクを実行してくださ。
gulp css
ステップ7:タスクの自動化
これまでは1度に1つのタスクだけを実行しました。しかしgulpfile.jsに新しくrunタスクを加えると、1コマンドですべてのタスクを実行できます。
// run all tasks
gulp.task('run', ['html', 'css', 'js']);
保存してコマンドラインからgulp runを入力し、すべてのタスクを実行してください。なおここでimagesタスクの実行を省いているのは、すでにhtmlタスクとcssタスクの中で、依存先としてセットされているためです。
これでもまだ手間がかかりすぎると思いますか? gulpにはほかのメソッドもあります。gulp.watchはソースファイルを監視し、ファイルが変更されたときに適切なタスクを実行します。このメソッドでは、監視対象フォルダーと、変更があった際に実行したいタスクを指定します。gulpfile.jsの末尾に、新規でwatchタスクを作ります。
// watch for changes
gulp.task('watch', function() {
// image changes
gulp.watch(folder.src + 'images/**/*', ['images']);
// html changes
gulp.watch(folder.src + 'html/**/*', ['html']);
// javascript changes
gulp.watch(folder.src + 'js/**/*', ['js']);
// css changes
gulp.watch(folder.src + 'scss/**/*', ['css']);
});
すぐにgulp watchを実行せず、初期実行するタスク(default)を作ります。
// default task
gulp.task('default', ['run', 'watch']);
gulpfile.jsを保存し、コマンドラインにgulpを入力してください。画像、HTML、CSS、JavaScriptがすべて処理され、さらにgulpはファイル変更の監視を続けて、必要なときにタスクを再度実行します。モニタを中止してコマンドラインに戻るにはCtrl/Cmd + Cを押してください。
ステップ8:実戦で活かす!
ほかにも次のようなプラグインが役に立つかもしれません。
- gulp-load-plugins:require宣言なしで、すべてのgulpプラグインモジュールを読み込む
- gulp-preprocess:シンプルなHTMLとJavaScirptのプリプロセッサー
- gulp-less:CSSプリプロセッサー Lessのプラグイン
- gulp-stylus:CSSプリプロセッサー Stylusのプラグイン
- gulp-sequence:指定した順番にgulpのタスクを実行する
- gulp-plumber:gulpが停止するのを防ぐエラーハンドリングのプラグイン
- gulp-size:ファイルのサイズや圧縮後のサイズを表示
- gulp-nodemon:nodemonを使い、変更があった際にNode.jsアプリケーションを自動で再起動する
- gulp-util:ロギングや色コードなどのユーティリティを提供
上のgulp-utilの便利なメソッドの1つは.noop()で、処理を一切せずにデータを次のpipeへ渡してしてくれます。これを使えば開発/プロダクションの処理を分けるコードをもっときれいに書けるかもしれません。
var gutil = require('gulp-util');
// HTML processing
gulp.task('html', ['images'], function() {
var out = folder.src + 'html/**/*';
return gulp.src(folder.src + 'html/**/*')
.pipe(newer(out))
.pipe(devBuild ? gutil.noop() : htmlclean())
.pipe(gulp.dest(out));
});
gulpはまた、ほかのNode.jsモジュールをコールできます。コールするのは必ずしもプラグインである必要はありません。たとえば、次のようなものです。
- browser-sync:変更があった際に、リソースを再読み込みないしブラウザーを更新する
- del:ファイルやフォルダーを消去する(実行のたびにbuildフォルダーを掃除できる)
少しだけ時間を投資すれば、gulpは何時間もの苛立たしい開発時間を節約してくれるのです。gulpの恩恵は、次のようになります。
- プラグインが山ほどある
- pipeを使った設定は読みやすくてフォローしやすい
- gulpfile.jsファイルはほかのプロジェクトでも改変・再利用できる
- 全体的にサイトが軽くなりパフォーマンスが向上する
- デプロイ作業をシンプルにできる
以下のリンクも参照してください。
- gulp home page(gulpホームページ)
- gulp plug-ins(gulpプラグイン)
- npm home page(npmホームページ)
これまでの処理を単純なWebサイト制作に適用すれば、トータルで50%以上も軽くなります。自分自身のサイトをページウェイト分析ツールやNew Relicが提供している洗練された性能計測ツールを使えば計測できます。
※本記事はGiulio Mainardi、Tim Severienが査読を担当しています。最高のコンテンツに仕上げるために尽力してくれたSitePointの査読担当者のみなさんに感謝します。
(原文:An Introduction to gulp.js)
[翻訳:西尾健史/編集:Livit]