Ususamaのアップデートと、Gulp4.x系の記述方法について

久々にUsusamaをアップデートしました。 今回は結構大規模な改修になったため、一気にナンバリングを5.x.x-4.3.1系から6.0.0-4.3.1に上げました。 変更点については以下を参照。 トピックはいくつかありますが、大きな変更点としてはGulpタスクの文法を変えたことです。 その際に躓いた点、また検索はしたもののあまり情報がヒットせずもやもやした点をまとめたいと思います。

目次

  • 前置き
  • 本題
    1. 関数式の記述が可能か
    2. exportsしたタスクをどのようにインポートし、実行するか
    3. フラグによってタスクを増減させたい
    4. 複数のタスクが1ファイルにある場合、どうexportsするか

前置き

過去の話になりますが、一応Gulpのバージョンを長らく使われていた3.9.1から4.x系に移行したのが、Ususamaでは3.7.0-4.1.3になります。 このときは今から思えばとりあえず4.x系にアップデートしただけという感じで、
  • package.jsonでのバージョン指定はnextという表記方法
  • まだgulpfile.js1つに全てのタスクを記載
  • タスクの定義はgulp.task()構文のまま
という形でした。 これを基本的に1タスク1ファイルという形で分離させたのが、3.11.0-4.1.3でした。 ただし、このときもまだタスクの定義はgulp.task()構文のままでした。

本題

しかし、Gulp4.x系は上述のgulp.task()構文は
This API isn’t the recommended pattern anymore task() ・ gulp.js

ということで、推奨されなくなりました。代わりに、関数宣言とexportsが推奨となりました。独自構文からECMAScriptに寄った形になったのだと思います。
/* 今まで */
gulp.task('TASKNAME', function() {
    //処理
});
//アロー関数
gulp.task('TASKNAME', () => {
    //処理
});

/* 4.x系 */
function TASKNAME() { //関数宣言した時点では`gulp`コマンドで実行不可能
    //処理
});
exports.TASKNAME = TASKNAME;
以下、node.jsのバージョンは10.16.3、Gulpは4.0.2の環境で確認しました。

1. 関数式の記述が可能か

ここでまず気になったのは、「関数宣言ではなく関数式は使えるのか?」ということ。
//関数式でも可
const TASKNAME = () => {
    //処理
});

exports.TASKNAME = TASKNAME;
試してみたところできました。

2. exportsしたタスクをどのようにインポートし、実行するか

1.でexportsはできました。次は、これをどうやってインポートするか。
task1.js
//exports
const TASKNAME = () => {
    //処理
});

exports.TASKNAME = TASKNAME;
gulpfile.js
const TASK1 = require('./task1'); //`require`でインポート

exports.TASK1 = gulp.series(TASK1); //`gulp TASK1`で実行可能
requireでJSファイルを読み込んで、exportsに渡す、と。受け取り側はrequireですが、ECMAScriptの標準的な構文に近い形で行けました。

3. フラグによってタスクを増減させたい

Ususamaでは、YAMLファイルの設定を読み込んでタスクを変化させたいものがいくつかありました。 例えば、「dist/下にあるHTMLファイルを探索して、人間用のサイトマップを生成する」というのもその一つです。フラグがtrueならばサイトマップ生成をタスクに込め、falseならばサイトマップは生成しない、という具合です。
//タスクを読み込み
const ejs = require('./gulp/tasks/ejs');
const imagemin = require('./gulp/tasks/imagemin');
const js = require('./gulp/tasks/js');
const scss = require('./gulp/tasks/scss');
const sitemap = require('./gulp/plugins/sitemap');

//タスクの配列
let taskArray = [scss, js, imagemin];

//ejsだけ別個に宣言
let taskEjs = [ejs];

if(plugins.sitemap) { //`plugins`オブジェクトの中の`sitemap`をチェック
    taskEjs.push(sitemap); //`true`ならばサイトマップ生成のタスクを`taskEjs`に追加
}

//`taskArray`の中身は並列
//ただしサイトマップ生成は`dist/`下のHTMLファイルを見るため、EJSコンパイルが完了してからにしたい
//そのため、`taskEjs`は`series`にする
const taskBuild = gulp.parallel(taskArray, gulp.series(taskEjs));
exports.build = taskBuild;
イメージとしてはこのような形。 gulpfile.jsに関してはこれで良かったのですが、一点元のタスクファイル側で変更が必要でした。
const TASKNAME = () => {
    //処理
});

module.exports = TASKNAME; //左辺を`exports.TASKNAME`から`module.exports`に変更
左辺がexports.TASKNAMEだと[Object Object]になってしまって上手く動かなかったので、module.exportsに変更しました。 この形式ならば、上述のタスク(というより関数)の配列をそのままseriesparallelに突っ込んで動かすことができました。 seriesparallelに突っ込むのがただの「JavaScriptの関数の配列」ならば、配列操作のpushunshift等が使えるだろう、と推測できたので試してみたとこと、思った通り実現できた、という次第です。

4. 複数のタスクが1ファイルにある場合、どうexportsするか

今度は、複数のタスクが1ファイルに宣言されているケースについて。 例えば、JavaScriptでconcatの後にuglifyする、としています。
//js圧縮&結合&リネーム
const jsConcat = () => {
    return gulp.src(['PATH/TO/JSLIBRARY1', 'PATH/TO/JSLIBRARY2'])
        .pipe(_.concat('lib.js'))
        .pipe(gulp.dest(`${PATH/TO/SRC/JS}/concat/`));
};
const jsBuild = () => {
    return gulp.src('PATH/TO/SRC/JS/*.js')
        .pipe(_.uglify({output: {comments: 'some'}}))
        .pipe(_.rename((path) => {
            path.dirname = 'PATH/TO/DIST/JS/'
            path.basename += '.min'
            path.extname = '.js'
        }))
        .pipe(gulp.dest('./'));
};

module.exports = gulp.series(jsConcat, jsBuild);
4.x系で導入されたseriesparallelを使えば、タスクを塊にしてexportsはできます。が、それぞれのタスクを単体で実行したい場合はどうするか。
js.js
//js圧縮&結合&リネーム
const js = {
    jsConcat: () => {
        return gulp.src(['PATH/TO/JSLIBRARY1', 'PATH/TO/JSLIBRARY2'])
            .pipe(_.concat('lib.js'))
            .pipe(gulp.dest(`${PATH/TO/SRC/JS}/concat/`));
    },
    jsBuild: () => {
        return gulp.src('PATH/TO/SRC/JS/*.js')
            .pipe(_.uglify({output: {comments: 'some'}}))
            .pipe(_.rename((path) => {
                path.dirname = 'PATH/TO/DIST/JS/'
                path.basename += '.min'
                path.extname = '.js'
            }))
            .pipe(gulp.dest('./'));
    }
};

module.exports = js;
gulpfile.js
const jsTask = require('./js');
const js     = gulp.series(jsTask.jsConcat, jsTask.jsBuild);

exports.jsconcat = gulp.series(jsTask.jsConcat); //`gulp jsconcat`でconcatだけ走る
exports.jsbuild = gulp.series(jsTask.jsBuild); //`gulp jsbuild`でjsbuildだけ走る
exports.js = js; //concat→jsbuildの順番で走る
モジュールでexportsできるならば、この書き方でできるはず。実際できました。
以上4点、私のケースでは対処できたサンプルのまとめでした。

参考

全般

タスクの関数の書き方→アロー関数でも良い

seriesparallelに渡す引数→普通の配列で良い

この記事を書いた人

アルム=バンド

フロントエンド・バックエンド・サーバエンジニア。LAMPやNodeからWP、Gulpを使ってejs,Scss,JSのコーディングまで一通り。たまにRasPiで遊んだり、趣味で開発したり。