このドキュメントは、gulpfile
の再利用性/メンテナンス性を高めることを目的とした、非公式なスタイルガイドです。
更新情報
- 2014/10/05 - バージョン番号つけました。
- 2014/10/04 - 「タスク辞書」あらため「タスクの共有」に。以前の内容はgulpfilesとして別ドキュメントに。
はじめに
gulpfile
はgulp.jsのタスクを定義するファイルです。スクリプトファイルなので自由に書ける反面、プロジェクトが大きくなると、書式を統一が問題になります。そんな時、このスタイルガイドが役に立つでしょう。対象となるのは次のようなケースです。
- gulp.js に慣れて来て、再利用性のあるタスクを書きたい
- チームで運用する必要があり、書式を統一したい
スタイルガイドとして、下記を参考にしています。また、ここで述べない JavaScript / CoffeeScript の言語としての記述スタイルについても、こちらを参照してください。
チェックリスト
スタイルガイドに全て目を通す前に、簡単に診断できるチェックリストを挙げておきます。後半への見出しも兼ねます。
項目 | チェックすること |
---|---|
a.レイアウト | インデントあたりスペース2つ、1行あたり最大79字、UTF-8 |
b.記述言語 | JavaScript か CoffeeScript |
c.プラグイン | ブラックリスト入りはNG、インラインプラグインは適宜 |
d.繰り返し | ファイルならgulp-foreach、配列ならmerge-stream |
e.非同期処理 | 関数がプロミスを返す場合はプロミス、それ以外はコールバック |
f.設定ファイル |
package.json にカスタムフィールド、多い場合は別ファイル |
g.ファイル分割 | 1ファイルあたり50行程度まで、それ以上は目的別に分割 タスク名はファイル名を接頭辞にしてハイフン区切り タスクファイルは task/ またはtasks/ ディレクトリに配置require-dirで読み込み |
コードレイアウト
タブかスペースか
スペースのみを使います。1インデントあたり、2スペースとします。タブとスペースを混ぜてはいけません。
1ラインの最大文字数
すべての行は、最大79文字までに制限します。モニターがどんなに大きくても、これを越えてはいけません。
エンコーディング
UTF-8です。
モジュールの読み込み
順序変更 / コピー&ペーストしやすいように、一行ずつvar
を使います。
var gulp = require('gulp');
var browserify = require('browserify');
var source = require('vinyl-source-stream');
以下は「悪い」例です。同じこと(モジュールの読み込み)をしているにも関わらず、行ごとの表記がことなるのは望ましくありません。JavaScriptの省略記法は、初学者を混乱させるので避けます。
var gulp = require('gulp')
,browserify = require('browserify')
,source = require('vinyl-source-stream');
記述言語
gulpfile
は、下記のいずれかで記述します。
- JavaScript
- CoffeeScript
JavaScriptが「共通言語」
外部に公開する場合、プレゼンテーションで表示する場合は、JavaScriptを基本とします。また、フォーラムなどへの質問に際してもCoffeeScriptを主題としない限り、JavaScriptで投稿するべきです。
CoffeeScript
共通言語としてJavaScriptが推奨されながら、CoffeeScriptを使う理由は、「設定ファイル」としての見やすさです。gulpfile
の記述にあたっての利点を下記に列挙します。
- 括弧の省略
- カンマの省略
- 文字列挿入 (String Interpolation)
その他のAltJS言語
gulpfile
は、その他のLiveScript他の言語で記述することも可能ですが、これはチームメンバーで合意が出来ている場合に限定されます。将来参加するメンバーについても想定に入れておくべきでしょう。
プラグインについて
ブラックリスト入りしているプラグインを避ける
プラグインは、公式ディレクトリで検索可能です。ただし、ブラックリストに入っていると表示されません。実際に使用する前に、ここで確認しておきましょう。gulp.jsのバージョン4以降は下記のコマンドでもチェックが可能になります。
$ gulp --verify
どうしても「ブラックリスト入り」を利用する必要がある場合は、その旨をgulpfile
にコメントとして明記します。
インラインプラグイン
gulpfile
内で、関数としてその場だけで使う「インラインプラグイン」を実装するケースがあります。
- 再利用性が低い
- プラグインが公開されていない
- JavaScript APIを使う必要がある
などの場合です。下記はDuoを使う一例ですが、別関数とすることでタスク内の見通しが良くなっています。
gulp.task('script', function() {
gulp.src('main.js')
.pipe(duo())
.pipe(gulp.dest('dist'))
})
function duo() {
return map(function(file, cb) {
Duo(file.base)
.src(file.contents.toString());
.run(function(err, src) {
if (err) return cb(err);
file.contents = new Buffer(src);
cb(null, file);
});
});
}
スクリプト
繰り返し(1)
ファイルについての繰り返しには、gulp-foreachを使います。
var foreach = require('gulp-foreach');
gulp.task('zip', function(){
return gulp.src('recipe/*')
.pipe(foreach(function(stream, file){
return gulp.src(
path.basename(file.path) + '/**/*',
{ cwd: 'recipe/' }
)
.pipe(zip(name + '.zip'));
}))
.pipe(gulp.dest('download/'));
});
繰り返し(2)
配列内の繰り返しについては、merge-streamを使います。
var merge = require('merge-stream');
var data = [{ name: 'a' }, { name: 'b' }, { name: 'c' }];
gulp.task('page', function(){
return merge(data.map(function(entry){
return gulp.src('template.html')
.pipe(consolidate(entry))
.pipe(gulp.dest('dist/'));
}));
});
※CoffeeScriptの場合は、for
文の方が読みやすいでしょう。
プロミス (promise)
タスク内で非同期関数が使われる場合で、関数がプロミス(promise)を返す場合は、関数の結果をタスク内でreturn
します。コールバックと、プロミスの両方に対応する関数の場合、プロミスを使うことを優先します。
gulp.task('async', function(){
return iHaveAPromise();
});
独自のプロミス
タスク内で独自のプロミスを生成することは、可読性を下げます。特にチーム内のデザイナーにとって、次のようなコードは理解しにくいものとなります。次項のコールバックを使う方が望ましいでしょう。
gulp.task('async', function(){
var deferred = Q.defer();
setTimeout(function() {
deferred.resolve();
}, 1000);
return deferred.promise;
});
そんなとき「then」
プロミスはthen
を使って次の処理につなげられる点が便利ですが、タスク内での利用は必ずしも推奨されません。then
でタスク内の処理を長々と書くよりも、
- タスクは十分に小さいか (特定の目的にフォーカスしているか)
- タスクの外側でコントロールすべきではないか
を確認すべきです。連続するタスクをコントロールするために、次の方法が用意されています。
- gulp.js v3.x: run-sequenceが利用可能です。
- gulp.js v4.x: 標準APIに、
gulp.parallel
とgulp.series
が追加されました。
コールバック
タスク内で非同期関数が使われる場合で、プロミスに対応していない場合、引数としてコールバック関数を受け取り、非同期処理が完了時点でコールバック関数を呼び出します。コールバック関数であることを明示するため、callback
ないしcb
を一貫して使うものとします。
gulp.task('async', function(callback){
setTimeout(function() {
callback();
}, 1000);
});
設定ファイル
gulpfile
を複数プロジェクトで共有して、固有の値だけを変更したい場合があります。その場合は、package.json
にカスタムフィールドを追加して記述します。設定の数が大量にある場合は、config.json
といった名称で別ファイルに記述することが望ましいでしょう。
下記は、パッケージ名をフォントの名称にしている例です。
var meta = require('./package.json');
gulp.task('icon', function(){
gulp.src('icon/*.svg')
.pipe(iconfont({ fontName: meta.name })
.pipe(gulp.dest('dist'));
});
ファイルの分割
通常のスクリプトと同様に、長過ぎるgulpfile
はメンテナンス性が下がります。目的別に分割する必要があります。
gulpfile
は1ファイルあたり、50行程度に留めるべきです。何行以下という制限は特に設けませんが、ひとつのファイルには、ひとつの目的のタスクのみを集約します。例えば、JavaScriptを扱うタスクと、CSSを扱うタスクは別のファイルにするべきです。
プロジェクトが大きくなると、担当者ごとに管理するgulp.jsのタスクが分かれて行きます。その際、他のメンバーが担当するタスクを不用意に変更する必要がないよう、タスクのモジュール性を維持するのは、良い戦略です。
以下、便宜的に次のように呼ぶこととします。
- ルートファイル(root file): プロジェクトディレクトリ直下の
gulpfile
- タスクファイル(task file): タスクや目的別に分けられた
gulpfile
タスクの再利用性
プロジェクトごとにタスクの組み合わせや設定が変わっても、タスクファイルはプロジェクト間で流用可能なケースが多々あります。下記の方針を立てることは、再利用性を高めるために重要です。
- 目的ごとにタスクファイルを分ける
- タスクファイル外のタスクに依存しない
- シンプルに留める
1タスクごとに1タスクファイルとする必要はありません。例えばタスクファイルcoffee.js
に、
- coffee
- coffee-client
- coffee-server
- coffee-lint
といった複数のタスクを定義して構いません。これらのタスクは、必要とするモジュールも、利用されるシーンも重なっており、1ファイルにまとめるのが合理的です。
ルートファイルに含めるべきタスク
プロジェクト間で共有しにくいタスクがあります。次の3つについては、ルートファイルに含めます。
- default
- clean
- watch
また、以下のケースも、ルートファイルに書くと良いでしょう。
- 横断的に実行すべきタスク (他の
gulpfile
に依存するタスク) - アドホックなタスク
タスクファイルの置き場所
プロジェクトのルートにgulpfile
が散乱するのは、望ましくありません。task
ないし、tasks
ディレクトリにまとめます。下記は配置の例です。
- gulpfile.js (ルートファイル)
- task/
- bower.js (タスクファイル)
- css.js (タスクファイル)
- coffee.js (タスクファイル)
- icon.js (タスクファイル)
タスクファイルの読み込み
タスクファイルを読み込むには、require-dir
モジュールを利用します。ルートファイルの先頭に下記を追加します。
var requireDir = require('require-dir');
var dir = requireDir('./task');
タスク名は、ファイルを越えてgulp.jsのプロセス内で共有されます。そのため、この記述だけで、他のファイルに書かれたタスク('css'とか'script'とか)を呼び出すことが出来るようになります。
タスクファイルの記述
タスクファイルも、通常のgulpfile
と同様に記述します。module.exports = yourFunction
の形式で書く必要はありません。
タスク名は、ファイル名と同一か、ファイル名を接頭辞とします。
対象 | 例 |
---|---|
ファイル名 | coffee.js |
タスク名 |
coffee , coffee-client , coffee-server など |
タスクファイルの共有
再利用を促進するには、タスクファイルをGitHubに置くのが有効です。(Dotfilesを思い出してください)