Gulp4へ対応してみた

# Gulpの利用実態について
はじめに、現状のGulpの利用実態を説明すると、
フロントソース実装に伴う一部作業の自動実行化を行っていました。
今回新たに以下の内容をGulp経由で自動実行化するにあたりGulp4へ対応した内容へ変更しました。

```text:機能要件一覧
▼ 自動実行内容
scss → cssへの変換
TypeScript → JavaScript(es5)への変換
変換されたcss・JSの圧縮対応
handlebars → htmlへの変換
Frontnoteを利用したscssファイル内のコメントベースのドキュメント生成
```

## Gulp3 → Gulp4への変更について
Gulp4では主に以下の内容が変更となりました。
すでにGulp3で実装されているソースは以下の通り変更を実施する必要があります。
### 1. funciton定義について
Gulp4からは特定のパターンに該当する戻り値を設定しないとタスクの終了が把握できず、エラーを起こすようになりました。
特定のパターンとはタスク内で`stream`, `promise`, `event emitter`, `child process`, `observable`のいずれかに該当する形で戻り値が返るように実装を調整する必要があります。
詳細は[公式ドキュメントの内容](https://gulpjs.com/docs/en/getting-started/async-completion)を参考。
▼ 該当行
> Node libraries handle asynchronicity in a variety of ways. The most common pattern is error-first callbacks, but you might also encounter __streams, promises, event emitters, child processes, or observables.__ Gulp tasks normalize all these types of asynchronicity.

一般的な形として関数を呼び出して戻り値を必要としない場合のfuncitonの定義は、
コールバック実行用の引数を設定し、すべての処理が完了した時点でコールバックを実行すれば問題なく実行できます。
具体的な実行内容は以下の例を参考。

```js
// 削除タスク
function delTask(callback) {
    // 該当するファイルの削除を時刻
    del.sync("削除対象のファイルパス情報", { force: true })
    // 明示的な終了を告げるためのコールバックを実行
    callback();
}
// delタスクとしてdelTask()の内容をタスク登録
gulp.task('del', delTask);
```
また、TypeScript → JavaScriptへ変換された内容を指定場所へファイル出力処理までFunciton内で定義する場合は、
戻り値=即時関数として設定するだけでエラーなく実行ができます。
具体的な実装例は以下を参考

```js

function tsTask(_config) {
    // typescriptトランスコンパイル設定ファイルの読み込み
    const tsconfig = ts.createProject('./tsconfig.json');

    return ()=> {
        // tsファイルをes5の内容へ変換、指定先へ変換結果を出力
        return gulp.src("typescriptファイルパスの指定")
        .pipe(tsconfig()).js 
        .pipe(gulp.dest("出力先パスの設定"));
    };
    
}

gulp.task('ts', tsTask());
```

Gulp3実装当時から `戻り値 = stream` を定義している場合は特に修正する必要なくGulp4へ対応可能です。
### 2. Gulpタスク実行定義の変更
Gulp4からは直列(逐次)、並列で実行したいタスクを定義する必要があります。
`defaultタスク`として指定タスクを順番に実行したい場合は、
gulp.series("実行対象")の形式で定義することで "実行対象" で指定された内容が実行されます。
以下が実装例です。

```JS
// defaultタスクとして del → css → js → copy の順番に実行
gulp.task('default', gulp.series('del','css','js', 'copy'));
```

指定タスクを特に順番に関係なく並列(複数のタスクを同時に実行)で実行したい場合は、
gulp.parallel("実行対象")の形式で定義します。
以下が実装例です。

```JS
// defaultタスクとして css、js を同時に並行実行
gulp.task('default', gulp.parallel('css','js'));
```
また直列処理の一部として並列処理を設定することも可能です。(その逆も可能)

```JS
// defaultタスクとして del → 並列処理(css、js) → copy の順番に実行
// 並列処理(css、ｊｓ）は双方のタスクが終わり次第、次の登録タスクが実行されます。
gulp.task('default', gulp.series('del',gulp.parallel('css','js'), 'copy'));
```

基本的な `Gulp3 → Gulp4` への移行に伴う変更ポイントはこれくらいです。
## Gulp4移行時に学んだTips集
### 肥大化する gulpfile.js をタスク別に外部ファイル管理する
Gulpタスクの実装をする上で一番の悩みどころがタスクが増えるに連れて行数が肥大化する __gulpfile.js__ だと思われます。
かくいう私も今回Gulpタスク環境を整えるにあたり、以下の内容を満たすために最低でも5つのタスクを登録する必要があり、
タスク別の実装が進むに連れて肥大化する __gulpfile.js__ をどうにかできないものか悩みどころでした。

```text:機能要件一覧
scss → cssへの変換
TypeScript → JavaScript(es5)への変換
変換されたcss・JSの圧縮対応
handlebars → htmlへの変換
Frontnoteを利用したscssファイル内のコメントベースのドキュメント生成
```

今回解決策として[gulp-hub](https://github.com/frankwallis/gulp-hub)というプラグインを導入して解決しました。
__gulp-hub__ を導入することでそれぞれの実行タスクを外部ファイルとして定義を行い、タスク実行することが可能となります。
例えばTypeScript → JavaScriptへ変換する__tsタスク__を外部ファイル(ts.js)に定義し、
__gulpfile.js__内でgulp-hub経由でタスクの読み込み、defaultタスクとして読み込まれたタスクを実行するには以下のように実装します。
※ gulpfile.jsとts.jsは同じ親ディレクトリの配下の直下に存在する構成

```js:ts.js
const gulp = require('gulp');
const ts   = require('gulp-typescript');

function tsTask() {
    // typescriptトランスコンパイル設定ファイルの読み込み
    const tsconfig = ts.createProject('./tsconfig.json');

    return ()=> {
        return gulp.src("typescriptファイルパスの指定")
        .pipe(tsconfig()).js
        .pipe(gulp.dest("出力先パスの設定"));
    };
    
}

/**
 * tsタスク・Watchタスクの登録
 */
gulp.task('ts', tsTask(config.ts));
// typescriptファイルに変更があればtsタスクを実行する
gulp.watch("typescriptファイルパスの指定", gulp.task('ts'));
```

```js:gulpfile.js
// npm-plugins
const gulp    = require('gulp');
const gulpHub = require('gulp-hub');

/**
 * 外部ファイルとしてタスク別に定義された内容を読み込み
 * (今回例として挙げた内容はts.jsのみ)
 */
var tasks = new gulpHub([
    './del.js',
    './copy.js',
    './handlebars.js',
    './scss.js',
    './js.js',
    './ts.js',
    './images.js',
    './styledoc.js',
]);
// Gulpレジストリ領域に登録
gulp.registry(tasks);
// defaultタスクの登録
gulp.task('default', gulp.series('del',gulp.parallel(
        'handlebars',
        'scss',
        'spScss',
        'commonScss',
        'js',
        'spJs',
        'commonJs',
        'ts',
        'spTs',
        'images',
        'spImages',
        'commonImages'
        ), 'copy'));
```
###仮想環境下でgulp.watchが実行されない不具合
開発環境として用意された__仮想環境(CentOS)の共有ディスク設定されたディレクトリ配下に存在するファイル__をgulp.watchの対象として設定した場合、
watchタスクがうまく実行されない不具合に遭遇しました。
不具合の原因はGulp4からはwatchタスク内で実行するプラグインが__chokidar__を利用したものに変更となったことが原因でした。
不具合の解決方法としてはwatchタスク登録時に第二引数として__{ usePoolig: true }__ を設定することで解決しました。
ts.jsでの設定例は以下のとおりです。

```js:ts.js
const gulp = require('gulp');
const ts   = require('gulp-typescript');

function tsTask() {
    // typescriptトランスコンパイル設定ファイルの読み込み
    const tsconfig = ts.createProject('./tsconfig.json');

    return ()=> {
        return gulp.src("typescriptファイルパスの指定")
        .pipe(tsconfig()).js
        .pipe(gulp.dest("出力先パスの設定"));
    };

}

/**
 * tsタスク・Watchタスクの登録
 */
gulp.task('ts', tsTask(config.ts));
// typescriptファイルに変更があればtsタスクを実行する
gulp.watch("typescriptファイルパスの指定",{ usePoolig: true }, gulp.task('ts'));
```
gulp.watchの第二引数に設定されたオプションはそのままwatch内で実行される__chokidar__へのオプションとして適用されるため
問題なく動作するようになったようです。
> [参考: chokidar](https://github.com/paulmillr/chokidar)
> ▼ chokidar 設定可能オプション
> 
> ```json
>  // Full list of options. See below for descriptions. (do not use this example)
>  chokidar.watch('file', {
>   persistent: true,
> 
>   ignored: '*.txt',
>   ignoreInitial: false,
>   followSymlinks: true,
>   cwd: '.',
>   disableGlobbing: false,
> 
>   usePolling: true,
>   interval: 100,
>   binaryInterval: 300,
>   alwaysStat: false,
>   depth: 99,
>   awaitWriteFinish: {
>     stabilityThreshold: 2000,
>     pollInterval: 100
>   }
> ```

# 感想
Gulp4への移行が完了したことで実感できたメリットはとにかくwatchタスク中のCPUリソースの消費が抑えられた点です。
Gulp3時代は複数のwatchタスクが実行されると常時監視タスクが可動しているためCPU使用率が高い状態が続いていましたが、
Gulp4移行後は特にCPU使用率が上がることもなく可動しています！！
Gulp3時代から特に変更していないタスクでもサクサクタスク実行→完了をしてくれるようになったような...?