【制作過程実況動画付】npm scriptとGulp4で簡単なWeb制作向けタスクランナーを作る方法

こちらの記事では、npm script からGulpを呼び出してSass→CSSのコンパイルをさせたり、画像を最適化して別のところに出力させたり、JavaScriptをBabelを通して書き換えたりするためのタスクランナーを自分でつくることができるようになります。

noteで有料販売したけれど売れなかったので無料公開します!w

実際には、こんな感じのものを自分で作ることができます。

最下部に、実際作っている実況動画と最終的なコードを掲載しているので、とりあえず試したいという方は、そちらを試されてから必要なところだけ読んでもらうという使い方もしていただけるかと思います。

前提

こちらの環境はMacですので、Macでの解説がメインです。Windows利用者の方は適宜読み換える必要がある部分があります。あらかじめご了承ください。

あると良い知識

JavaScriptがなんとなくわかると良いと思います。npmは見よう見まねでも今は大丈夫でしょう。使っていく上で必要であれば自ずと覚えれます。

タスクランナーとは?

Web制作で言うタスクランナーとは、

「タスクランナー」とは、「タスク(仕事・課題)」をプログラム処理で自動化してくれるツール全般のことを指します。
https://www.aura-office.co.jp/blog/taskrunner_nodejs/

というようなもので、Web制作における様々なタスクをプログラムで自動的に処理してもらう仕組みのことを言います。

例えば、
・SassをCSSにコンパイル(変換)する
・EJSをHTMLへ変換する(←今回は取り扱いません)
・画像データを最適化(圧縮)する

このようなことを毎回コマンドライン(Terminalとか黒い画面って言われるやつ)でコードを入力して実行して・・・とやらなければならないタスクをプログラム(今回で言うとGulp)で自動化してしまおう!ということです。

なんとなくわかってもらえれば大丈夫です!

下準備

では、実際にタスクランナーを構築していきましょう。

まずはプロジェクトのディレクトリ(フォルダー)を作る

まずは好きな場所にプロジェクト用のディレクトリ(フォルダー)を作ります。

例えば、MAMP(ローカル開発環境)の中とかですかね。別にデスクトップのどこかで試してみても大丈夫です。WordPressのテーマ開発とかだったりすると、

[WordPress本体]/wp-content/themes/[開発するテーマのフォルダ]

みたいな感じになると思います。

今回、僕の場合ですが、MAMPにディレクトリを作って進めていきたいと思います。違う場所にディレクトリを作られた方も適宜読み変えてもらえれば大丈夫かと思います。

コマンドラインで作りたい場所へ移動して、

$ cd LocalMAMP
$ mkdir task-runner-test

とかで作っても大丈夫です。当然、右クリックで「新しいフォルダを作る」とかでも同じです。

Node.js が入っているか確かめる

黒い画面を立ち上げます。Macで言うとTerminalですかね。最初から入っているアプリです。アプリケーションフォルダに入っていると思います。

立ち上げたら、このように入力してエンターを押してみてください。

$ node -v
$ npm -v

これらを黒い画面で入力して、何かしらの数字(バージョン番号)が出てきたら、インストールされているということです。

インストールされていないようであれば、インストールしましょう。色々な方法がありますが、とりあえず簡単にインストーラーを使うという方法もあります。試してみましょう。

Node.jsとnpmのインストールで戸惑う方は、色々と検索してみるといいと思います。検索技術って今後も大事になってくると思いますし。

npm init する

プロジェクトディレクトリにいるか確認した上で、ターミナルでこのように入力してみましょう。

$ npm init

色々と聞かれると思いますが、エンター連打して回避しましょう。何を聞かれているか気になる方は調べてみるのも良い勉強になります。(本当は知った上でやるべきなんだろうけどねw)

すると、プロジェクトディレクトリに package.json というファイルが作られているはずです。

開いてみると、

{
 "name": "task-runner-test",
 "version": "1.0.0",
 "description": "",
 "main": "index.js",
 "scripts": {
   "test": "echo \"Error: no test specified\" && exit 1"
 },
 "author": "",
 "license": "ISC"
}

このような内容になっているはずです。この package.json というファイルが npm の設計図(もしくは仕様図)だと思ってください。

これで下準備が完了といったところです。

ここから、必要なnpm packageをインストールしていきながら設定していく部分に進んでいきたいと思います。

試しにSassをCSSにコンパイルしてみる

npmでgulpをインストールする

ここでは、ざっくりと「Gulpって何ができるのよ?」というところを実際に試してみようと思います。

まずは、ターミナルでこのように入力してみましょう。ターミナルでちゃんとプロジェクトディレクトリにいるかどうか($ pwdとかで)確認してくださいね。

$ npm install --save-dev gulp

実行後に、プロジェクトディレクトリに node_modules というディレクトリが作られていたら成功です。

その中を見てみると、たくさんのディレクトリやファイルが存在していることがわかります。とりあえず、ここには npm install したプラグインなどが全て保存されると考えておいてもらえれば大丈夫です。

ちなみに、先ほどのコマンドは、npm を install するというものです。–save-dev はローカルにインストールするためのオプションです。ローカルというのは、プロジェクトディレクトリのことですね。逆に -g をつけるとグローバルにインストールすることになります。

大雑把に使い分け方を言うと、
・ローカルインストールは開発環境で使うもの
・グローバルインストールは本番環境でも利用するもの

と考えて良いかと思っています。

npm ではGulp以外にも様々なライブラリやプラグインを管理することができます。例えば、イメージスライダーでよく利用される slick.js もnpmで管理することができますし、npm に公開されているものは全て管理することができます。

こういったものは、本番環境でも使うのでグローバルインストールで良いでしょう。しかし、Gulpなど開発環境は本番環境では使わないので、基本的には –save-dev(= -D)をつけてインストールで良いと思います。

ファイルの中身を確認してみると、

"devDependencies": {
   "gulp": "^4.0.2"
 }

という記述が見られると思います。devDependenciesにはローカルインストールしたものが記載されます。ここにはまだありませんが、グローバルインストールしたものは、

"Dependencies": {
   .....
}

Dependenciesの中に記載されるようになります。とりあえずうる覚えでいいです。

gulp-sassをインストールする

先ほどのインストールに引き続き、gulp-sass もインストールしておきたいので、

$ npm install --save-dev gulp-sass

も行っておきましょう。上に書いたように、devDependenciesに gulp-sass も追加されていることが確認できるはずです。

gulpfile.js に命令を書いていく

プロジェクトディレクトリー(package.jsonが作られた場所)にgulpfile.js というファイルを作りましょう。JavaScriptを書くので拡張子は .js です。

まず、先ほどインストールした gulp で利用可能な命令を宣言していきます。

const { src, dest, watch, series, parallel } = require( "gulp" );

constというのは、JavaScriptでの変数宣言ですね。

それで、src、dest、watch、series、parallelというものを gulp を使って宣言して、使えるようにしています。

・src →参照元を指定するときに使う
・dest →出力先を指定するときに使う
・watch →ファイルを監視するときに使う
・series(直列処理)とparallel(並列処理) →タスクを設定するときに使う

このような用途です。こんな風に書かれても使ってみないとわかんないですよね。では、実際に使ってみましょう。


先ほどと同じように、gulp-sassも宣言しましょう。

const sass = require( "gulp-sass" );

ここでは、sass という変数名で宣言しました。こうしておくことで、毎回 require( … と書かなくても、sass と書くだけで gulp-sass を利用することができます。

ちなみに、プロジェクトディレクトリ内に、src/cssdist/ ディレクトリも追加して作成しておきます。現状のディレクトリ内構造としては、

- dist/
    css/
- node_moodules/
- src/
    css/
- gulpfile.js
- package.json
- package-lock.json

このようになっています。

SassをCSSにコンパイルする処理を書いてみる

このような感じになります。

const cssSass = () => {
   return src( 'src/css/**/*.scss' )
       .pipe( sass() )
       .pipe( dest( 'dist/css/' ) )
}

順に説明していきますね。

まず、const cssSass でこの処理をひとまとめとして宣言しています。

そして、最初に宣言した src を使って、どこにあるファイルを参照するかを指定しています。ここでは、srcディレクトリを新しく作り、そのなかにcssディレクトリも作り、そこにSassファイル(.scss)を置いておくことを想定して書いています。

そのあとに、 .pipe() で処理をつなげていきます。sass() (先ほどgulp-sassを宣言したもの)でSassからCSSにコンパイルします。

最後に、こちらも最初に宣言した dest を使って、コンパイルしたものをどこに出力させるのかを指定しています。

参照元(src)と出力先(dest)は好きなところに設定してくださいね。

タスクをまとめる

Gulp 4では今まで使えていたtask()処理が非推奨になりました。なので、このような書き方をして処理をまとめていくと後々便利です。

exports.default = series( cssSass );

defaultのタスクを指定しています。seriesの中に先ほど作ったコンパイル部分の処理をまとめたcssSassを指定しています。

現状、中身は、

const { src, dest, watch, series, parallel } = require( "gulp" );
const sass = require( "gulp-sass" );

const cssSass = () => {
   return src( 'src/css/**/*.scss' )
       .pipe( sass() )
       .pipe( dest( 'dist/css/' ) )
}

exports.default = series( cssSass );

こうなっています。

サンプルファイルを作る

ちゃんとコンパイルされるかどうかを確認するために、src/css 内にstyle.scss というファイルを作成して、Sassををこのように記述してみます。記述内容はなんでもOKです。

body {
 & > div {
   background-color: red;
 }
}

用意ができたらテストしてみましょう。

gulp を実行してみる

では、実際にコンパイルされるかどうかテストしてみましょう。

ターミナルで以下のように入力して実行してみましょう。

$ gulp

先ほど、default のタスクを設定しましたが、gulpと実行すると gulp default と実行するのは同じ意味になります。なので、

$ gulp default

と実行しても同じです。defaultは省略することができるということですね。

すると、ターミナルには、

[06:11:42] Using gulpfile ~/LocalMAMP/task-runner-test/gulpfile.js
[06:11:42] Starting 'default'...
[06:11:42] Starting 'cssSass'...
[06:11:42] Finished 'cssSass' after 30 ms
[06:11:42] Finished 'default' after 34 ms

こんな感じで処理の内容が表示されるはずです。上にようにならない場合にはエラー個所が出力されていると思うので、確認してみましょう。

dist/css の中に style.cssが作成されていることが確認できると思います。(ちなみにディレクトリを事前に作っておかなくても自動的に作ってくれます)

出力されたstyle.cssの中身を確認すると、

body > div {
 background-color: red; }

と書いてあることがわかります。ちゃんとコンパイルされていますね!成功です!

Sassの処理を作る

Sassのコンパイルをカスタマイズする

では、先ほどの処理を踏まえてカスタマイズしていきましょう。以下の機能を追加していきたいと思います。

・gulp-sourcemaps(ソースマップ作成)
・gulp-notify(エラー発生時のアラート出力)
・gulp-plumber(エラーが発生しても強制終了させない)
・gulp-postcss(PostCSS利用)
・postcss-cssnext(CSSNext利用)
・gulp-clean-css(圧縮)
・gulp-rename(ファイル名変更)

ソースマップというのは、最終的に圧縮されたCSSを読み込む際にデバッグ時などにあると便利なものです。

gulp-notifyとgulp-plumberはエラー関連の処理を担います。

PostCSSはCSSを生成する上でためになるツールが集まったようなものだと考えてください。その中で、CSS Nextもあるという感じですね。深く知りたい方は検索してみましょう!

gulp-clean-cssはファイルを圧縮する際に使います。gulp-renameは名称変更の際に使います。

では、インストールしていきましょう。

$ npm install --save-dev gulp-sourcemaps gulp-notify gulp-plumber gulp-postcss postcss-cssnext gulp-clean-css gulp-rename

このように一度に複数インストールすることもできるので覚えておくと便利です。

現状はこのようになっています。

{
 "name": "task-runner-test",
 "version": "1.0.0",
 "description": "",
 "main": "index.js",
 "scripts": {
   "test": "echo \"Error: no test specified\" && exit 1"
 },
 "author": "",
 "license": "ISC",
 "devDependencies": {
   "gulp": "^4.0.2",
   "gulp-clean-css": "^4.2.0",
   "gulp-notify": "^3.2.0",
   "gulp-plumber": "^1.2.1",
   "gulp-postcss": "^8.0.0",
   "gulp-rename": "^2.0.0",
   "gulp-sass": "^4.0.2",
   "gulp-sourcemaps": "^2.6.5",
   "postcss-cssnext": "^3.1.0"
 }
}

それぞれの処理を組み立てる

では、gulpfile.js に宣言をして、タスクを組み立てていきましょう。

まず、宣言はこんな感じです。

const plumber = require( "gulp-plumber" );
const notify = require( "gulp-notify" );
const postcss = require( "gulp-postcss" );
const cssnext = require( "postcss-cssnext")
const cleanCSS = require( "gulp-clean-css" );
const rename = require( "gulp-rename" );
const sourcemaps = require( "gulp-sourcemaps" );

処理はこのような感じになります。後ほど解説しますがまずはソースを。

const cssSass = () => {
   return src( 'src/css/**/*.scss' )
       .pipe( sourcemaps.init() ) //gulp-sourcemapsを初期化
       .pipe(
           plumber( //エラーが発生しても処理は止めず
               {
                   errorHandler: notify.onError( 'Error: <%= error.message %>' )
                   // エラー出力設定
               }
           )
       )
       .pipe( sass() )
       .pipe( postcss( [cssnext(browsers)] ) ) //PostCSS
       .pipe( dest( 'dist/css/' ) ) //CSSを出力
       .pipe( cleanCSS() ) //CSSを圧縮
       .pipe(
           rename(
               { extname: '.min.css' } //ファイル名に min.css をつける
           )
       )
       .pipe( sourcemaps.write( '/maps' ) ) //ソースマップを mapsディレクトリに出力
       .pipe( dest( 'dist/css/' ) ) //圧縮したCSSを出力
}

この状態で実行してみましょう。

$ gulp

すると、

browser is not defined

というエラーが表示されるはずです。CSS Nextで参照させているbrowserの設定を用意していませんでした。

const browsers = [
   'last 2 versions',
   '> 5%',
   'ie = 11',
   'not ie <= 10',
   'ios >= 8',
   'and_chr >= 5',
   'Android >= 5',
]

こちらは、どんなブラウザ対応条件にするかというのを設定しておくことで、それに対応したプリフィックスとかをつけてくれるというものです。案件によって変わるかもしれないので、外に出して見やすく管理できるようにconstしています。

こちらをgulpfile.jsに追記して、再度実行してみましょう。

今回はこちらのCSSをコンパイルしてみます。

body {
 & > div {
   display: flex;
   background-color: red;
 }
}

すると、style.cssには

body > div {
 display: -webkit-box;
 display: -ms-flexbox;
 display: flex;
 background-color: red; }
/*# sourceMappingURL=maps/style.css.map */

プリフィックスも付いていることが確認できますね。また、ソースマップも記述も加えられていることがわかりますね。

ちなみに、同じ場所に出力されている style.min.cssの中身を確認すると、

body>div{display:-webkit-box;display:-ms-flexbox;display:flex;background-color:red}
/*# sourceMappingURL=maps/style.min.css.map */

ファイルが圧縮されていることが確認できますね。また、ちゃんとソースマップも付いています。

これでSassからCSSへのコンパイル処理は完了になります。

この時点でのファイルの記述を確認しておきましょう。

const { src, dest, watch, series, parallel } = require( "gulp" );
const sass = require( "gulp-sass" );
const plumber = require( "gulp-plumber" );
const notify = require( "gulp-notify" );
const postcss = require( "gulp-postcss" );
const cssnext = require( "postcss-cssnext")
const cleanCSS = require( "gulp-clean-css" );
const rename = require( "gulp-rename" );
const sourcemaps = require( "gulp-sourcemaps" );
const browsers = [
   'last 2 versions',
   '> 5%',
   'ie = 11',
   'not ie <= 10',
   'ios >= 8',
   'and_chr >= 5',
   'Android >= 5',
]
const cssSass = () => {
   return src( 'src/css/**/*.scss' )
       .pipe( sourcemaps.init() )
       .pipe(
           plumber(
               {
                   errorHandler: notify.onError( 'Error: <%= error.message %>' )
               }
           )
       )
       .pipe( sass() )
       .pipe( postcss( [cssnext(browsers)] ) )
       .pipe( sourcemaps.write( '/maps' ) )
       .pipe( dest( 'dist/css/' ) )
       .pipe( cleanCSS() )
       .pipe(
           rename(
               { extname: '.min.css' }
           )
       )
       .pipe( sourcemaps.write( '/maps' ) )
       .pipe( dest( 'dist/css/' ) )
}
exports.default = series( cssSass );
{
 "name": "task-runner-test",
 "version": "1.0.0",
 "description": "",
 "main": "index.js",
 "scripts": {
   "test": "echo \"Error: no test specified\" && exit 1"
 },
 "author": "",
 "license": "ISC",
 "devDependencies": {
   "gulp": "^4.0.2",
   "gulp-clean-css": "^4.2.0",
   "gulp-notify": "^3.2.0",
   "gulp-plumber": "^1.2.1",
   "gulp-postcss": "^8.0.0",
   "gulp-rename": "^2.0.0",
   "gulp-sass": "^4.0.2",
   "gulp-sourcemaps": "^2.6.5",
   "postcss-cssnext": "^3.1.0"
 }
}

参照元と出力先の管理方法変更

これで一通り、SassからCSSヘコンパイルする処理を作り終わったわけですが、今後はJavaScriptの処理と画像の処理を作っていきます。

また、他にも必要に応じて様々な処理をさせることができるのですが、場合によっては参照元(src())と出力先(dest())を同じ場所で管理した方が楽ということもあります。(個人的には同じ場所でひとまとめに管理できた方が楽と感じるタイプ)

ですので、その方法を紹介します。まず先にコードを掲載しますね。

参照元をまとめたものがこちら。

const srcPath = {
   css: 'src/css/**.scss',
   js: 'src/js/*.js',
   img: 'src/images/**/*',
   html: './**/*.html',
}

出力先をまとめたものがこちら。

const destPath = {
   css: 'dist/css/',
   js: 'dist/js/',
   img: 'dist/images/'
}

どうやって使うか、なんですが、先ほど作った処理をこちらを活用して必要箇所を置き換えてみると、

const cssSass = () => {
   return src( srcPath.css ) //←ここ
       .pipe( sourcemaps.init() )
       .pipe(
           plumber(
               {
                   errorHandler: notify.onError( 'Error: <%= error.message %>' )
               }
           )
       )
       .pipe( sass() )
       .pipe( postcss( [cssnext(browsers)] ) )
       .pipe( sourcemaps.write( '/maps' ) )
       .pipe( dest( destPath.css ) ) //←ここ
       .pipe( cleanCSS() )
       .pipe(
           rename(
               { extname: '.min.css' }
           )
       )
       .pipe( sourcemaps.write( '/maps' ) )
       .pipe( dest( destPath.css ) ) //←ここ
}

このような感じで使うことができます。便利でしょ?書き出し先を同じタスクで複数箇所同じものを書く場合があるときには、編集元が1つにできるので便利になるかと思いますがいかがでしょうか?

JavaScriptをBabelって圧縮する

JavaScriptをBabelを使ってコンパイルします。同じようにWeb Packなどを利用することもできると思いますので、よかったらチャレンジしてみてください。

Babelって何?という方も多いかもしれませんが、誤解を恐れず簡単に説明をすると「新しい記述方法で書いたJavaScriptを現在ブラウザが正常に判断してくれる書き方へ変換してくれる」ものと理解してもらって大丈夫かなと思っています。

まぁ、ざっくり良しなにやってくれるものだと解釈しておきましょう。

こちらで新しく使うものとしては、gulp-babelgulp-uglify です。

前者は先ほどお話したBabelってくれるもの。gulp-uglifyはJavaScriptを圧縮してくれるものです。先ほどのCSSの処理でいうと gulp-clean-css みたいなものですね。

Babelを利用するということで、一緒に @babel/core と @babel/preset-envもインストールします。

これらをまずはインストールしましょう。

$ npm install --save-dev @babel/core @babel/preset-env gulp-babel gulp-uglify

そして、宣言も追加しておきます。

const babel = require( "gulp-babel" );
const uglify = require( "gulp-uglify" );

では、処理を作ってみましょう。先ほどのSassをCSSにコンパイルする処理と同じような感じで書いていくことができます。

const jsBabel = () => {
   return src( srcPath.js )
       .pipe(
           plumber(
               {
                   errorHandler: notify.onError( 'Error: <%= error.message %>' )
               }
           )
       )
       .pipe( babel( {
           presets: [ '@babel/preset-env' ]
       } ) )
       .pipe( dest( destPath.js ) )
       .pipe( uglify() )
       .pipe(
           rename(
               { extname: '.min.js' }
           )
       )
       .pipe( dest( destPath.js ) )
}

exports.default = series( cssSass, jsBabel );

先ほど入れた @babel/preset-env はBabelの設定内容みたいなもんですな。他は先ほどのCSSとあまり変わりない感じですね。

最後のexports.default の部分に、作ったjsBabelというタスクをCSSタスクと一緒に併記しています。こうしておくことで、黒い画面で

$ gulp

を実行した際に、cssSassを実行した後に jsBabelが実行されるという形(直列)になります。

現在のソースコードは、

{
 "name": "task-runner-test",
 "version": "1.0.0",
 "description": "",
 "main": "index.js",
 "scripts": {
   "test": "echo \"Error: no test specified\" && exit 1"
 },
 "author": "",
 "license": "ISC",
 "devDependencies": {
   "@babel/core": "^7.8.7",
   "@babel/preset-env": "^7.8.7",
   "gulp": "^4.0.2",
   "gulp-babel": "^8.0.0",
   "gulp-clean-css": "^4.2.0",
   "gulp-notify": "^3.2.0",
   "gulp-plumber": "^1.2.1",
   "gulp-postcss": "^8.0.0",
   "gulp-rename": "^2.0.0",
   "gulp-sass": "^4.0.2",
   "gulp-sourcemaps": "^2.6.5",
   "gulp-uglify": "^3.0.2",
   "postcss-cssnext": "^3.1.0"
 }
}
const { src, dest, watch, series, parallel } = require( "gulp" );
const sass = require( "gulp-sass" );
const plumber = require( "gulp-plumber" );
const notify = require( "gulp-notify" );
const postcss = require( "gulp-postcss" );
const cssnext = require( "postcss-cssnext")
const cleanCSS = require( "gulp-clean-css" );
const rename = require( "gulp-rename" );
const sourcemaps = require( "gulp-sourcemaps" );
const babel = require( "gulp-babel" );
const uglify = require( "gulp-uglify" );

const srcPath = {
   css: 'src/css/**.scss',
   js: 'src/js/*.js',
   img: 'src/images/**/*',
   html: './**/*.html',
}

const destPath = {
   css: 'dist/css/',
   js: 'dist/js/',
   img: 'dist/images/'
}

const browsers = [
   'last 2 versions',
   '> 5%',
   'ie = 11',
   'not ie <= 10',
   'ios >= 8',
   'and_chr >= 5',
   'Android >= 5',
]

const cssSass = () => {
   return src( srcPath.css )
       .pipe( sourcemaps.init() )
       .pipe(
           plumber(
               {
                   errorHandler: notify.onError( 'Error: <%= error.message %>' )
               }
           )
       )
       .pipe( sass() )
       .pipe( postcss( [cssnext(browsers)] ) )
       .pipe( sourcemaps.write( '/maps' ) )
       .pipe( dest( destPath.css ) )
       .pipe( cleanCSS() )
       .pipe(
           rename(
               { extname: '.min.css' }
           )
       )
       .pipe( sourcemaps.write( '/maps' ) )
       .pipe( dest( destPath.css ) )
}

const jsBabel = () => {
   return src( srcPath.js )
       .pipe(
           plumber(
               {
                   errorHandler: notify.onError( 'Error: <%= error.message %>' )
               }
           )
       )
       .pipe( babel( {
           presets: [ '@babel/preset-env' ]
       } ) )
       .pipe( dest( destPath.js ) )
       .pipe( uglify() )
       .pipe(
           rename(
               { extname: '.min.js' }
           )
       )
       .pipe( dest( destPath.js ) )
}

exports.default = series( cssSass, jsBabel );

画像の圧縮処理を作る

では、同じように画像データ(JPEG/PNG/SVG)を圧縮してもらう処理を作っていきましょう。

ここでは、gulp-imagemin を使っていきます。そして、JPEG・PNG・SVGそれぞれを圧縮するために、imagemin-mozjpeg・imagemin-pngquant・imagemin-svgo を使っていきます。

まずはインストールしていきましょう。

$ npm install --save-dev gulp-imagemin imagemin-mozjpeg imagemin-pngquant imagemin-svgo

そして、gulpfile.js に宣言してきましょう。

const imagemin = require( "gulp-imagemin" );
const imageminMozjpeg = require( "imagemin-mozjpeg" );
const imageminPngquant = require( "imagemin-pngquant" );
const imageminSvgo = require( "imagemin-svgo" );

では、準備が整ったので処理を書いていこうと思います。。

const imgImagemin = () => {
   return src(srcPath.img)
       .pipe(
           imagemin(
               [
                   imageminMozjpeg({
                       quality: 80
                   }),
                   imageminPngquant(),
                   imageminSvgo({
                       plugins: [
                           {
                               removeViewbox: false
                           }
                       ]
                   })
               ],
               {
                   verbose: true
               }
           )
       )
       .pipe( dest( destPath.img ) )
}

exports.default = series( cssSass, jsBabel, imgImagemin );​

各種圧縮度合いなどについては、それぞれ設定を変更することができるので、適宜必要に応じて変更していけば良いと思います。こちらは僕はデフォルトで使っている設定になります。

JavaScriptの処理と同じように、最後に exports.default に 作ったばかりのimgImageminを追記して、順次処理を実行します。

何かしらサンプルになりそうな画像を src/images ディレクトリ(なければ作る)に入れてみて実行してみましょう。

今回、僕の方ではこのようなサンプルデータを用意してテストしてみました。

・CoderDojoGifu_Mark_sq1200.png(50.51kb)
・Gulp.js_Logo.svg(13.42kb)
・mountain-photography-2444429.jpg(2.37MB)

$ gulp default
[08:27:15] Using gulpfile ~/LocalMAMP/task-runner-test/gulpfile.js
[08:27:15] Starting 'default'...
[08:27:15] Starting 'cssSass'...
[08:27:16] Finished 'cssSass' after 1.53 s
[08:27:16] Starting 'jsBabel'...
[08:27:17] Finished 'jsBabel' after 568 ms
[08:27:17] Starting 'imgImagemin'...
[08:27:17] gulp-imagemin: ✔ Gulp.js_Logo.svg (saved 5.17 kB - 38.5%)
[08:27:17] gulp-imagemin: ✔ CoderDojoGifu_Mark_sq1200.png (saved 25.2 kB - 49.9%)
[08:27:20] gulp-imagemin: ✔ mountain-photography-2444429.jpg (saved 1.5 MB - 63.4%)
[08:27:20] gulp-imagemin: Minified 3 images (saved 1.53 MB - 63%)
[08:27:20] Finished 'imgImagemin' after 2.58 s
[08:27:20] Finished 'default' after 4.68 s

コマンドラインにも結果が表示されていますが、

・CoderDojoGifu_Mark_sq1200.png(50.51kb → 25.3kb)
・Gulp.js_Logo.svg(13.42kb → 8.25kb)
・mountain-photography-2444429.jpg(2.37MB → 867.17kb)

このようにデータを圧縮させることができました。これで画像圧縮タスクもひとまず完成です。

現状のソースコードは、

{
 "name": "task-runner-test",
 "version": "1.0.0",
 "description": "",
 "main": "index.js",
 "scripts": {
   "test": "echo \"Error: no test specified\" && exit 1"
 },
 "author": "",
 "license": "ISC",
 "devDependencies": {
   "@babel/core": "^7.8.7",
   "@babel/preset-env": "^7.8.7",
   "gulp": "^4.0.2",
   "gulp-babel": "^8.0.0",
   "gulp-clean-css": "^4.2.0",
   "gulp-imagemin": "^7.1.0",
   "gulp-notify": "^3.2.0",
   "gulp-plumber": "^1.2.1",
   "gulp-postcss": "^8.0.0",
   "gulp-rename": "^2.0.0",
   "gulp-sass": "^4.0.2",
   "gulp-sourcemaps": "^2.6.5",
   "gulp-uglify": "^3.0.2",
   "imagemin-mozjpeg": "^8.0.0",
   "imagemin-pngquant": "^8.0.0",
   "imagemin-svgo": "^7.1.0",
   "postcss-cssnext": "^3.1.0"
 }
}
const { src, dest, watch, series, parallel } = require( "gulp" );
const sass = require( "gulp-sass" );
const plumber = require( "gulp-plumber" );
const notify = require( "gulp-notify" );
const postcss = require( "gulp-postcss" );
const cssnext = require( "postcss-cssnext")
const cleanCSS = require( "gulp-clean-css" );
const rename = require( "gulp-rename" );
const sourcemaps = require( "gulp-sourcemaps" );
const babel = require( "gulp-babel" );
const uglify = require( "gulp-uglify" );
const imagemin = require( "gulp-imagemin" );
const imageminMozjpeg = require( "imagemin-mozjpeg" );
const imageminPngquant = require( "imagemin-pngquant" );
const imageminSvgo = require( "imagemin-svgo" );
const srcPath = {
   css: 'src/css/**.scss',
   js: 'src/js/*.js',
   img: 'src/images/**/*',
   html: './**/*.html',
}
const destPath = {
   css: 'dist/css/',
   js: 'dist/js/',
   img: 'dist/images/'
}
const browsers = [
   'last 2 versions',
   '> 5%',
   'ie = 11',
   'not ie <= 10',
   'ios >= 8',
   'and_chr >= 5',
   'Android >= 5',
]
const cssSass = () => {
   return src( srcPath.css )
       .pipe( sourcemaps.init() )
       .pipe(
           plumber(
               {
                   errorHandler: notify.onError( 'Error: <%= error.message %>' )
               }
           )
       )
       .pipe( sass() )
       .pipe( postcss( [cssnext(browsers)] ) )
       .pipe( sourcemaps.write( '/maps' ) )
       .pipe( dest( destPath.css ) )
       .pipe( cleanCSS() )
       .pipe(
           rename(
               { extname: '.min.css' }
           )
       )
       .pipe( sourcemaps.write( '/maps' ) )
       .pipe( dest( destPath.css ) )
}
const jsBabel = () => {
   return src( srcPath.js )
       .pipe(
           plumber(
               {
                   errorHandler: notify.onError( 'Error: <%= error.message %>' )
               }
           )
       )
       .pipe( babel( {
           presets: [ '@babel/preset-env' ]
       } ) )
       .pipe( dest( destPath.js ) )
       .pipe( uglify() )
       .pipe(
           rename(
               { extname: '.min.js' }
           )
       )
       .pipe( dest( destPath.js ) )
}
const imgImagemin = () => {
   return src( srcPath.img )
       .pipe(
           imagemin(
               [
                   imageminMozjpeg({
                       quality: 80
                   }),
                   imageminPngquant(),
                   imageminSvgo({
                       plugins: [
                           {
                               removeViewbox: false
                           }
                       ]
                   })
               ],
               {
                   verbose: true
               }
           )
       )
       .pipe( dest( destPath.img ) )
}
exports.default = series( cssSass, jsBabel, imgImagemin );

こうなっています。付いてきてくれているかな??

ファイルの自動監視と自動ブラウザリロードの仕組みを作る

最後になりますが、ファイルの自動監視とそれに伴うブラウザの自動リロードの仕組みを作っていきます。

今までは、処理を実行するために毎度コマンドを入力して実行してきました。実際の制作作業の流れを想像すると、コマンドを実行してソースが出来上がったらブラウザをリロードさせて(F5 or ⌘+Rとか)表示を確認するという感じになります。

これって、めっちゃ面倒じゃないですか?僕はもうこれすらできない体になってしまっています…(慣れって怖い…

ここからは、CSS(Sass)やJavaScript、画像データなどに動き(保存やファイル変更)があった場合に、自動でタスクを実行させる仕組みを作っていきます。

browser-syncをインストール

browser-syncを使って処理を作っていきます。まずはインストールしましょう。

$ npm install --save-dev browser-sync

インストールが完了したら、gulpfile.js に宣言します。

const browserSync = require( "browser-sync" );

browser-syncの設定

まずは、browser-syncの設定をします。

const browserSyncFunc = () => {
   browserSync.init( browserSyncOption );
}

ここで指定している browserSyncOption を別途用意します。

const browserSyncOption = {
   proxy: 'localhost:8888/task-runner-test',
   open: true,
   watchOptions: {
       debounceDelay: 1000
   },
   reloadOnRestart: true,
}

こんな感じですね。proxy はそれぞれ環境で変わってきます。ここでは、冒頭でも紹介しているようにMAMPを使っているので、そのプロジェクトディレクトリを指定しています。

テストで実行するためにタスクを作ります。

exports.browser = series( browserSyncFunc );

後々のテストのためにも1つHTMLファイルをディレクトリルート(gulpfile.jsが置いてあるところ)に作っておきましょう。サンプルとして置いておきますね。

<!doctype html>
<html lang="ja">
<head>
	<meta charset="UTF-8">
	<meta name="viewport"
		  content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
	<meta http-equiv="X-UA-Compatible" content="ie=edge">
	<title>npm scriptとGulp4で簡単なWeb制作向けタスクランナーを作る方法 Demo</title>
	<link rel="stylesheet" href="./dist/css/style.css">
</head>
<body>
<h1>npm scriptとGulp4で簡単なWeb制作向けタスクランナーを作る方法 Demo</h1>
<p>これは十月よほどこういう使用家についてのの以上がなったた。</p>
<p>けっしてその間の講演国はもうその談判たたでもでするばいるたがも煩悶尊ぶでしょですが、あいにくには這入っただたで。先生に連れます訳はとうとう事実をもしですでた。</p>
<p>そのうち岩崎さんを講演文章どう挨拶をなるない世間どんな生徒私か合点をとかいう今講義たないたたて、その事実も何か辺国家をなれると、大森さんののを気の私がもうお焦燥というがあなた私立がご拡張を聴いようにとにかく小らくが立ったたけれども、まあいくら参考のさたばいるた気をなるですまし。</p>
</body>
</html>

では、テストしてみましょう。

$ gulp browser

すると、このような感じでタスクが走ると思います。

[08:48:18] Using gulpfile ~/LocalMAMP/task-runner-test/gulpfile.js
[08:48:18] Starting 'browser'...
[08:48:18] Starting 'browserSyncFunc'...
[Browsersync] Proxying: http://localhost:8888
[Browsersync] Access URLs:
-------------------------------------------------------
      Local: http://localhost:3000/task-runner-test
   External: http://192.168.10.16:3000/task-runner-test
-------------------------------------------------------
         UI: http://localhost:3001
UI External: http://localhost:3001
-------------------------------------------------------

それと同時にブラウザが自動で立ち上がって(すでに立ち上がっている場合には新規タブで開くかな?)このようなページ(先ほど用意したHTML)が表示されていることが確認できると思います。

npm_scriptとGulp4で簡単なWeb制作向けタスクランナーを作る方法_Demo

サーバーを終了する際には、control + Cで終了できます。Windowsだと何になるんだろうか…

では、こちらを活用して自動監視とブラウザの自動リロード部分を作っていきましょう。

リロードの処理を作る

では、リロードの処理を作りましょう。こんな感じになります。

const browserSyncReload = ( done ) => {
   browserSync.reload();
   done();
}

こちらのタスクを実行すると、ブラウザがリロードされます。

自動監視の処理を作る

冒頭の導入部分で watch というのを使えるようにしていたと思います。(忘れちゃった方は見直してみてください)

そちらを利用して、ファイルの動きの変化をチェックさせます。

const watchFiles = () => {
   watch( srcPath.css, series( cssSass, browserSyncReload ) )
   watch( srcPath.js, series( jsBabel, browserSyncReload ) )
   watch( srcPath.img, series( imgImagemin, browserSyncReload ) )
   watch( srcPath.html, series( browserSyncReload ) )
}

こんな感じになります。

watch( [監視する場所], [動きがあったら実行する処理] )

という感じで書くことができます。ここでもまとめておいた参照元が活用されています。便利でしょ?

そして、seriesは直列処理なので、

series( [処理1], [処理2], .... )

[処理1]を実行してから[処理2]を実行…という形になります。同時進行しないってことですね。

ファイルの自動監視においては、【ファイルに変更があった!】→【コンパイルなどの処理】→【ブラウザのリロード】という流れにしないと、ソースコードが出来上がっていないのにブラウザリロードしても意味ないですからねー。なのでこのように書いています。

HTMLに関しては、

watch( srcPath.html, series( browserSyncReload ) )

単純に、HTMLファイルに変更があったらブラウザリロードしているだけです。

例えばEJSなどのテンプレートエンジンを利用する場合には、リロードの前に処理を入れる感じになると思います。(今回は取り扱っていません)

最後に、この watchFiles と browserSyncFunc(browser syncを立ち上げる処理)を exports.default に入れてみましょう。

exports.default = series( series( cssSass, jsBabel, imgImagemin ), parallel( watchFiles, browserSyncFunc ) );

初めて parallel が出てきましたが、series の中に コード生成系処理のseries と並列処理のparallelwatchFiles(ファイルの自動監視とリロード)と browserSyncFunc(browser-syncの立ち上げ)を行うという形にしてあります。ちょっとややこしいですね。色々と自分でいじって見ると良い勉強になりますよ!

ちゃんと動くかテストしてみる

最後のテストになります。

$ gulp

Sassファイルなどを編集して、自動でブラウザがリロードされるかどうかを確認してみましょう。うまく動けば完成です。

なかなか快適な作業環境になりませんか?僕は最低限これくらい整ってないと何もできない身体になってしまいました…(楽するとダメね…

ここまでのソースコードはこのようになります。

{
 "name": "task-runner-test",
 "version": "1.0.0",
 "description": "",
 "main": "index.js",
 "scripts": {
   "test": "echo \"Error: no test specified\" && exit 1"
 },
 "author": "",
 "license": "ISC",
 "devDependencies": {
   "@babel/core": "^7.8.7",
   "@babel/preset-env": "^7.8.7",
   "browser-sync": "^2.26.7",
   "gulp": "^4.0.2",
   "gulp-babel": "^8.0.0",
   "gulp-clean-css": "^4.2.0",
   "gulp-imagemin": "^7.1.0",
   "gulp-notify": "^3.2.0",
   "gulp-plumber": "^1.2.1",
   "gulp-postcss": "^8.0.0",
   "gulp-rename": "^2.0.0",
   "gulp-sass": "^4.0.2",
   "gulp-sourcemaps": "^2.6.5",
   "gulp-uglify": "^3.0.2",
   "imagemin-mozjpeg": "^8.0.0",
   "imagemin-pngquant": "^8.0.0",
   "imagemin-svgo": "^7.1.0",
   "postcss-cssnext": "^3.1.0"
 }
}
const { src, dest, watch, series, parallel } = require( "gulp" );
const sass = require( "gulp-sass" );
const plumber = require( "gulp-plumber" );
const notify = require( "gulp-notify" );
const postcss = require( "gulp-postcss" );
const cssnext = require( "postcss-cssnext")
const cleanCSS = require( "gulp-clean-css" );
const rename = require( "gulp-rename" );
const sourcemaps = require( "gulp-sourcemaps" );
const babel = require( "gulp-babel" );
const uglify = require( "gulp-uglify" );
const imagemin = require( "gulp-imagemin" );
const imageminMozjpeg = require( "imagemin-mozjpeg" );
const imageminPngquant = require( "imagemin-pngquant" );
const imageminSvgo = require( "imagemin-svgo" );
const browserSync = require( "browser-sync" );
const srcPath = {
   css: 'src/css/**.scss',
   js: 'src/js/*.js',
   img: 'src/images/**/*',
   html: './**/*.html',
}
const destPath = {
   css: 'dist/css/',
   js: 'dist/js/',
   img: 'dist/images/'
}
const browsers = [
   'last 2 versions',
   '> 5%',
   'ie = 11',
   'not ie <= 10',
   'ios >= 8',
   'and_chr >= 5',
   'Android >= 5',
]
const browserSyncOption = {
   proxy: 'localhost:8888/task-runner-test',
   open: true,
   watchOptions: {
       debounceDelay: 1000
   },
   reloadOnRestart: true,
}
const cssSass = () => {
   return src( srcPath.css )
       .pipe( sourcemaps.init() )
       .pipe(
           plumber(
               {
                   errorHandler: notify.onError( 'Error: <%= error.message %>' )
               }
           )
       )
       .pipe( sass() )
       .pipe( postcss( [cssnext(browsers)] ) )
       .pipe( sourcemaps.write( '/maps' ) )
       .pipe( dest( destPath.css ) )
       .pipe( cleanCSS() )
       .pipe(
           rename(
               { extname: '.min.css' }
           )
       )
       .pipe( sourcemaps.write( '/maps' ) )
       .pipe( dest( destPath.css ) )
}
const jsBabel = () => {
   return src( srcPath.js )
       .pipe(
           plumber(
               {
                   errorHandler: notify.onError( 'Error: <%= error.message %>' )
               }
           )
       )
       .pipe( babel( {
           presets: [ '@babel/preset-env' ]
       } ) )
       .pipe( dest( destPath.js ) )
       .pipe( uglify() )
       .pipe(
           rename(
               { extname: '.min.js' }
           )
       )
       .pipe( dest( destPath.js ) )
}
const imgImagemin = () => {
   return src( srcPath.img )
       .pipe(
           imagemin(
               [
                   imageminMozjpeg({
                       quality: 80
                   }),
                   imageminPngquant(),
                   imageminSvgo({
                       plugins: [
                           {
                               removeViewbox: false
                           }
                       ]
                   })
               ],
               {
                   verbose: true
               }
           )
       )
       .pipe( dest( destPath.img ) )
}
const browserSyncFunc = () => {
   browserSync.init( browserSyncOption );
}
const browserSyncReload = ( done ) => {
   browserSync.reload();
   done();
}
const watchFiles = () => {
   watch( srcPath.css, series( cssSass, browserSyncReload ) )
   watch( srcPath.js, series( jsBabel, browserSyncReload ) )
   watch( srcPath.img, series( imgImagemin, browserSyncReload ) )
   watch( srcPath.html, series( browserSyncReload ) )
}
exports.default = series( series( cssSass, jsBabel, imgImagemin ), parallel( watchFiles, browserSyncFunc ) );

npm scriptからGulpを動かす

では、ここからはnpm scriptと今まで作ったGulpのタスクランナーを紐づけていきます。

といっても、簡単です。

npm script は package.json の scripts に書きます。例えば、現状だと package.json が生成された時のままになっているはずなので、

"scripts": {
   "test": "echo \"Error: no test specified\" && exit 1"
 },

こう書いてあると思います。これを実行するためには、ターミナル等で、

$ npm run test

とやれば実行できます。npm scriptの動かし方はこんな感じです。

過去にnpm scriptだけでタスクランナー的なものを作っていたこともあるので、詳しいことに興味がある方は僕の過去記事を読んでいただければ参考になるかもしれません。

なぜ、npm scriptで作ったタスクランナーからGulで作られたものに切り替えたかというと、単純に処理のスピードでの違いです。

npm scriptはやはりブラウザリロードまでのゴールに時間がかかりました。そのもっさり感に耐えられなくなったという個人的な感覚の理由からです。

しかし、今でもコンパイルやジェネレート系のタスク以外は基本的にnpm scriptで実行させています。それくらい扱いも楽です。なので、npm scriptアンチというわけではありません。


話がそれましたが、次にnpm scriptから先ほど作ったタスクランナーを動かしてみましょう。

package.json の scripts に処理を書きます。先ほどの test の処理は使わないので削除してしまって構いません。

"scripts": {
   "start": "gulp"
 }

こう記述をしました。そして、ターミナル等で

$ npm run start

と入力して実行してみましょう。すると、先ほど作ったタスクランナーは動くと思います。

これでnpm script から Gulpのタスクランナーを動かすことができました。

では、どんなメリットがあるの?という点なんですが、個人差は大きいかもしれませんが、僕としては色々な処理をまとめて package.json で管理できるという点が大きいかと思います。

先ほど、start というタスクを npm script の方に作りましたが、次に build というタスクを作ってみたいと思います。

build の流れとしては、
・distディレクトリの中身を全てクリーンにする
・サーバー立ち上げはせず、必要なファイルを全てコンパイルもしくは画像圧縮し直す

このような流れになると思います。(細かいこというともっと色々とやりたくなるところはあると思いますが…汗

Gulp に build タスクを作る

browser-sync部分の処理がないコンパイル&画像圧縮だけのタスクを作ります。gulpfile.js の最後にこのようなタスクを作成してみました。

exports.build = series( cssSass, jsBabel, imgImagemin );

先ほど作成した exports.default のタスクと見比べてみるとわかりやすいのですが、コンパイルと画像圧縮部分の直列処理だけを抜き出したものです。

こちらは、

$ gulp build

することでテストすることができます。

では、npm script で「distディレクトリの中身を全てクリーンにする」処理を作ってみましょう。(別にGulpでやってもいいんですけどね

ディレクトリをクリーン(削除)する処理を作る

ここでは、rimraf を入れてみようと思います。

$ npm install --save-dev rimraf

そして、package.json の scripts にタスクを2つ追加します。distディレクトリをクリーンにするタスクと、それを実行したあとにコンパイル&画像圧縮処理を実行するタスクです。

"scripts": {
   "clean": "rimraf dist/*",
   "start": "gulp",
   "build": "npm run clean && gulp build"
 }

まず、clean では、先ほど入れたrimrafdist/*(distの中にあるもの全部)の中を削除させています。

そして、buildcleannpm scriptで実行させた後、&& で繋いで gulp build してコンパイル&画像圧縮を実行しています。

こうすることで、制作段階で蓄積されたファイル(もう使わない可能性のあるファイルも存在するかもしれないので)を一旦削除して、今一度最新の環境でコンパイル&画像圧縮したものを生成するという流れになります。


正直な話、

$ gulp

$ npm run start

は同じ処理を実行することになります。

しかし、今後他のライブラリなどを使うこともあるでしょうし、npmで何かライブラリ(slick.jsとか)を使う際にも、node_modulesに直接アクセスさせる訳にもいかないと思うので、然るべき場所にパッケージをコピーさせることもあるでしょう。

そういった処理をpackage.json(npm scriptを使って)にまとめておくことができる点を個人的には気に入っています。

個人的にはWordPressに関する開発を行なうことが多いのですが、その際にもかなりの部分を自動化もしくはコマンドを実行するだけで処理をさせることができるようにしています。

そうすることで、細かいことではあるのですが時短にも繋がります。それは、結果的に自分の作業単価をあげることにも繋がる可能性が高いです。

そういった考え方から、個人的な好みは皆さんあるとは思いますが、僕はnpm scriptで全てまとめた上で、タスクランナーはGulpを利用するという形に今の所落ち着いています。

まとめ

順をおって説明してきましたが、この記事では「導入」を優先しているので「理解」という面では不十分な解説となっています。

まずは触ってみてください。すると、「ここはもう少しこうできないかなぁ?」とか「この方が便利じゃないかなぁ?」というアイデアがどんどん出てくると思います。

そこから学習を進めて行かれれば良いと思います。

これまでにも参照記事という形で、様々なリファレンスページへのリンクを掲載してあります。

それらの記事が掲載されているウェブサイトの他のページなどを参照していただければ、ほぼ必ずと言っていいほどやりたいことの答えは掲載されていると思います。

どんどん調べて、どんどん試してみてください。

この記事が、皆さんのnpmやGulp、JavaScriptなどの学習を進められる足場になれば幸いです。どんどん踏み台に使ってください。

最終的なコード

{
 "name": "task-runner-test",
 "version": "1.0.0",
 "description": "",
 "main": "index.js",
 "author": "",
 "license": "ISC",
 "devDependencies": {
   "@babel/core": "^7.8.7",
   "@babel/preset-env": "^7.8.7",
   "browser-sync": "^2.26.7",
   "gulp": "^4.0.2",
   "gulp-babel": "^8.0.0",
   "gulp-clean-css": "^4.2.0",
   "gulp-imagemin": "^7.1.0",
   "gulp-notify": "^3.2.0",
   "gulp-plumber": "^1.2.1",
   "gulp-postcss": "^8.0.0",
   "gulp-rename": "^2.0.0",
   "gulp-sass": "^4.0.2",
   "gulp-sourcemaps": "^2.6.5",
   "gulp-uglify": "^3.0.2",
   "imagemin-mozjpeg": "^8.0.0",
   "imagemin-pngquant": "^8.0.0",
   "imagemin-svgo": "^7.1.0",
   "postcss-cssnext": "^3.1.0",
   "rimraf": "^3.0.2"
 },
 "scripts": {
   "clean": "rimraf dist/*",
   "start": "gulp",
   "build": "npm run clean && gulp build"
 }
}
const { src, dest, watch, series, parallel } = require( "gulp" );
const sass = require( "gulp-sass" );
const plumber = require( "gulp-plumber" );
const notify = require( "gulp-notify" );
const postcss = require( "gulp-postcss" );
const cssnext = require( "postcss-cssnext")
const cleanCSS = require( "gulp-clean-css" );
const rename = require( "gulp-rename" );
const sourcemaps = require( "gulp-sourcemaps" );
const babel = require( "gulp-babel" );
const uglify = require( "gulp-uglify" );
const imagemin = require( "gulp-imagemin" );
const imageminMozjpeg = require( "imagemin-mozjpeg" );
const imageminPngquant = require( "imagemin-pngquant" );
const imageminSvgo = require( "imagemin-svgo" );
const browserSync = require( "browser-sync" );
const srcPath = {
   css: 'src/css/**.scss',
   js: 'src/js/*.js',
   img: 'src/images/**/*',
   html: './**/*.html',
}
const destPath = {
   css: 'dist/css/',
   js: 'dist/js/',
   img: 'dist/images/'
}
const browsers = [
   'last 2 versions',
   '> 5%',
   'ie = 11',
   'not ie <= 10',
   'ios >= 8',
   'and_chr >= 5',
   'Android >= 5',
]
const browserSyncOption = {
   proxy: 'localhost:8888/task-runner-test',
   open: true,
   watchOptions: {
       debounceDelay: 1000
   },
   reloadOnRestart: true,
}
const cssSass = () => {
   return src( srcPath.css )
       .pipe( sourcemaps.init() )
       .pipe(
           plumber(
               {
                   errorHandler: notify.onError( 'Error: <%= error.message %>' )
               }
           )
       )
       .pipe( sass() )
       .pipe( postcss( [cssnext(browsers)] ) )
       .pipe( sourcemaps.write( '/maps' ) )
       .pipe( dest( destPath.css ) )
       .pipe( cleanCSS() )
       .pipe(
           rename(
               { extname: '.min.css' }
           )
       )
       .pipe( sourcemaps.write( '/maps' ) )
       .pipe( dest( destPath.css ) )
}
const jsBabel = () => {
   return src( srcPath.js )
       .pipe(
           plumber(
               {
                   errorHandler: notify.onError( 'Error: <%= error.message %>' )
               }
           )
       )
       .pipe( babel( {
           presets: [ '@babel/preset-env' ]
       } ) )
       .pipe( dest( destPath.js ) )
       .pipe( uglify() )
       .pipe(
           rename(
               { extname: '.min.js' }
           )
       )
       .pipe( dest( destPath.js ) )
}
const imgImagemin = () => {
   return src( srcPath.img )
       .pipe(
           imagemin(
               [
                   imageminMozjpeg({
                       quality: 80
                   }),
                   imageminPngquant(),
                   imageminSvgo({
                       plugins: [
                           {
                               removeViewbox: false
                           }
                       ]
                   })
               ],
               {
                   verbose: true
               }
           )
       )
       .pipe( dest( destPath.img ) )
}
const browserSyncFunc = () => {
   browserSync.init( browserSyncOption );
}
const browserSyncReload = ( done ) => {
   browserSync.reload();
   done();
}
const watchFiles = () => {
   watch( srcPath.css, series( cssSass, browserSyncReload ) )
   watch( srcPath.js, series( jsBabel, browserSyncReload ) )
   watch( srcPath.img, series( imgImagemin, browserSyncReload ) )
   watch( srcPath.html, series( browserSyncReload ) )
}
exports.default = series( series( cssSass, jsBabel, imgImagemin ), parallel( watchFiles, browserSyncFunc ) );
exports.build = series( cssSass, jsBabel, imgImagemin );

制作過程をYoutubeライブした動画アーカイブ

WordPress 6.5.x 対応版を出版しました

WordPress デフォルトテーマ Twenty Twenty-Four を使って、シンプルなブログやポートフォリオサイト、そしてコーポレートサイトを作りながら、ブロックテーマやサイトエディターの基本を理解することができます。