【初心者】gulpfile.js内の記述をECMAScript Modules（ESM）構文へ移行メモ【備忘録】

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


# 目的

こちらは、前回からの続きになります。
前回：[【初心者】Gulp環境構築メモ【備忘録】](https://qiita.com/miyaayim_study/items/70621d13d9e02a2c3e82)

目的は、gulp-imageminパッケージが最新バージョンだとECMAScript Modules構文（以下、ESM構文）に変更しなければならないため、それに合わせて全ての記述を変更する必要があったためです。
ESM構文への移行にあたり、エクスポートとインポートについて学習しました。
学習結果は下記にまとめました。
[【初心者】エクスポート・インポート記述メモ（ ECMAScript Modules [ ESM ] 構文 ）【備忘録】](https://qiita.com/miyaayim_study/items/29a9090c94b2de9277d8)
[【初心者】エクスポート・インポート記述メモ（ CommonJS Modules構文 ）【備忘録】](https://qiita.com/miyaayim_study/items/dd5beb29f0ee4e245dc0)

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


# 1.拡張子変更
gulpfile内でESM構文はそのまま使用できないため、変更が必要となる。
手段はいくつかあるか、今回は`gulpfile.js`の拡張子を`gulpfile.mjs`に変更する手段で行いました。
理由は、これが一番影響範囲が少なくて済みそうだと感じたため。（素人考えです。）

他の手段や細かい説明は下記のリンクを参考。
[gulpfile.esm.js で gulp-imagemin v8.0.0 を利用するとエラーになる](https://dskd.jp/archives/116.html)
[gulpで画像を軽量化、gulp-imageminを使ってコマンドラインから画像を圧縮する（インストール編）](https://www.asobou.co.jp/blog/web/gulp-imagemin)


# 2.gulpモジュール読み込み変更
ESM構文では、`const { src, dest, watch } = require("gulp"); `が使用できなかったため、下記のように変更しました。

```javascript:gulpfile.mjs
const { src, dest, watch } = require("gulp"); // 変更前
import gulp from 'gulp'; // 変更後
```

これにより、各`src, dest, watch`の前に`gulp.`を付与する必要がありますので、ファイル内の記述を全て変更しました。
下記は変更例です。
```javascript:gulpfile.mjs
src("src/sass/style.scss") // 変更前
gulp.src("src/sass/style.scss") // 変更後
```

# 3.Sass部分の記述変更
ここで躓いたので、まずは基本に戻って下記が何をしているかChatGPT先生に聞いてみた。
```javascript:gulpfile.js
const sass = require("gulp-sass")(require("sass"));
```
>このコードは、Node.jsで開発されたGulpタスクランナーのプラグインであるgulp-sassをインポートしています。また、このプラグインは、Sassコンパイラーの実行に必要な"sass"パッケージを依存関係としてインストールします。
具体的には、gulp-sassは、SassファイルをCSSにコンパイルするタスクを簡単に作成するためのツールです。Sassは、CSSの拡張言語であり、CSSをより効率的に記述することができます。gulp-sassを使用することで、Sassファイルを自動的にコンパイルし、更新されたCSSファイルを出力することができます。

自力では解決できなかったので、ChatGPT先生にESM構文に変更してもらいました。
```javascript:gulpfile.mjs
// 変更前
const sass = require("gulp-sass")(require("sass")); // gulp-sassモジュールを読み込み、Sassコンパイラを設定する

// 変更後
import gulpSass from "gulp-sass"; // gulp-sassパッケージのgulp-sassモジュールをインポートする
import sassCompiler from "sass"; // sassパッケージに含まれるSassコンパイラ本体のsassモジュールをインポートする
const sass = gulpSass(sassCompiler); // gulp-sassのコンストラクタ関数にsassコンパイラを渡して、コンパイラを使用するための定数sassを定義
```
>上記のコードでは、import文を使用して必要なパッケージをインポートしています。また、constキーワードを使用して、gulp-sassとsassパッケージを取得し、gulp-sassのコンストラクター関数にsassパッケージを渡しています。

回答をみたらなんとなく理解できました。

あとは、エクスポート部分の記述を変更しました。
```javascript:gulpfile.mjs
exports.sassTest = compileSass; // 変更前
export { compileSass }; // 変更後
```


Sass部分の全体としては下記になりました。

```javascript:gulpfile.mjs
// Sass関連---------------------------------------------
import gulpSass from "gulp-sass"; // gulp-sassパッケージのgulp-sassモジュールをインポートする
import sassCompiler from "sass"; // sassパッケージのsassモジュールをインポートする
const sass = gulpSass(sassCompiler); // gulp-sassのコンストラクタ関数にsassコンパイラを渡して、コンパイラを使用するための定数sassを定義


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

export { compileSass };
```

# 4.Webpack部分の記述変更
まず、インポート・エクスポート部分の記述をESM構文に変更してみました。
```javascript:gulpfile.mjs
const webpack = require("webpack"); // webpackのJavaScript APIを使用するためのライブラリ
const webpackStream = require('webpack-stream'); // webpackをgulpで使用するためのプラグイン
const webpackConfig = require("./webpack.config"); // webpackの設定ファイルの読み込み

// 上記を下記に変更

import webpack from 'webpack';  // webpackのJavaScript APIを使用するためのライブラリ
import webpackStream from 'webpack-stream';  // webpackをgulpで使用するためのプラグイン
import webpackConfig from './webpack.config';  // webpackの設定ファイルの読み込み
```

```javascript:gulpfile.mjs
exports.bundleTest = bundleWebpack; // 変更前
export { bundleWebpack }; // 変更後
```

実行したがエラー発生、`webpack.config`が参照できないとあるので、インポートの参照記述に拡張子も追加。
さらに`webpack.config.js`もESM構文にしなければならないとのことなので、`webpack.config.js`の拡張子を`webpack.config.mjs`に変更。

```javascript:gulpfile.mjs
import webpackConfig from './webpack.config'; // 変更前
import webpackConfig from './webpack.config.mjs'; // 変更後
```

次に、`webpack.config.mjs`内の記述をESM構文に変更。
```javascript:webpack.config.mjs
module.exports = { // 変更前
export default { // 変更後
```

実行したがエラー発生、ESMでは `__dirname` の使用が許可されていないとのこと。
`__dirname`は現在のファイルが存在するディレクトリのパスを表すグローバル変数。

なので、出力ファイルのディレクトリ設定を変更する。
試しに、`__dirname` の記述を削除して相対パスとして実行したがエラー発生、Webpackの`path`オプションは、絶対パスを指定する必要があるとのこと。
解決策として、`path.resolve()`メソッドを使用する。
`path.resolve()` メソッドは、与えられたパスのセグメントを結合し、絶対パスを生成するもの。
今回はセグメント結合ではなく、絶対パスの目的で使用。
`path.resolve()`メソッドを使用するには、Node.jsの`path`モジュールをインポートする必要がある。
`path`モジュールは、ファイルパスを扱うための便利な関数を提供するために使用される、`path.resolve()`メソッドもそのうちの一つ。

ということで、まずはNode.jsの`path`モジュールをインポートする。
`webpack.config.mjs`内の先頭に下記を追加。
```javascript:webpack.config.mjs
import path from 'path'; // 追加
```
次に、`outoput`の`path`記述を変更する。
```javascript:webpack.config.mjs
path: `${__dirname}/dist`, // 変更前
path: path.resolve("./dist"), // 変更後
```
ここまで記述し終えて実行したところ、無事成功しました。

Webpack部分全体としては下記になりました。
```javascript:gulpfile.mjs
import gulp from 'gulp'; // gulpモジュールを読み込む

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

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

export { bundleWebpack };
```

```javascript:webpack.config.mjs
import path from 'path'; // Node.jsのpathモジュールをインポートして、path.resolve()メソッドを使用できるようにしている。

export default {
  mode: "development", // 開発モード（"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: path.resolve("./dist"), //  出力ファイルのディレクトリ名
    filename: "main.js", // 出力ファイル名
  },
  resolve: { // モジュールを解決するためのオプション
    extensions: [".js", ".jsx", ".ts", ".tsx"]  // importやrequireで指定する際に、拡張子を省略できるようにする
  },
};
```

# 5.Browsersync部分の記述変更
エクスポートとインポートの記述をESM構文に変更しました。
```javascript:gulpfile.mjs
const browserSync = require("browser-sync"); // 変更前
import browserSync from 'browser-sync'; // 変更後
```

```javascript:gulpfile.mjs
exports.default = watchFiles; // 変更前
export default watchFiles; // 変更後
```

Browsersync部分全体としては下記になりました。
```javascript:gulpfile.mjs
// Browsersync関連---------------------------------------------
import browserSync from '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ファイル
    },
  });

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

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

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

# 5.gulp-imagemin部分の記述変更
エクスポートとインポートの記述をESM構文に変更しました。
```javascript:gulpfile.mjs
const imagemin = require('gulp-imagemin'); // 変更前
import imagemin from 'gulp-imagemin'; // 変更後
```

```javascript:gulpfile.mjs
exports.img = ImgImagemin; // 変更前
export { ImgImagemin }; // 変更後
```

Browsersync部分全体としては下記になりました。
```javascript:gulpfile.mjs
// gulp-imagemin（画像圧縮）関連---------------------------------------------
import imagemin from 'gulp-imagemin'; // gulp-imageminのプラグインの読み込み

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

export { ImgImagemin };
```
return文やコールバック関数はなしでもいけるらしいです。
理由はChatGPT先生に聞いたけど理解できませんでした…。
もしかしたら前回時点で既に不要だったのかもしれません。


# 6.まとめ
結果として、各ファイルの中身は下記のようになりました。
```javascript:gulpfile.mjs
import gulp from 'gulp'; // gulpモジュールを読み込む

// Sass関連---------------------------------------------
import gulpSass from "gulp-sass"; // gulp-sassパッケージのgulp-sassモジュールをインポートする
import sassCompiler from "sass"; // sassパッケージに含まれるSassコンパイラ本体のsassモジュールをインポートする
const sass = gulpSass(sassCompiler); // gulp-sassのコンストラクタ関数にsassコンパイラを渡して、コンパイラを使用するための定数sassを定義

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

export { compileSass };


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

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

export { bundleWebpack };


// Browsersync関連---------------------------------------------
import browserSync from '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ファイル
    },
  });

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

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

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


// gulp-imagemin（画像圧縮）関連---------------------------------------------
import imagemin from 'gulp-imagemin'; // gulp-imageminのプラグインの読み込み

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

export { ImgImagemin };
```

```javascript:webpack.config.mjs
import path from 'path'; // Node.jsのpathモジュールをインポートして、path.resolve()メソッドを使用できるようにしている。

export default {
  mode: "development", // 開発モード（"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: path.resolve("./dist"), //  出力ファイルのディレクトリ名
    filename: "main.js", // 出力ファイル名
  },
  resolve: { // モジュールを解決するためのオプション
    extensions: [".js", ".jsx", ".ts", ".tsx"]  // importやrequireで指定する際に、拡張子を省略できるようにする
  },
};
```

以上です。

続き：[【初心者】Gulpでベンダープレフィックス自動付与（Autoprefixer）メモ【備忘録】](https://qiita.com/miyaayim_study/items/9f6b471775293d87ea09)



# 参考サイト
[gulpfile.esm.js で gulp-imagemin v8.0.0 を利用するとエラーになる](https://dskd.jp/archives/116.html)
[gulpで画像を軽量化、gulp-imageminを使ってコマンドラインから画像を圧縮する（インストール編）](https://www.asobou.co.jp/blog/web/gulp-imagemin)