【2021年】Gulp4 インストールとSass,EJSの環境設定までの手順メモ

こんにちは、デザイナーの [kanna ](https://twitter.com/white_raichi) です。
この記事はWEB制作の環境構築に初挑戦して学んだことをまとめています。

__Gulp４__についての内容になります。
バージョンの違いによって書き方が変わる部分があるのでご注意ください。

【環境】
・macOS Big Sur v11.4 
・VScode v1.59.0
・zsh

#そもそもGulpって何？
gulpはNode.jsをベースとしたタスクランナーの一つです。タスクランナーとは、Webサイト構築に必要な処理をタスクとして自動化してくれるプログラムのことで、「どのファイルを」「どのように加工して」「どこに出力するか」などをタスクとして設定できるツール全般のことを指します。


* [現場で使えるgulp入門｜第1回 gulpとは何か | CodeGrid](https://www.codegrid.net/articles/2014-gulp-1/) 
* [Gulp公式サイト](https://gulpjs.com/) 

#gulpで実現したいこと
* __Sassのコンパイル（scssをcssへ）__
* __EJSをhtmlファイルに変換__
* __画像(jpeg, png, gif)の圧縮__
* __ブラウザの自動更新__

今回はこの４つの処理を自動化したいので、これらを中心に解説していきます。


#Node.jsインストールまでの流れを確認
Gulpを動かすためにはNode.jsが必要です。
Node.jsをインストールしたい場合、公式サイトから直接インストールしても問題ありません。
ただし、複数のプロジェクトで異なるバージョンを使う場合には、__公式のインストーラとHomebrew経由では複数のバージョンをインストールできない__のでおすすめしません。

プロジェクトごとにNode.jsのバージョンを管理できる__anyenv__というツールが便利だと教えてもらったので、anyenv経由でnode.jsをインストールすることにしました。


__Homebrew 経由の anyenv 経由の nodenv 経由で Node.js をインストール__する。

__いや、経由しすぎ..！！！__（ゴールが遠く感じる瞬間…笑）


1. Homebrewのインストール
1. anyenvのインストール
1. nodenvのインストール
1. Node.jsのインストール




精神的なダメージをくらいつつも順番が分かったので、実際にインストールをしてみる。



* [Homebrew 経由の anyenv 経由の nodenv 経由で Node.js をインストールする](https://qiita.com/ucan-lab/items/ce35bcaf2562054917a8) 




#Homebrewをインストール
Macでターミナルを立ち上げて、[Homebrew 公式サイト](https://brew.sh/index_ja) から記載されているインストールコマンド（シェルスクリプト）をターミナル画面にペーストし実行（Enter）します。


```zsh:zsh
/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"
```

:::note info
HomebrewはmacOS用のパッケージマネージャーです
macOS Catalinaからデフォルトシェルが bash から zsh に変更されています
:::

#anyenvをインストール

[anyenv](https://github.com/anyenv/anyenv)でインストール手順を確認
zshの場合だと、コマンド入力の出だしが `$` ではなく `%` になっている

####anyenvをインストール

```zsh:zsh
brew install anyenv
```

####シェルにanyenvを設定する

```zsh:zsh
anyenv install --init
```

####pathを通す
インストールしただけではコマンドを打っても動かないので、コマンドがたたけるように`path`を通します。PATHを通すってなんぞや？という方は下記の記事がオススメです。
 [PATHを通すとは？ (Mac OS X)](https://qiita.com/soarflat/items/09be6ab9cd91d366bf71) 

```zsh:zsh
echo 'eval "$(anyenv init -)"' >> ~/.zshrc

```

####シェルを再起動する
pathを通したらログインシェルを再起動して新しい設定ファイルを読ませる。

```zsh:zsh
exec $SHELL -l

```

####下記のコマンド実行すればpathが通るようになる

```zsh:zsh
eval “$(anyenv init -)”

```


####インストールしたバージョンを確認する
コマンドを実行した後に `anyenv X.X.X `と出ればインストール成功です。

```zsh:zsh
anyenv -v

```


####anyenv-update プラグイン をインストールする

```zsh:zsh
mkdir -p $(anyenv root)/plugins

```

```zsh:zsh
git clone https://github.com/znz/anyenv-update.git $(anyenv root)/plugins/anyenv-update

```




####インストールできる○○envを確認する
下記のコマンドを打つと、インストールできる一覧がズラッとでます。
その中に目的の `nodenv` が入っていることが確認できます。

```zsh:zsh
anyenv install --list

```

#nodenvをインストール(anyenv経由）

####anyenvから [nodenv](https://github.com/nodenv/nodenv) をインストールする

```zsh:zsh
anyenv install nodenv　

```

####シェルを再起動する

```zsh:zsh
exec $SHELL -l

```

####nodenv-default-packagesを設定する

```zsh:zsh
touch $(nodenv root)/default-packages

```

#ターミナルでプロジェクトフォルダに移動する

:::note warn
node.jsはプロジェクトごとにバージョンを管理するので、必ずプロジェクトフォルダに移動してからインストールを行ってください。
:::

`cd`コマンドで移動するか、VScode内でターミナルを立ち上げるかどちらでも大丈夫です。
VScode上でフォルダを開いた状態でターミナルを立ち上げると、そのフォルダに移動した状態で立ち上がるので移動の手間がないのでオススメです。

例えば「GULP-TEST」というフォルダをVScodeで開く。
VScodeの上のメニューから「ターミナル」を押すか、下記のショートカットで起動できます。
`ctrl + Shift + @`




#nodenvでnode.jsをインストール
いよいよnode.jsのインストールです…

####インストール可能な node.js のバージョンを確認

```zsh:zsh
nodenv install --list

```


インストールするバージョンは、最新の一つ前のメジャーバージョンの最新がバグなども少なくておすすめだそうです。例えば、`v16.6.1`が最新の場合、`v15.14.0`をインストールすると良いとのこと。

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/486362/a3e0b6f6-3777-323b-1435-33a996070a9d.png" width=100 />

####バージョンを指定してnode.js をインストール

```zsh:zsh
nodenv install 15.14.0

```

####グローバルの node.js を特定バージョンで有効化

```zsh:zsh
nodenv global 14.12.0

```

特定のディレクトリだけNodeバージョンを切り替えたい場合は、切り替えたいプロジェクトディレクトリにいってから `nodenv local x.x.x `を実行すると、`.node-version`という隠しファイルが作成されます。これでnodenvがバージョンを自動で切り替えてくれます。


####インストール後にリハッシュする

```zsh:zsh
nodenv rehash

```

:::note info
nodenvからnodeやグローバルnpmパッケージを見えるようにするためにrehashします。
新しいnodeのバージョンやnpm install -gなどを行ったときに実行する必要があります。
:::

####インストールしたnode.jsのバージョンを確認

```zsh:zsh
node -v

```


バージョン番号が正しく表示されればインストール成功です。



#プロジェクトフォルダのディレクトリ構成
`GULP-TEST`というプロジェクトフォルダを作ったとして、配置はこんな感じ。


<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/486362/d33f1c79-187b-c326-efcd-a12fd803da4a.png" width=300px />


#package.jsonファイルを作成
移動した`GULP-TEST`フォルダで以下のコマンドを打ちます。

```zsh:zsh
npm init -y

```

フォルダの直下に__`pakage.json`__というファイルが自動生成されます。


#Gulpをローカルにインストールする



```zsh:zsh
npm install -D gulp

```

`-D`はローカルの`--save-dev`の略 。
最近ではGulpはローカルにインストールするのが主流のようです。

:::note info
グローバルインストール ＝ PC上のどこからでも利用できる
ローカルインストール ＝ プロジェクトのフォルダ内でのみ利用できる
:::

コマンドの実行が完了すると、__`node_modules`__と__`pakage-lock.json`__が自動生成されます。

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/486362/5eee07db-5a0e-0915-8542-9cf97bbae0db.png" width=300px />

インストールしたバージョンも確認しましょう。

```zsh:zsh
gulp -v

```

`local version : ○.○.○`と出ていればOK！

#Gulpのプラグインをインストールする
[Gulpプラグイン](https://gulpjs.com/plugins/) にたくさんのプラグインがあります。
よく使用されているものをインストールします。

 * gulp-sass   
 * gulp-plumber
 * gulp-notify
 * gulp-sass-glob
 * browser-sync
 * gulp-postcss
 * autoprefixer
 * css-declaration-sorter
 * gulp-imagemin
 * imagemin-optipng
 * imagemin-mozjpeg
 * gulp-ejs
 * gulp-rename


####上記のプラグインを一括でローカルインストールする

```zsh:zsh
npm install gulp-sass gulp-plumber gulp-notify gulp-sass-glob browser-sync gulp-postcss autoprefixer css-declaration-sorter gulp-imagemin imagemin-optipng imagemin-mozjpeg gulp-ejs gulp-rename --save-dev

```
インストールが完了したら`package.json`ファイルを開いて、`devDependencies`にインストールしたプラグインたちが追加されているか確認します。

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/486362/1cbe9141-35c0-7724-5bc9-8d4d39c0f4fb.png" width=450px />

もし`devDependencies`ではなく`dependencies`に追記されていたらグローバルインストールしたことになります。その場合はグローバルにいれたものをアンインストールして、もう一度ローカルにインストールしなおしてください。

自分の場合、参照したサイトが多すぎて最初にグローバルインストールしてしまってgulp実行でエラー吐きまくりになったので要注意です。

#gulpfile.jsを作成する
package.jsonファイルと同じ階層に手動で`gulpfile.js`ファイルを作成します。
（＋）のファイルアイコンから新規作成で追加できます。

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/486362/1ee73d2d-82b5-f82e-4db7-6a6aea3ada84.png" width=300px />

作成したgulpfile.jsの中身を書いていきます。
スーパー初心者なので、コード元は参考にさせていただいた下記のサイト様からお借りしてちょこっと編集しております。主にエラー部分と参照元ファイルのパス、出力先パスを変更しました。



 
 * [Gulp4で作業爆速化！オススメ設定を公開する【コピペOK】](https://flex-box.net/gulp/#co-index-19)


```js:gulpfile.js
var gulp = require('gulp');
var sass = require('gulp-sass')(require('sass')); //Sassコンパイル
var plumber = require('gulp-plumber'); //エラー時の強制終了を防止
var notify = require('gulp-notify'); //エラー発生時にデスクトップ通知する
var sassGlob = require('gulp-sass-glob'); //@importの記述を簡潔にする
var browserSync = require('browser-sync'); //ブラウザ反映
var postcss = require('gulp-postcss'); //autoprefixerとセット
var autoprefixer = require('autoprefixer'); //ベンダープレフィックス付与
var cssdeclsort = require('css-declaration-sorter'); //css並べ替え
var imagemin = require('gulp-imagemin');//画像を自動圧縮する
var optipng = require('imagemin-optipng');//pngの圧縮
var mozjpeg = require('imagemin-mozjpeg');//jpegの圧縮
var ejs = require("gulp-ejs");//htmlに変換
var rename = require("gulp-rename"); //.ejsの拡張子を変更


// scssのコンパイル
gulp.task('sass', function () {
    return gulp
        .src('./src/scss/**/*.scss', gulp.task('sass'))
        .pipe(plumber({ errorHandler: notify.onError("Error: <%= error.message %>") }))//エラーチェック
        .pipe(sassGlob())//importの読み込みを簡潔にする
        .pipe(sass({
            outputStyle: 'expanded' //expanded, nested, campact, compressedから選択
        }))
        .pipe(postcss([autoprefixer(
            {
                // ☆IEは11以上、Androidは5以上
                // その他は最新2バージョンで必要なベンダープレフィックスを付与する
                "overrideBrowserslist": ["last 2 versions", "ie >= 11", "Android >= 5"],
                cascade: false
            }
        )]))
        .pipe(postcss([cssdeclsort({ order: 'alphabetical' })]))//プロパティをソートし直す(アルファベット順)
        .pipe(gulp.dest('./dest/css'));//コンパイル後の出力先
});

// 保存時のリロード
gulp.task('browser-sync', function (done) {
    browserSync.init({
        //ローカル開発
        server: {
            baseDir: "./",
            index: "index.html"
        }
    });
    done();
});
gulp.task('bs-reload', function (done) {
    browserSync.reload();
    done();
});
var browserSync = require('browser-sync'); //再度ブラウザ反映



gulp.task("ejs", (done) => {
    gulp
        .src(["ejs/**/*.ejs", "!" + "ejs/**/_*.ejs"])
        .pipe(plumber({ errorHandler: notify.onError("Error: <%= error.message %>") }))//エラーチェック
        .pipe(ejs())
        .pipe(rename({ extname: ".html" })) //拡張子をhtmlに
        .pipe(gulp.dest("./dest/html")); //出力先
    done();
});

// 監視
gulp.task('watch', function (done) {
    gulp.watch('./src/scss/**/*.scss', gulp.task('sass')); //sassが更新されたらgulp sassを実行
    gulp.watch('./src/scss/**/*.scss', gulp.task('bs-reload')); //sassが更新されたらbs-reloadを実行
    gulp.watch('./src/js/*.js', gulp.task('bs-reload')); //jsが更新されたらbs-relaodを実行
    gulp.watch('./ejs/**/*.ejs', gulp.task('ejs')); //ejsが更新されたらgulp-ejsを実行
    gulp.watch('./ejs/**/*.ejs', gulp.task('bs-reload')); //ejsが更新されたらbs-reloadを実行
});

// default
gulp.task('default', gulp.series(gulp.parallel('browser-sync', 'watch')));

//圧縮率の定義
var imageminOption = [
    optipng({ optimizationLevel: 5 }),
    mozjpeg({ quality: 85 }),
    imagemin.gifsicle({
        interlaced: false,
        optimizationLevel: 1,
        colors: 256
    }),
    imagemin.mozjpeg(),
    imagemin.optipng(),
    imagemin.svgo()
];

// 画像の圧縮
// $ gulp imageminで./src/img/base/フォルダ内の画像を圧縮し./src/img/フォルダへ
// .gifが入っているとエラーが出る
gulp.task('imagemin', function () {
    return gulp
        .src('./src/img/base/*.{png,jpg,gif,svg}')
        .pipe(imagemin(imageminOption))
        .pipe(gulp.dest('./src/img'));
});

``` 

#タスクの書き方を簡単に解説


```js:gulpfile.js
var plumber = require('gulp-plumber'); //エラー時の強制終了を防止
var notify = require('gulp-notify'); //エラー発生時にデスクトップ通知する
var ejs = require("gulp-ejs");//htmlに変換
var rename = require("gulp-rename"); //.ejsの拡張子を変更

gulp.task("ejs", (done) => {
    gulp
        .src(["ejs/**/*.ejs", "!" + "ejs/**/_*.ejs"])
        .pipe(plumber({ errorHandler: notify.onError("Error: <%= error.message %>") }))//エラーチェック
        .pipe(ejs())
        .pipe(rename({ extname: ".html" })) //拡張子をhtmlに
        .pipe(gulp.dest("./dest/html")); //出力先
    done();
});
```

`var ○○○○ = require("gulp-○○○○");`で宣言をして、下にタスクを追加していきます。
タスクは`.pipe()`を使うことで複数の処理を簡潔にまとめて書くことができます。
新しく処理を追加したい場合は`.pipe()`を増やして中身を追記し、プラグインが必要であればインストールして宣言文も追加してください。


処理の流れは、`ejs`フォルダにある`.ejs`形式のファイルに対してエラーチェック＞コンパイル＞拡張子の変換 の順で処理を行い、最後に`gulp.dest()`というメソッドによってファイルを書き出します。`gulp.dest()`のカッコの中に出力先のパスを指定します。

書き方はざっとこんな感じです。

#テスト用のファイルを準備する
index.ejsとstyle.scssを作成して処理が実行できるかをテストします。
`./ejs`フォルダに`index.ejs`を置いて`./src/scss`の下に`style.scss`を置いています。ファイルの置き場所とgulpfile.jsに記載した参照元が一致していないと動かないので、パスがあっているかしっかり確認します。


<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/486362/6393865c-cdaa-f005-8c34-36b0b506c942.png" width=300px />


適当なコードを用意する

```scss:style.scss
//サンプルです
#main {
    width: 600px;
    p {
        margin-bottom: 20px;
        padding-top: 10px;
    }
    span {
        font-size: 16px;
        color: #c53b3b;
    }
}

```

```html:index.ejs
<!DOCTYPE html>
<html lang="ja">
<head prefix="og: http://ogp.me/ns# fb: http://ogp.me/ns/fb# article: http://ogp.me/ns/article#">

    <meta charset="UTF-8">
    <title><%= (typeof title != "undefined" ? title : "") %></title>

</head>

<body>
</body>
</html>

```

#タスクを実行して動作確認をする

ターミナルで以下のコマンドを入力して実行すると、destフォルダ内のhtmlフォルダ内にindex.htmlが生成される。

```zsh:zsh
gulp ejs

```
同様にsassコマンドを実行すればstyle.cssファイルが生成される。

```zsh:zsh
gulp sass

```
画像圧縮コマンド

```zsh:zsh
gulp imagemin

```

`npx gulp`でタスクを一括実行することもできます。sassとejsの変換、ブラウザのリロードが実行されます。

```zsh:zsh
npx gulp

```

このように出力されていればOKです👌
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/486362/1c629ec1-9b7c-4f38-8360-643ba66bd650.png" width=300px />

＼(^o^)／完成！！！！！　

#おわりに
知識ほぼ０からのスタートなので、いろんな情報をインプットするのが大変でした。
いろんなエラーを解消するのに３日くらい悩みましたが、おかげでgulpfile.jsの中身が大分理解できるようになりました。自分にとっては結構ハードルがありましたが、出来たときの喜びも半端なかったです。

この記事が少しでも誰かのお役に立てれば嬉しいです😊

#おまけ：エラー解消
大量のエラーに遭遇しましたが、その中でも特に苦しんだものをピックアップします…。

`gulp: command not found`  > パスが通っていない可能性
https://dezanari.com/gulp-error-matome/
https://masahiro.me/2019/06/post-2246/
https://qiita.com/sugurutakahashi12345/items/b814a09b65d8852226ad


`Error: Cannot find module 'date'`> インストール失敗orパスが通ってない可能性
https://techacademy.jp/magazine/16113
https://wordpress.ideacompo.com/?p=12299
https://teratail.com/questions/167950


`gulp-notify: [Error running Gulp] Error: Invalid built-in order 'alphabetically' provided.
Available built-in orders are: alphabetical,concentric-css,smacss` > gulpfile内の文字を修正　
https://teratail.com/questions/258845



#参考サイト
https://ics.media/entry/3290/

https://flex-box.net/gulp/#co-index-18

https://www.asobou.co.jp/blog/web/sass-gulp

https://techacademy.jp/magazine/16105

https://qiita.com/manabuyasuda/items/0971dbd3729cf68f87fb

https://qiita.com/tatakahashiap/items/1c4ab221c4993e7c4ebf

https://qiita.com/non_cal/items/a8fee0b7ad96e67713eb

https://qiita.com/282Haniwa/items/a764cf7ef03939e4cbb1