私はgulp.js の大ファンです(『面倒な作業も発狂しない!Web制作を超効率化するgulp.jsの始め方(2017年版)』参照)。最近の調査では、フロントエンド開発者の約44%がgulpタスクを使っているそうです(『5000人調査でわかった!世界のフロントエンド開発者が使うツールはこれだ』参照)。
gulpの魅力の1つはその簡便さです。自分のgulpfile.jsにタスク関数を書きます。
gulp.task('doSomething', () => {
// do something
});
そして、コマンドラインからgulp doSomethingと入力してタスクを実行します。好みに応じて基本的なタスクから複雑なものまで取り扱え、さらにサブタスクを含められます。
しかし、タスクはコマンドラインで引数を使えません。たとえば、次のような使い方です。
gulp doSomething --option1 "my string" --option2 123 --option3
例のoption3はtrueとなります。
引数はgulpアプリケーション自身に渡され、タスクには渡されません。gulpは引数の情報を持っていないのでgulpfile.js内では変数を扱えず、タスク関数で値を調べたり、使ったりはできません。
gulpタスクに引数は必要?
通常は必要ありません。そうでなければ、何年も前にタスクに引数を渡す機能が付けられていたはずです。gulpタスクはJavaScriptで書かれますので、変数のデフォルト値はコード内で指定できます。
NODE_ENVなどの環境変数の値も調べられます。たとえば、本番サーバーで環境変数がproductionなどに設定されているかをチェックできます。設定が分かれば、タスクが実行されたとき、JavaScriptのソースファイルが最小化されているかどうか判断できます。たとえば、次のようにします。
// is this a development build?
const devBuild = ((process.env.NODE_ENV || 'development').trim().toLowerCase() === 'development');
// gulp plugins
const
stripdebug = require('gulp-strip-debug'), // remove debugging code
uglify = require('gulp-uglify'); // minify
// build JavaScript
gulp.task('js', () => {
let jsbuild = gulp.src('src/js/*')
.pipe(some-plugin1())
.pipe(some-plugin2());
// production server tasks
if (!devBuild) {
jsbuild = jsbuild
.pipe(stripdebug())
.pipe(uglify());
}
return jsbuild.pipe(gulp.dest('build/js/'));
});
gulp jsタスクを走らせる前に、Linux/Macならexport NODE_ENV=productionで、Windowsならset NODE_ENV=productionで環境変数を設定できます。この設定で、JavaScriptファイルを最小化する前にconsole.logやdebugger文が削除されます。
最後に、タスクに少し違ったことをさせたい場合、新しいタスクを作れます。必要に応じて、タスクを結合して、順番に実行できます。たとえば、次のように書きます。
gulp.task('doSomething1', () => {
return gulp.src('src/*')
.pipe(some-plugin1())
.pipe(gulp.dest('build/'));
});
// run doSomething1 first
gulp.task('doSomething2', [doSomething1], () => {
// do something else
return gulp.src('src/*')
.pipe(some-plugin2())
.pipe(gulp.dest('build/'));
});
gulp doSomething1で、最初のタスクが実行されます。gulp doSomething2で、両方のタスクが順に実行されます。なぜならdoSomething1は、タスク名の次に書かれたオプションの配列で依存オブジェクトとして定義されているからです。
引数について本当に考える必要がある?
もっと良い別の方法があるなら、引数は使わないほうが良いです。引数--option1がgulpの次のバージョンで有効なコマンドラインオプションになって、好ましくない結果となることだってあり得ます。
そうは言ったものの、微妙な場合もあります。
1. パスワードとセキュリティ
IDやパスワードなどの資格情報をgulpfile.js内にハードコードするのは、通常、避けるべきです。次のタスクについて考えます。vinyl-ftpプラグインを使って、FTP経由でファイルをサーバーに展開するタスクです。
gulp.task('deploy', () => {
let
ftp = require('vinyl-ftp'),
conn = ftp.create({
host : 'mysite.com',
user : 'myuserid',
password : 'mypassword',
parallel : 5
}),
glob = [
'build/**/*'
],
src = {
base : 'build/',
buffer : false
},
remotePath = '/public_html/';
return gulp.src(glob, src)
.pipe(conn.newerOrDifferentSize(remotePath))
.pipe(conn.dest(remotePath));
});
たしかにFTPはあまり良いファイル展開方法ではありませんが、使っている開発者はまだ多く、ホストによっては、ほかに方法がないこともあります。
この方法には問題があります。
- FTPホスト、ユーザーID、パスワード、パスがファイルにハードコードされているので、公開GitHubにコードが保存されている場合、セキュリティの問題が生じる。誰もが、複製し、実行できてしまう
- どのデバイスからも、開発者の誰もが、いつでもgulp deployを実行できる。大規模なチームで、ファイルがいつ展開されたかを管理したい場合、望ましいことではない
- 資格情報が変更されたとき、デプロイメントタスクを引き続きうまく動かすには、マニュアルでgulpfile.jsを更新しなければならない
2. 異なったソース、ビルド、タスクの場所
gulpは典型的なWebサイトのタスク以外にも使えます。たとえば、フォルダーの消去、データベースの生成、ファイル転送などの一般的なタスクです。データベース名やフォルダー名などをハードコードすると、タスクの使い勝手が悪くなってしまいます。
3. 複雑なタスク
プラグインをいくつも使う複雑なタスクを想像してください。複数のサブタスクに分割するのが現実的でない場合、タスクを実行する前に、直接gulpfile.jsを編集せずにコンフィギュレーションのオプションを追加するのが難しくなります。
ほかにもいろいろな場合があるかもしれません。
gulp.jsタスクに引数を渡す方法
Node.jsのprocess.argvプロパティは、プロセス、スクリプト、コマンドライン引数をすべて含んだ配列を返します。たとえばgulp task1 --a 123--"my string"--cは次の配列を返します(値そのものはOSや設定によって異なります) 。
[
'/usr/bin/nodejs',
'/home/user/.node_modules_global/bin/gulp',
'task1',
'--a',
'123',
'--b',
'my string',
'--c'
]
この配列はgulpfile.jsで解析できます。次のコードはargという名前で、引数の値を格納したオブジェクトを生成します。
// fetch command line arguments
const arg = (argList => {
let arg = {}, a, opt, thisOpt, curOpt;
for (a = 0; a < argList.length; a++) {
thisOpt = argList[a].trim();
opt = thisOpt.replace(/^\-+/, '');
if (opt === thisOpt) {
// argument value
if (curOpt) arg[curOpt] = opt;
curOpt = null;
}
else {
// argument name
curOpt = opt;
arg[curOpt] = true;
}
}
return arg;
})(process.argv);
この関数は、配列process.argvを繰り返し調べ、 1つ以上のダッシュで始まる値に出会うとargオブジェクトに新しい名前付き変数を生成しtrueとします。ダッシュのつかない値に出会ったときは、直前の名前付き変数があるならば、その文字列に設定します。
gulp task1 --a 123--"my string"--cを実行するととargオブジェクトは次のように設定されます。
{
"a": "123",
"b": "my string",
"c": true
}
従って、必要に応じて値を調べたり、使ったりできます。
argがgulpfile.jsの先頭に設定されているとして、先ほどのFTPで展開するタスクを次のように書き換えて、値を渡せるようにします。
- ユーザーIDは--userあるいは--uの引数として
- パスワードは--passwordあるいは--pの引数として
gulp.task('deploy', () => {
let
ftp = require('vinyl-ftp'),
conn = ftp.create({
host : 'mysite.com',
user : arg.user || arg.u, // command line option
password : arg.password || arg.p, // command line option
parallel : 5
}),
glob = [
'build/**/*'
],
src = {
base : 'build/',
buffer : false
},
remotePath = '/public_html/';
return gulp.src(glob, src)
.pipe(conn.newerOrDifferentSize(remotePath))
.pipe(conn.dest(remotePath));
});
タスクを実行するときに適切なFTPの資格情報によって、はじめてファイルが展開されます。たとえば、次のようになります。
gulp deploy --u myuserid --p mypassword
最後に
ここまで説明してきたように、ほんの少し自分でコードを書くだけでgulpタスクにパラメーターを渡せます。タスクが引数を受け取る必要性はありませんが、役に立つ場合もあることを紹介しました。ツールキットに入れておく、絶対に良いテクニックです。
引数解析コードはNode.jsのコマンドライン・プロセスならどれにでも使えますが、gulpプロジェクト以外で必要になったときは、commanderモジュールがより詳しい機能を提供してくれます。
この記事が役に立てば幸いです。引数をgulpタスクに渡せるからといって、引数を渡さなければいけないという訳ではありません。
※本記事はTim Severienが査読を担当しています。最高のコンテンツに仕上げるために尽力してくれたサイトポイントの査読担当者のみなさんに感謝します。
(原文:How to Pass Command Line Parameters to Gulp Tasks)
[翻訳:関 宏也/編集:Livit]