【初心者】Gulp環境構築メモ【備忘録】

# はじめに
投稿時点で、筆者は知識ゼロの状態から勉強を初めて2ヶ月程度の実力です。
そのため、理解不足や説明不足、誤った内容や呼び方をしている可能性があります。
万が一参考にする場合は上記の点を考慮した上でご一読ください。
説明文の内容はChatGPT先生からの引用も多いです。

Gulp記述については下記にまとめました。。
[【初心者】Gulp記述メモ【備忘録】](https://qiita.com/miyaayim_study/items/646b22559d822aafcb5a)

完成品の[Git Hubはこちら](https://github.com/miyaayim-study/template_project_react/tree/34ba655492216aaf1e8e1cf5b75ce5730dbfc146/project_react)になります。

# 目標
Gulpで下記を使えるようにする
- Sass
- JSのバンドル（React使用想定）
- Browsersync
- 画像圧縮


# 筆者の環境
Windows10使用。
Visual Studio Code使用。
Nodeのバージョンはｖ18.15.0

# 実装手順

## 1.Gulp導入
下記の記事を参考に進めていきます。
というか丸パクリですので、この当たりは参考元の記事を読んだほうが確実にわかりやすいです。（大変お世話になりました。）
[絶対つまずかないGulp 4入門 インストールとSassを使うまでの手順](https://ics.media/entry/3290/)

Node.jsの[公式サイト](https://nodejs.org/ja)からNode.jsのインストーラーをダウンロードしてインストールします。
「推奨版」と「最新版」の2種類あるが、「推奨版」をダウンロードすること。
（理由は調べてません。）

コマンドプロントで下記コマンドを実行すると、インストールしたNodeのバージョンがわかります。
私は`v18.15.0`でした。
```:Nodeバージョン確認
node -v
```

package.jsonファイルの作成
>ウェブサイトのファイル一式が保存されるフォルダー（以下、プロジェクトフォルダーと呼びます）を任意の場所に作成し、コマンドラインでその場所に移動します。今回は、myprojectというフォルダーを使う前提で解説します。あらかじめフォルダーを作成しておき、次のコマンドで移動します。

とあり、参考元ではWindowsのコマンドプロンプトで行っていましたが、私はVisual Studio Code（以下、VSC）上で行いました。（楽そうだったから）
ここではプロジェクトフォルダー名を「project」とします。

VSCを起動して作成したプロジェクトフォルダーをドラッグ&ドロップで放り込んで、プロジェクトフォルダーが表示されたらVSCの「表示」→「ターミナル」を選択してVSC上でターミナルを使える状態にする。
VSC内で使用するターミナルはコマンドプロントにしています。（これしか使ったことがないので）
初期では確か違うのになってるはずなので注意。
ターミナルを出したら下記コマンドを入力して実行、`package.json`ファイルがプロジェクトフォルダー内に生成されます。
```:package.jsonファイル作成
npm init -y
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3310435/f9a82a29-eccf-da81-8c98-fa59c86c7245.png)

そのままVSCのターミナルにて下記コマンドを実行。
```:Gulp本体をローカル環境にインストール
npm install -D gulp
```
Gulp本体がローカル環境にインストールされました。
`–D`は`––save-dev`を省略したものだそうです。
また、`npm install`も`npm i`と省略することができます。
ですので全て省略形で記述すると`npm i -D gulp`となります。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3310435/6ed4860d-2a57-8ab0-90eb-1e729ba65bfc.png)

ここで一つ問題点が、
私のPCにはカスペルスキーセキュリティというウイルス対策ソフトを入れているのですが、この時に生成されたファイルの一部が危険と認識されたのか隔離されてしまうんですね。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3310435/d45c7ff3-60c2-1e3a-970f-b62bbb4ae923.png)
私の場合は隔離画面から復元することでひとまず良しとしました。（大丈夫…だよね？）


インストールする際のコマンド内容についてですが、
参考元には、
>記事によってはnpm install -g gulp-cliとグローバルにGulpを導入する手順を解説しているところもあります。現在ではそれは少数派です。Gulpはグローバルではなく、ローカルだけにインストールするのがフロントエンド界隈では一般的です。環境差異で動作しないなどトラブルのもとになるので、グローバルにインストールしないほうが望ましいでしょう。

とあり、私の大先生でもあるChatGPTさんにも質問してみました。
以下、ChatGPTからの引用
> ローカル環境でのGulpのインストールは、プロジェクトごとに必要なバージョンを管理することができ、プロジェクトごとに異なるバージョンを使用できるため、依存関係の管理が容易です。また、プロジェクト固有の設定を保持することができるため、他のプロジェクトとの競合を防ぐことができます。ただし、プロジェクトごとにインストールする必要があるため、複数のプロジェクトで同じバージョンのGulpを使う場合には、インストールの手間が必要になります。
一方、グローバル環境でのGulpのインストールは、複数のプロジェクトで同じバージョンのGulpを使用できるため、インストールの手間を省くことができます。ただし、複数のプロジェクトで異なるバージョンのGulpを使用する場合には、切り替えが必要になり、バージョンの競合が生じる可能性があるため、依存関係の管理が複雑になります。
総じて、一般的にはプロジェクトごとにローカル環境でGulpをインストールすることが推奨されます。依存関係の管理が容易で、プロジェクトごとに異なる設定を保持することができるため、プロジェクトの保守性を高めることができます。ただし、特定の状況に応じて、グローバル環境でのインストールを選択することもあります。

ふむふむなるほど？（脳内メモリ使用量80%）
とりあえずローカル環境が良いということですね！
オレ、オボエタ。

## 2.Sass導入
とりあえずhtmlやcssやらの必要なファイルを作成しました、下記のようなディレクトリ構成としました。
SassはFLOCSS設計を想定しています。（まだ始めたばかりでディレクトリ構成あってるかわからないけど）
そしてプロジェクトフォルダー直下に`gulpfile.js`というファイルを作成します。（中身は空）
```:ディレクトリ構成
project/
　├ dist/
　│　├ images/（後ほどGulpコマンドで生成）
　│　│  └ test.jpeg（後ほどGulpコマンドで生成）
　│　├ main.js（後ほどGulpコマンドで生成）
　│　└ style.css（後ほどGulpコマンドで生成）
　├ node_modules/
　├ src/
　│　├ images/
　│　│  └ test.jpeg
　│　├ jsx/
　│　│  └ components/
　│　│  │  └ App.jsx
　│　│  └ index.jsx
　│　└ sass
　│　│　└ foundation/
　│　│　└ global/
　│　│　└ layout/
　│　│　└ object/
　│　│　└ style.scss
　│　└ index.html
　├ package-lock.json
　├ package.json
　└ gulpfile.js
```

下記コマンドにてSassモジュールを2つインストールします。 
```:
npm install -D sass gulp-sass
```

`gulpfile.js`にコードを記述していきます。

```gulpfile.js
const { src, dest } = require("gulp"); // gulpモジュールを読み込む

// Sass関連---------------------------------------------
const sass = require("gulp-sass")(require("sass")); // gulp-sassモジュールを読み込み、Sassコンパイラを設定する

const compileSass = (done) => { // (done)はラストのdone()でタスク完了の合図を受け取るためのもの
  src("src/sass/style.scss") // コンパイルするSassファイルを指定
    .pipe(
      sass({
        outputStyle: "expanded" // 出力されるCSSの書式を"expanded"（展開形式）に設定する
      })
    )
    .pipe(dest("./dist")) // 出力先ディレクトリを指定
    done(); //done()でタスク完了の信号を出す
};

exports.sassTest = compileSass;
```


ここまでできたら、試しに下記コマンドを実行してコンパイルされるか試してみます。

```:Sassコンパイル
npx gulp sassTest
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3310435/d5fffb2c-d33d-0067-31c7-aebfee74a910.png)
無事、distフォルダ内に`style.css`が作成されました。

※補足
`done`についてですが、こちらはコールバック関数でGulpタスクが完了したことを通知するためのものです。
試しに`done`抜きのコードで実行してみます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3310435/60f07d07-7635-065f-d3bc-bf1cf69748e4.png)
同じように`style.css`ファイルは作成されましたが、先程にはなかったメッセージが表示されています。
こちらのメッセージ内容をChatGPTさんに説明してもらいました。
> このメッセージは、sassTestタスクが完了しなかったため、Gulpがエラーを検出したことを示しています。また、タスクが完了するための非同期の完了信号が送信されていない可能性があることも示唆しています。
この問題の一般的な原因は、タスク内で非同期処理を実行しているが、処理が完了した後にタスクが完了するようにGulpに通知していないことです。タスクが完了するためには、非同期処理が完了したことを示すシグナルを送信する必要があります。
この場合、sassTestタスクの中で何が起こっているかについては詳細が不明ですが、おそらく非同期の処理が含まれている可能性があります。この場合、Gulpがタスクの完了を認識するために、コールバック関数、Promise、またはasync/await構文を使用して、非同期処理が完了したことを示す必要があります。

ということで、Gulpタスクは全て非同期処理なので必ずreturn文かコールバック関数でGulpタスク完了の通知を送りましょう。


さて、参考元ではこのあとにwatch機能の説明をしていますが、これは後ほどBrowsersyncでまとめてやるので必要ありません。
ただし勉強のためにwatchの監視タスクも試したのでその内容は下記で説明します。
Browsersync無しで監視タスクをする場合下記の記述になります。

```gulpfile.js
const { src, dest, watch } = require("gulp"); // gulpモジュールを読み込む

// Sass関連---------------------------------------------
const sass = require("gulp-sass")(require("sass")); // gulp-sassモジュールを読み込み、Sassコンパイラを設定する

const sassWatch = (done) => { // style.scssの監視タスクを作成する

  // 監視設定
  watch("./src/sass/**/*.scss", (done) => { // 監視するファイルを指定

    // 監視対象の更新があった場合の処理
    src("src/sass/style.scss") // コンパイルするSassファイルを指定
    .pipe(
      sass({
        outputStyle: "expanded" // 出力されるCSSの書式を"expanded"（展開形式）に設定する
      })
    )
    .pipe(dest("./dist")) // 出力先ディレクトリを指定
    done();

  });
  
  done(); //done()でタスク完了の信号を出す
};

exports.sassTest = sassWatch;
```

では実行してみましょう。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3310435/b2e2517b-2cea-b8c0-ad39-1409d15a347e.png)
監視が始まり、監視対象のファイルが更新されたら自動でコンパイルされるようになりました。

下記のように記述しても同じことができます。

```gulpfile.js
const { src, dest, watch } = require("gulp"); // gulpモジュールを読み込む

// Sass関連---------------------------------------------
const sass = require("gulp-sass")(require("sass")); // gulp-sassモジュールを読み込み、Sassコンパイラを設定する

const compileSass = (done) => { // (done)はラストのdone()でタスク完了の合図を受け取るためのもの
  src("src/sass/style.scss") // コンパイルするSassファイルを指定
    .pipe(
      sass({
        outputStyle: "expanded" // 出力されるCSSの書式を"expanded"（展開形式）に設定する
      })
    )
    .pipe(dest("./dist")) // 出力先ディレクトリを指定
    done(); //done()でタスク完了の信号を出す
};

const sassWatch = (done) => { // style.scssの監視タスクを作成する

  // 監視設定 & 監視対象の更新があった場合の処理
  watch("./src/sass/**/*.scss", compileSass); // 監視するファイルを指定 ＆ sassコンパイル
  done(); //done()でタスク完了の信号を出す
};

exports.sassTest = sassWatch;
```

役割を分けて記述した感じですね。
何を監視して何を実行するかという仕事量が増えるてくる場合は、このように役割分担したほうが後々効果が出てきます。

ここで少し脱線しますが、試しにコールバック関数部分（done部分）を外してみたいと思います。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3310435/bfbbcc17-5db2-096c-8f13-8ce89b399b1b.png)
こうすると、監視対象ファイルが更新された際にコンパイル作業に入りますが、終了の合図がないためコンパイル後に監視タスクに戻ることができません。
このようになることを防ぐためにコールバック関数、もしくはreturn文が必要なのですね。（身をもって知りました、めちゃくちゃ沼ってた）










## 3.React導入 ～　webpackとBabelを添えて　～
続いての参考元のメインはこちらです。
[最新版で学ぶwebpack 5入門 Babel 7でES2023環境の構築 (React, Vue, Three.js, jQueryのサンプル付き)](https://ics.media/entry/16028/)
[Gulp4 + Webpackの超オススメ設定を公開【爆速コーディング環境構築】](https://flex-box.net/gulp-webpack/)

こちらも同時に参考にさせて頂きました。
[Create React Appを使わずにWebpackとBabelでReactの開発環境を構築する方法](https://www.youtube.com/watch?v=Jjl8XsxSm1A)



ちょっと疲れてきたので細かい説明省きながら説明していきます。（元気が出たら書き直すかも）

webpack関連モジュールをインストールします。
```:webpack関連モジュールをインストール
npm install -D webpack webpack-cli
```


`webpack-cli`についてですが、[こちらの記事](https://qiita.com/tonkotsuboy_com/items/2d4f3862e6d05dc0bea1)によると下記のようです。
> webpackをCLIで使う場合はwebpack-cliが必要ですが、Gulp経由で使用する場合は不要です。

とのこと、今回は一つずつ動作確認していきたいので`webpack-cli`も入れています。

次はbabel関連モジュールをインストールします。
```:babel関連モジュールをインストール
npm install -D babel-loader @babel/core @babel/preset-env
```

更にReact関連モジュールをインストールします。
```:React関連モジュールをインストール
npm install -D @babel/preset-react
```

最後に実行用の`React`と`React-DOM`をインストールします。
```:ReactとReact-DOMをインストール
npm install react react-dom
```

色々入れた結果、`package.json`内はこうなりました。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3310435/c4b80854-2a32-6463-22b1-e8ba0730d2da.png)

プロジェクトフォルダー直下に`webpack.config.js`というファイルを作成して、コードを記述していきます。

```webpack.config.js
module.exports = {
  mode: "development", // 開発モードで動作するように設定（ソースマップ有効でJSファイルが出力される、本番環境では"production"に設定すると最適化された状態で出力される）
  entry: `./src/jsx/index.jsx`, // エントリーポイント（メインとなる処理を行うJavaScriptファイル）の指定
  module: {
    rules: [ // モジュールに対するルールを定義する
      {
        test: /\.(js|jsx|ts|tsx)$/, // test = loaderを使う特定の拡張子を指定、今回は複数の拡張子に対応できるようにしてみた
        use: [ // use = testで指定した拡張子にloaderを使いwebpackが読み込めるようにする
          {
            loader: "babel-loader", // Babelを使ってJSXなどの新JS文法を旧JS文法に変換する

            // babel-loader のオプションを指定する
            options: {
              presets: [
                "@babel/preset-env",// ES5に変換
                "@babel/react" // ReactのJSXを解釈
              ]
            }
          }
        ]
      }
    ]
  },
  target: ["web", "es5"], // ES5(IE11等)向けの指定（webpack 5以上で必要）
  output: { // ファイルの出力設定
    path: `${__dirname}/dist`, //  出力ファイルのディレクトリ名
    filename: "main.js", // 出力ファイル名
  },
  resolve: { // モジュールを解決するためのオプション
    extensions: [".js", ".jsx", ".ts", ".tsx"]  // importやrequireで指定する際に、拡張子を省略できるようにする
  },
};
```


エントリポイントを指定しなかった場合はどこを見に行くのだろう？
私はエントリポイントを指定しないと上手くいかなかったのでエントリポイントを指定しました。
ディレクトリ名を指定する際に`__dirname`をしてるところは理由を調べても自分の頭では理解できませんでした…。（相対パスを絶対パスにするらしいがここで行うメリットは…？）

`resolve`内の`extensions`は拡張子という意味。
ここでは、例:`import ～ from "app/src/sample.js"`のように参照する際に、拡張子抜きで記述しても認識できるようにするもの。
左側から参照していく。
これを入れないと、jsxファイルを見つけにいくことができなかった。
理由は下記のChatGPTさんから引用
> Webpackはデフォルトで、ファイル名の拡張子が指定されていない場合には ".js" を探しに行きます。例えば、import App from "./App" というコードがあった場合には、Webpackは "./App.js" を探しに行きます。しかし、Reactでは、JSXファイルを使うことが一般的です。JSXファイルの拡張子は ".jsx" であり、デフォルトではWebpackは ".jsx" ファイルを探しに行くことはありません。
そのため、拡張子を指定することで、Webpackがファイルを正しく探し出し、エラーを回避することができるようになります。

では試しに実行してみましょうか。
`package.json`ファイルに実行コマンドを追加します。今回は`build`という名前でwebpackを実行するように下記を追加します。
```package.json
"build": "webpack"
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3310435/c8c5eee7-38dc-a7a2-4744-f4c8394fea1d.png)
webpackを実行してみましょう
```:webpackを実行
npm run build
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3310435/3b136d21-dcdd-73cd-23af-8ed47fbbff94.png)
distフォルダ内に`main.js`が生成されました、成功です。

`index.js`等の中身の記述が気になる方は、GitHubに完成データを入れておくのでそちらからご参照ください。（忘れてたらごめんなさい）
（気力あったらindex.jsxの中身とかの説明も記事にします。）







### GulpでWebpackを動かす
では、さきほどのをGulpで行えるようにしましょう。
今回参考したのは主にこちらです
[Gulpで始めるwebpack 4入門](https://qiita.com/tonkotsuboy_com/items/2d4f3862e6d05dc0bea1)
[Gulp4 + Webpackの超オススメ設定を公開【爆速コーディング環境構築】](https://flex-box.net/gulp-webpack/)
[gulpとwebpackでJSのコンパイル環境を構築する](https://hakoirioyaji.com/blog/gulp-webpack-javascript/)

webpackをgulpで使用する為のプラグインを入れます。
```:webpackをgulpで使用する為のプラグインを入れる
npm install -D webpack-stream
```
次に`gulpfaile.js`の中身にタスクを追加します。

下記を追加します。
```gulpfaile.js
const webpack = require("webpack"); // webpackのJavaScript APIを使用するためのライブラリ
const webpackStream = require('webpack-stream'); // webpackをgulpで使用するためのプラグイン
const webpackConfig = require("./webpack.config"); // webpackの設定ファイルの読み込み（さきほど作成したwebpack.config.jsのコンフィグ情報を読み込む）

const bundleWebpack = (done) => { // "webpack"というgulpタスクを定義、 (done)はラストのdone()でタスク完了の合図を受け取るためのもの
  webpackStream(webpackConfig, webpack) // webpackStreamを使用して、webpackを実行します。webpackConfigは設定ファイル、webpackはwebpackの実行ファイルを指定します。
    .pipe(dest("dist")); // 出力先ディレクトリを指定して、バンドルしたJavaScriptファイルを出力
    done(); //done()でタスク完了の信号を出す
};

exports.bundleTest = bundleWebpack;
```

`webpack.config`で、出力先の指定をしているのでGulp側でWebpackを実行するならタスク内に`.pipe(dest())`は不要では？と思ったのですが、webpack-stream使用してWebpackを実行する際は、`webpack.config`側の出力先ディレクトリ指定は無効になり、出力先は`.pipe(dest())`指定の場所になるそうです、実際に試したらそうでした。
以下、ChatGPTの説明
>Webpackでoutput設定を行っている場合、指定された出力先にバンドルされたJavaScriptファイルが生成されます。そのため、本来はgulp.dest()による出力処理は不要です。
ただし、gulpタスクでWebpackを実行する場合、webpack-streamプラグインを使用することでWebpackをgulpで実行することができますが、この場合、Webpackのoutput設定を直接参照するわけではなく、webpack-streamが自動的に出力先を決定するため、gulp.dest()で出力先を指定する必要があります。
つまり、Webpackのoutput設定とgulp.dest()は、両方とも同じ出力先を指定する必要があります。Webpackのoutput設定で出力先を指定する場合は、gulp.dest()での出力処理は不要になります。一方、gulp.dest()で出力先を指定する場合は、Webpackのoutput設定も出力先を同じにする必要があります。

上記には`gulp.dest()`での出力処理は不要とあるが、実際やってみると設定しないと出力されませんでした。

まとめた結果の現在の`gulpfaile.js`の中身はこちらです。
```gulpfaile.js
const { src, dest } = require("gulp"); // gulpモジュールを読み込む

// Sass関連---------------------------------------------
const sass = require("gulp-sass")(require("sass")); // gulp-sassモジュールを読み込み、Sassコンパイラを設定する

const compileSass = (done) => { // (done)はラストのdone()でタスク完了の合図を受け取るためのもの
  src("src/sass/style.scss") // コンパイルするSassファイルを指定
    .pipe(
      sass({
        outputStyle: "expanded" // 出力されるCSSの書式を"expanded"（展開形式）に設定する
      })
    )
    .pipe(dest("./dist")) // 出力先ディレクトリを指定
    done(); //done()でタスク完了の信号を出す
};

exports.sassTest = compileSass;

// webpack関連---------------------------------------------
const webpack = require("webpack"); // webpackのJavaScript APIを使用するためのライブラリ
const webpackStream = require('webpack-stream'); // webpackをgulpで使用するためのプラグイン
const webpackConfig = require("./webpack.config"); // webpackの設定ファイルの読み込み（さきほど作成したwebpack.config.jsのコンフィグ情報を読み込む）

const bundleWebpack = (done) => { // "webpack"というgulpタスクを定義、 (done)はラストのdone()でタスク完了の合図を受け取るためのもの
  webpackStream(webpackConfig, webpack) // webpackStreamを使用して、webpackを実行します。webpackConfigは設定ファイル、webpackはwebpackの実行ファイルを指定します。
    .pipe(dest("dist")); // 出力先ディレクトリを指定して、バンドルしたJavaScriptファイルを出力
    done(); //done()でタスク完了の信号を出す
};

exports.bundleTest = bundleWebpack;
```




では下記コマンドを入力してgulp経由でwebpackを実行してみましょう。

```:gulp経由でwebpackを実行
npx gulp bundleTest       
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3310435/d62c1cb9-fbfc-dd4e-0a23-6ea08a52bf43.png)

webpack単体で実行したときと同じように、`dist`フォルダ内に`main.js`ファイルが作成されました。
しかし何か気になるメッセージが…ChatGPT先生に聞いてみましょう！
>このログはエラーが発生していないことを示しています。
npx gulp bundleTest コマンドが正常に実行され、webpackがバンドルされた結果を main.js というファイル名で生成したことが示されています。
ただし、ログの中で [BABEL] Note: The code generator has deoptimised the styling of F:\webcoding\template\project\node_modules\react-dom\cjs\react-dom.development.js as it exceeds the max of 500KB. という警告が表示されています。これは、Babelが最適化されたコードを生成する際に、 react-dom.development.js ファイルのサイズが500KBを超えているため、最適化できなかったことを示しています。
この警告自体は問題ありませんが、Reactアプリケーションを実行する際にパフォーマンス上の問題が発生する可能性があることを示唆しています。可能であれば、 react-dom パッケージの最新バージョンを使用することをお勧めします。また、webpackの設定を調整して、react-dom.development.jsファイルを除外することもできます。

ふむ…react-domパッケージは最新のはずですので、出力形式を開発モードにしているのが原因なのかな？
ということで、`webpack.config.js`の`mode: "development"`部分を`mode: "production"`に変更し、本番環境の出力としてみました。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3310435/8507fcd8-7bc4-1c6f-3971-4cb3c682575e.png)
ダメっぽいメッセージは出ていなさそうですね。念のためChatGPT先生に確認してもらったところ、問題ないとのことです。
環境に合わせてmodeを使い分けましょう！
これでgulpとwebpackの紐づけが完了です。

ちなみに今回はWebpackの設定を`webpack.config.js`側で行っていますが、`webpack.config.js`を使用せずにGulp側のみでも設定可能なはず…多分…。





## 4.Browsersync導入

では次に、
- `scss`ファイルが更新されたら自動でsassコンパイルを実行して`style.css`を更新。
- `jsx`ファイルが更新されたら自動でバンドルを実行して`main.js`を更新。
- `index.html`ファイル、`style.css`ファイル、`main.js`ファイル、が更新されたら自動でブラウザリロード。（Browsersync）
こちらを行いたいと思います。

Browsersync導入に関しては下記をメインで参考にします。
[ブラウザ確認が一瞬！Browsersync入門（＋Gulpの利用方法）](https://ics.media/entry/3405/)

他にも参考にしたところはこちら。
[【gulp】browser-syncによる自動リロード（watch()の分離、defaultでSassと同時に実行）](https://www.i-ryo.com/entry/2020/04/08/074158)

まずは下記を入力してBrowsersyncをインストールします。
```:Browsersyncをインストール
npm install -D browser-sync
```

では少し脱線しますが、試しにgulpで`index.html`ファイルが更新されたらブラウザリロードされるようにしてみたいと思います。
最終的な答えだけ知りたい人は飛ばして問題ありません。
`gulpfile.js`に下記を追加します。
```gulpfile.js
const { src, dest, watch } = require("gulp"); // gulpモジュールを読み込む　（← watchを追加しました）

// Browsersync関連---------------------------------------------
const browserSync = require("browser-sync"); // browser-syncのプラグインの読み込み

const watchFiles = (done) => { // "watchFiles"というgulpタスクを定義、 (done)はラストのdone()でタスク完了の合図を受け取るためのもの

  browserSync({ // BrowserSyncライブラリを初期化するメソッドらしい
      server : {
          baseDir : './', // ルートとなるディレクトリを指定
          index : './src/index.html', // 読み込むHTMLファイル
      },
  });

  // 監視設定
  watch('./src/*.html', (done) => { // srcフォルダ直下のhtmlファイルを監視

    // 監視対象の更新があった場合の処理
    browserSync.reload(); // ファイルに変更があれば同期しているブラウザをリロード
    done() //done()でタスク完了の信号を出す

  });

  done(); //done()でタスク完了の信号を出す
};

exports.browserSyncTest = watchFiles;
```
`const { src, dest, watch } = require("gulp");`のように、watchを追加するのをお忘れなく。
では試しに実行してみましょう。
```:gulpでBrowserSync実行
npx gulp browserSyncTest
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3310435/3e45078d-1303-c283-f0ce-aab81d096854.png)
自動的にブラウザが立ち上がったら試しに`index.html`ファイルの中身を変更して更新しました。
そうすると自動でブラウザリロードしてくれるのを確認できました。
成功のようです。

※補足
- server内のindexですが、私の場合は、ワイルドカード（*)を使用しての記述だと、上手くいきませんでした。自動で開いたブラウザ画面に「Cannot GET /」と表示される感じです。
- `browserSync.init()`関数も非同期なので`done()`関数でタスク完了の合図を出す必要がある。
- `browserSync()`と`browserSync.init()`は同じらしい。
> browserSync()はbrowserSync.init()のエイリアスです。つまり、browserSync()を呼び出すことは、暗黙的にbrowserSync.init()を呼び出すことになります。
したがって、どちらの方法でも、BrowserSyncが初期化されます。ただし、`browserSync()`を使用する場合、オプションを直接渡すことができるため、より簡潔に記述することができます。
したがって、どちらを使用するかは、個人の好みやチームの方針によるところが大きいでしょう。ただし、`browserSync()`を使用する場合、引数がオブジェクトであることに注意してください。そのため、引数を指定する際には、`{ }`で囲む必要があります。

とChatGPT先生が仰っておりました。


ではここからは応用で、今までのsassとwebpackと合体させましょう。
先程までの記述は削除し、まずはリロードだけのタスクを作ります。

```javascript:gulpfile.js
// Browsersync関連---------------------------------------------
const browserSync = require("browser-sync"); // browser-syncのプラグインの読み込み

// リロード設定
const browserReload = (done) => { // "browserReload"というgulpタスクを定義 (done)はラストのdone()でタスク完了の合図を受け取るためのもの
  browserSync.reload(); // 同期しているブラウザをリロード
  done(); //done()でタスク完了の信号を出す
};
```

これだけでは意味がないので、Browsersyncタスクを作成し、監視タスクとリロードタスクを集結させます！
```javascript:gulpfile.js
const watchFiles = (done) => {
  browserSync({
    server : {
        baseDir : './', // ルートとなるディレクトリを指定
        index : './src/index.html', // 読み込むHTMLファイル（ワイルドカードNG）
    },
  });

  watch("./src/sass/**/*.scss", compileSass); // scssファイルの監視 ＆ sassコンパイル
  watch("./src/jsx/**/*.jsx", bundleWebpack); // jsxファイルの監視 ＆ webpackバンドル
  watch("./src/*.html", browserReload); // htmlファイルの監視 ＆ ブラウザリロード
  watch("./dist/*.css", browserReload); // sassコンパイルされてcssファイルが更新されるのを監視 ＆ ブラウザリロード
  watch("./dist/*.js", browserReload); // webpackバンドルされてjsファイルが更新されるのを監視 ＆ ブラウザリロード

  done();
};
```
監視作業内でブラウザリロードで設定しているのが3つありますが、これを1つにまとめるとこうなります。
```javascript:gulpfile.js
const watchFiles = (done) => {
  browserSync({
    server : {
        baseDir : './', // ルートとなるディレクトリを指定
        index : './src/index.html', // 読み込むHTMLファイル（ワイルドカードNG）
    },
  });

  watch("./src/sass/**/*.scss", compileSass); // scssファイルの監視 ＆ sassコンパイル
  watch("./src/jsx/**/*.jsx", bundleWebpack); // jsxファイルの監視 ＆ webpackバンドル
  watch("./src/*.html", browserReload); // htmlファイルの監視 ＆ ブラウザリロード
  watch("./dist/*.css", browserReload); // sassコンパイルされてcssファイルが更新されるのを監視 ＆ ブラウザリロード
  watch("./dist/*.js", browserReload); // webpackバンドルされてjsファイルが更新されるのを監視 ＆ ブラウザリロード

  done();
};
```
ではここで一度`gulpfile.js`内の全体を見てみます
```javascript:gulpfile.js
const { src, dest, watch } = require("gulp"); // gulpモジュールを読み込む

// Sass関連---------------------------------------------
const sass = require("gulp-sass")(require("sass")); // gulp-sassモジュールを読み込み、Sassコンパイラを設定する

const compileSass = (done) => { // "compileSass"というgulpタスクを定義、 (done)はラストのdone()でタスク完了の合図を受け取るためのもの
  src("src/sass/style.scss") // コンパイルするSassファイルを指定
    .pipe(
      sass({
        outputStyle: "expanded" // 出力されるCSSの書式を"expanded"（展開形式）に設定する
      })
    )
    .pipe(dest("./dist")) // 出力先ディレクトリを指定
    done(); //done()でタスク完了の信号を出す
};

exports.sassTest = compileSass;

// webpack関連---------------------------------------------
const webpack = require("webpack"); // webpackのJavaScript APIを使用するためのライブラリ
const webpackStream = require('webpack-stream'); // webpackをgulpで使用するためのプラグイン
const webpackConfig = require("./webpack.config"); // webpackの設定ファイルの読み込み（さきほど作成したwebpack.config.jsのコンフィグ情報を読み込む）

const bundleWebpack = (done) => { // "webpack"というgulpタスクを定義、 (done)はラストのdone()でタスク完了の合図を受け取るためのもの
  webpackStream(webpackConfig, webpack) // webpackStreamを使用して、webpackを実行します。webpackConfigは設定ファイル、webpackはwebpackの実行ファイルを指定します。
    .pipe(dest("dist")); // 出力先ディレクトリを指定して、バンドルしたJavaScriptファイルを出力
    done(); //done()でタスク完了の信号を出す
};

exports.bundleTest = bundleWebpack;

// Browsersync関連---------------------------------------------
const browserSync = require("browser-sync"); // browser-syncのプラグインの読み込み

// リロード設定
const browserReload = (done) => { // "browserReload"というgulpタスクを定義 (done)はラストのdone()でタスク完了の合図を受け取るためのもの
  browserSync.reload(); // 同期しているブラウザをリロード
  done(); //done()でタスク完了の信号を出す
};

// Browsersync起動して監視
const watchFiles = (done) => { // "watchFiles"というgulpタスクを定義 (done)はラストのdone()でタスク完了の合図を受け取るためのもの
  browserSync({ // BrowserSyncライブラリを初期化するメソッドらしい
    server : {
        baseDir : './', // ルートとなるディレクトリを指定
        index : './src/index.html', // 読み込むHTMLファイル
    },
  });

  // 監視設定（監視対象に変化があったら、指示されたタスクを実行）
  watch("./src/sass/**/*.scss", compileSass); // scssファイルの監視 ＆ sassコンパイル
  watch("./src/jsx/**/*.jsx", bundleWebpack); // jsxファイルの監視 ＆ webpackバンドル
  watch(["./src/*.html", "./dist/*.css", "./dist/*.js"], browserReload); // ファイルに変更があれば同期しているブラウザをリロード

  done(); //done()でタスク完了の信号を出す
};

exports.default = watchFiles; // npx gulpというコマンドを実行した時、watchSassFilesが実行されるようにします
```

では実行してみましょう。
今回はgulpのデフォルトタスクに設定したので下記のコマンドで実行できます。
```
npx gulp
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3310435/0714aa56-9f85-3cb0-e7e9-b327f394658d.png)

テストしてみたところ、ファイル更新されるたびにブラウザリロードしてくれました。
これで完了です！
`exports.sassTest = compileSass;`や`exports.bundleTest = bundleWebpack;`は不要になったので削除してもかまいません。

画像圧縮は頻繁にすることではないのでブラウザリロードとの紐づけはしませんでした。



## 5.gulp-imagemin（画像圧縮）導入
参考にするサイトは下記です。

[gulpで画像を軽量化、gulp-imageminを使ってコマンドラインから画像を圧縮する（インストール編）](https://www.asobou.co.jp/blog/web/gulp-imagemin)
[gulpで画像を軽量化、gulp-imageminを使ってコマンドラインから画像を圧縮する（オプション・プラグイン編）](https://www.asobou.co.jp/blog/web/gulp-imagemin-2)
[【gulp-imagemin】メタ言語コンパイル環境で画像圧縮も実行する](https://www.i-ryo.com/entry/2020/11/22/163820)
[gulpfile.esm.js で gulp-imagemin v8.0.0 を利用するとエラーになる](https://dskd.jp/archives/116.html)
[gulpfile.jsをES Modulesでの記述に移行する](https://cly7796.net/blog/other/migrate-gulpfile-js-to-es-modules/)
[CommonJSとES Modulesについてまとめる](https://zenn.dev/yodaka/articles/596f441acf1cf3)

たくさんありますよね、それだけ沼っていました…。
なぜ悩んだかというと、gulp-imageminパッケージのバージョンによって記述が変わるためです。
最新のバージョンですと、現在の`gulpfile.js`内のコード（CommonJS 構文）をES Modules 記法に全て記述し直す必要があるみたいです。
他にやり方はないのかとか、ES Modules 記法でチャレンジしたりしたのですが、今の自分では無理でした…。
なので今回は大人しく古いバージョンである。(7.x系)をインストールしていきます。

下記を入力してインストールします。最後の`@7`は古いバージョンであることを示しています。
```
npm install -D gulp-imagemin@7
```

`gulpfile.js`に下記を追加します。
```javascript:gulpfile.js
const imagemin = require('gulp-imagemin'); // gulp-imageminのプラグインの読み込み

const ImgImagemin = (done) => { // "ImgImagemin"というgulpタスクを定義
  src('src/images/**/*') // 圧縮するファイルを指定
    .pipe(imagemin())
    .pipe(dest('dist/images')) // 出力先ディレクトリを指定
    done();
};

exports.img = ImgImagemin;
```
（追記）
上記ですが、もしかしたらコールバック関数（done部分）は不要かも？
理由はChatGPT先生に聞いても理解できませんでしたが、試してみてもいいかも。


下記を入力して実行してみます。
```
npx gulp img
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3310435/1b074ead-449c-b885-93ee-cac9f7c76a14.png)
画像圧縮されました、比較してみます。
圧縮前…175KB
圧縮後…34.2KB
となりました、ちゃんと圧縮されていますね！

## 6.まとめ
最終的な`gulpfile.js`の中身は下記になりました。
```javascript:gulpfile.js

const { src, dest, watch } = require("gulp"); // gulpモジュールを読み込む

// Sass関連---------------------------------------------
const sass = require("gulp-sass")(require("sass")); // gulp-sassモジュールを読み込み、Sassコンパイラを設定する

const compileSass = (done) => { // "compileSass"というgulpタスクを定義、 (done)はラストのdone()でタスク完了の合図を受け取るためのもの
  src("src/sass/style.scss") // コンパイルするSassファイルを指定
    .pipe(
      sass({
        outputStyle: "expanded" // 出力されるCSSの書式を"expanded"（展開形式）に設定する
      })
    )
    .pipe(dest("./dist")) // 出力先ディレクトリを指定
    done(); //done()でタスク完了の信号を出す
};

exports.sassTest = compileSass;

// webpack関連---------------------------------------------
const webpack = require("webpack"); // webpackのJavaScript APIを使用するためのライブラリ
const webpackStream = require('webpack-stream'); // webpackをgulpで使用するためのプラグイン
const webpackConfig = require("./webpack.config"); // webpackの設定ファイルの読み込み（さきほど作成したwebpack.config.jsのコンフィグ情報を読み込む）

const bundleWebpack = (done) => { // "webpack"というgulpタスクを定義、 (done)はラストのdone()でタスク完了の合図を受け取るためのもの
  webpackStream(webpackConfig, webpack) // webpackStreamを使用して、webpackを実行します。webpackConfigは設定ファイル、webpackはwebpackの実行ファイルを指定します。
    .pipe(dest("dist")); // 出力先ディレクトリを指定して、バンドルしたJavaScriptファイルを出力
    done(); //done()でタスク完了の信号を出す
};

exports.bundleTest = bundleWebpack;

// Browsersync関連---------------------------------------------
const browserSync = require("browser-sync"); // browser-syncのプラグインの読み込み

// リロード設定
const browserReload = (done) => { // "browserReload"というgulpタスクを定義 (done)はラストのdone()でタスク完了の合図を受け取るためのもの
  browserSync.reload(); // 同期しているブラウザをリロード
  done(); //done()でタスク完了の信号を出す
};

// Browsersync起動して監視
const watchFiles = (done) => { // "watchFiles"というgulpタスクを定義 (done)はラストのdone()でタスク完了の合図を受け取るためのもの
  browserSync({ // BrowserSyncライブラリを初期化するメソッドらしい
    server : {
        baseDir : './', // ルートとなるディレクトリを指定
        index : './src/index.html', // 読み込むHTMLファイル
    },
  });

  // 監視設定（監視対象に変化があったら、指示されたタスクを実行）
  watch("./src/sass/**/*.scss", compileSass); // scssファイルの監視 ＆ sassコンパイル
  watch("./src/jsx/**/*.jsx", bundleWebpack); // jsxファイルの監視 ＆ webpackバンドル
  watch(["./src/*.html", "./dist/*.css", "./dist/*.js"], browserReload); // ファイルに変更があれば同期しているブラウザをリロード

  done(); //done()でタスク完了の信号を出す
};

exports.default = watchFiles; // npx gulpというコマンドを実行した時、watchSassFilesが実行されるようにします

// gulp-imagemin（画像圧縮）関連---------------------------------------------
const imagemin = require('gulp-imagemin'); // gulp-imageminのプラグインの読み込み

const ImgImagemin = (done) => { // "ImgImagemin"というgulpタスクを定義
  src('src/images/**/*') // 圧縮するファイルを指定
    .pipe(imagemin())
    .pipe(dest('dist/images')) // 出力先ディレクトリを指定
    done();
};

exports.img = ImgImagemin;
```
完成品のGit Hubは[こちら](https://github.com/miyaayim-study/template_project_react)になります。

ようやく終わりました…。
かなりぐちゃぐちゃした記事になった自覚はあります…。
また元気が出たり記憶の整理をしたくなったらもっと見やすい記事に整頓するかもしれません。
とりあえず今回は備忘録が目的ですのでこれぐらいで勘弁してください。
おそらくまだまだ改善点や不明な点もたくさんあります。
たとえば、`.on('end', done)`とか`const browserSync = require("browser-sync").create();`とかいう記述も見かけて何が良いのか正しいのかとか、`gulp-plumber`とかいうエラー出ても監視を止めないようにするやつとか…。
正直きりがありませんでした…。
gulpだけに時間を割くわけにもいきませんので、またgulpをカスタマイズしたくなったタイミングで、また知識を深めたいと思います。

それでは、お付き合いありがとうございました。


続き：[【初心者】gulpfile.js内の記述をECMAScript Modules（ESM）構文へ移行メモ【備忘録】](https://qiita.com/miyaayim_study/items/b3d0c6a28e93ab655534)