gulp+Handlebars.jsで静的htmlを作る

※（追記） 2017年に投稿した記事なので 「当時はコレで動きました。」 という話です。
※ これから`gulp`の本格導入を考えてる場合は、アテにしない方が良いかもです。
※（追記） gulp4.0.2でタスクを書く場合は、こちらもご参照下さい。
https://qiita.com/DaisukeNishi/items/625419cf6ad36993a7f2

#  はじめに
IDOMアドベントカレンダー（12日目）に投稿しています。
https://qiita.com/advent-calendar/2017/idom-engineer
初心者向け、gulp+Handlebars.jsを使って効率的に静的なhtmlを作る方法を書きます。
序盤の初期設定などは、既にnpmをお使いの皆さんはご存知かと思いますので、
飛ばして読んだ方が良いかと思います。（また、Macでの制作前提です。）

### イマドキ `gulp` …? `Webpack` や `Vite` を使わない理由は？
設定ファイルをゴリゴリかくようなタスクランナーは、需要がないかもしれないですが、
`Webpack` を使わない理由はエントリーポイントがJSではないためです。
静的な`html` を組み立てるだけであれば `gulp` で良いと思います。
WEB制作で `node.js` を使いはじめる瞬間に

- 「コレ `Apache` じゃないから `SSI` 使えない。困ったな」
- 「テキストは、 `html` と分けてバインディングしたい」
- 「最終的に `html` で納品しなければならない」

みたいな人には、まだ使えると思います。
- SPAで作りたい？

それはまた違う記事をご参照下さいませ。

### htmlテンプレートエンジンにあえて、`handlebars` をあえて使う理由は?
Github の stars は`pug` の方が多いのですが、
`npmtrends` では `handlebars` の方が多いです。

`handlebars.js` で日本語サイトを検索した感じでは
マイナーなライブラリの扱いでしたが、
グラフでみるとメジャーなテンプレートエンジンのようです。
`gulp` だと特に追加のインストールも不要です。

http://www.npmtrends.com/handlebars-vs-jade-vs-pug-vs-ejs-vs-hogan.js-vs-mustache-vs-jsrender
`ejs` よりとっつきやすく `pug` より覚えるのが億劫でないと思います。

-
- 
- ↓しばらく初期設定が続きます。


#  （スキップ可）Gitリポジトリを作る。
作成方法は人それぞれですが、

- Githubに `frontend` というリポジトリを作って `git clone` する

または

- `git init` で local に `frontend` というリポジトリを作って `$ git remote add`

`termminal` `cmd` がまったく分からない場合は
`SOURCETREE` を使って作れば良いです
https://www.sourcetreeapp.com/

それについての説明は省略します。
（※ gitに置かない選択肢がないと思うので、記載）

- 作成
```sh
touch .gitignore
```

- 記述
```sh:.gitignore
/node_modules
/dist
```
と`.gitignore`に書いておいてください。

`.gitignore` の参考リンク：
https://qiita.com/inabe49/items/16ee3d9d1ce68daa9fff

これの意味：
ライブラリやコンパイル後のファイルは、`commit`する必要がないです。

#  （スキップ可）Gulpを使う前の下準備
Homebrew のインストール（Mac）

```ruby:terminal
$ /usr/bin/ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)"
```

Nodebrew（node.js：パッケージ／バージョン管理）

```node.js:terminal
$ brew install nodebrew
```
`nvm` や `docker` が使う場合は、それでも良い。


たまにパーミッションの問題がありまして、
無いと動かない人も居るため、先に `mkdir` しておく。（8.3.0の箇所を打ちかえる）

```ssh:terminal
$ mkdir .nodebrew/src/v8.3.0
```

node.jsのバージョンを指定してインストールするやり方
（8.3.0の箇所は、使いたいバージョンに打ちかえる）

※2024年現在 `Node.js` は v22 が最新ですが、
　gulp3〜4系全盛期の Node.jsのバージョンは、ひとケタです。
　最新で動かない場合は、あえてバージョンを下げた方が良いです。

```node.js:terminal
$ nodebrew install-binary 8.3.0
```

最新版node.jsインストールのやり方

```node.js:terminal
$ nodebrew install-binary latest
```

インストールされたバージョンを一覧で見る

```node.js:terminal
$ nodebrew list
```

使うバージョンをuse〇〇で選ぶ、（8.3.0の箇所を打ちかえる）

```node.js:terminal
$ nodebrew use v8.3.0
```

もう一度、一覧をみる。current：に使うモノが選ばれている状態

```node.js:terminal
$ nodebrew list
```

### 下記「パスを通す」。
パスを通すとは、ターミナル開くたびにやらなくて良くする事。

「環境変数？パスを通す？」と思って、
`bash`や `zsh` について調べ始めると、奥が深いので、つまづきポイントになります
最初は「おまじない」と覚えて、後から勉強しましょう。

```sh
touch .bashrc
```

```bash:.bashrc
#!/bin/bash
export PATH=$HOME/.nodebrew/current/bin:$PATH
```

```bash:terminal
$ source ~/.bashrc
```
※ 最近は bash ではなく zsh が標準的に使われています。



#  （スキップ可）プロジェクトに移動する
ターミナルを開いて

```ssh:terminal
$ cd frontend
$ pwd
```

結果が

```
/Users/YourName/frontend
```

となっていれば良いです。

# （スキップ可）npmの初期設定
## プロジェクトを作る
次に `$npm init` コマンドを打ってください。
これは「このプロジェクトの初期化」の意味です。

`Package.json` という初期設定ファイルができていればOK。
他人のリポジトリを落としてきたりして、
既にこのファイルがある場合は `npm init` をしなくて良いです。
`npm init` は対話形式なので、聞かれたとおり素直に、適当に、答えたら大丈夫です。

```node.js:terminal
$ npm init
```

```node.js:terminal
name: (hoge) hoge
version: (0.0.0) 
description: 
entry point: (index.html) 
test command:test
git repository:frontend
keywords:fuga
author: hoge
license: (BSD) MIT
```

正確に答えておいた方がもちろん良いと思います。
これくらい適当でも「動かないことはない」。



（↑ここまでが初期設定でした。）


# handlebars.js
htmlの元となるファイル（テンプレート）を、
`handlebars.js` 形式で用意します。
ファイル名は `.hbs` とします。

ディレクトリを作りたい構造にファイルを配置します。

下記の例では `src` フォルダで
`pc` と`sp` のソースをウェブサーバーで表示する際の
ディレクトリ構造と一緒の構造で入れておき、

書き出した時に
`/dist/pc/` と`/dist/sp/` に仕分けされて保存されるようにしたものです。
最初から `/pc/` と`/sp/` に分けて作り始めると、
後で修正する際、行ったり来たり修正をしなければならず、
クライアントによってはすごく面倒です。

```
【ファイル＆ディレクトリ構成】

frontend
　├ .git
　├ .gitignore
　├ package.json
　├ package-lock.json
　├ gulpfile.js
　├ gulptask
　│　├ assemble_pc.js
　│　├ assemble_sp.js
　│　├ copy.js
　│　├ watch.js
　│　└ webserver.js
　│
　├ src
　│　├ asset
　│　│ ├css
　│　│ ├images
　│　│ └js
　│　│ 
　│　├ data
　│　│ └data.json
　│　│
　│　├ doc
　│　│ ├pages1
　│　│ │├index.pc.hbs
　│　│ │└index.sp.hbs
　│　│ ├pages2
　│　│ │├index.pc.hbs
　│　│ │└index.sp.hbs
　│　│ └pages3
　│　│  ├index.pc.hbs
　│　│  └index.sp.hbs
　│　│ 
　│　├ layouts
　│　│ ├pc.hbs
　│　│ └sp.hbs
　│　│ 
　│　└ partials
　│　  ├header.hbs
　│　  ├footer.hbs
　│　  └common_meta.hbs
　│　
　├ dist
　│　├ pc
　│　└ sp
　└ node_modules
```


#  /src/layouts/pc.hbs
まず「レイアウトファイル」を用意します。
コレは `html`で言うと、
「bodyの外側」を作る時に使います。

下記の例では `pc` という名前にしています。
これは最後に出来上がるファイルもPC用とSP用で分けようと思っているためです。

対応ブラウザの関係で、
スマホでしかモダンなタグを使えたり
`jQuery`（死語）のバージョンをスマホは最新にできるためです。

レスポンシブで作る場合は、作り分ける必要ないので、
このレイアウトファイルは `default.hbs` などにすれば良いです。

`{% body %}` が置き換わります。

```handlebars:pc.hbs
<!DOCTYPE html>
<html lang="ja" dir="ltr">
 <head>
  {{>common_meta}}
  <title>{{title}}</title>
  <link rel="canonical" href="https://sample.jp/{{dir}}/" />
 </head>
 <body>
  <div id="container">
   {% body %}
  </div>
 </body>
</html>
```

htmlに毎回使うようなパーツを読み込みたい場合は
`{{>パーツ名}}` と書きます。

そして `/src/partials/` 以下に
`パーツ名.hbs` と言った感じに用意しておくと、読み込まれます。

`GoogleTagManager` `header` `footer`も、
このやり方で埋め込む事ができます。

これで `Apache` がなくても
ローカルでパーツ読み込みができるようになります。

`Layouts` の `hbs` ファイルでも、
個別の `hbs` ファイルの中でも、
下記のような書式で読み込みできます。

```handlebars:partial
{{>common_meta}}
{{>gtm}}
{{>header}}
{{>footer}}
```

また、下記のようなデータバインディングを
`<title></title>`にはさみ込んでいます。

ココは ページ によって毎回異なるので、
可変部分をレイアウト側に書いて良いのかな？
と言う疑問が湧くと思いますが、


ココに書いて良いです。
レイアウト側の`title` や`dir` に
どうやってページ事に変わるテキストを入れるのか？
という事は後で書きます。

```handlebars:pc.hbs
{{title}}
{{dir}}
```

# /src/partials/common_〇〇.hbs
パーツの読み込みには、
Partialファイルを用意します。
`common_meta.hbs` の中はこんな感じ。

```html:common_meta.hbs
{{>common_meta}}
 ↓↓↓
htmlに変換すると
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=Edge" />
<meta name="keywords""/>
```

上記は `include` です。

何か変数を入れる場合は下記のような感じです。
例えば `thymeleaf` という変数を true にして `gulp` に処理させる場合は
`if` 文の上が入る感じです。

動的な処理が入る場所に関しては予めif文で分岐させておき、
`dist`で確認する時は `static` に表示させ、
正式に書き出す場合に `thymeleaf` を `true` にして書き出すといいです。

`true` にするやり方は、色々あるので省略。

```handlebars:common_meta.hbs
{{#if thymeleaf}}
<meta th:substituteby="/pc/common/inc/head :: common_meta" />
{{else}}
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=Edge" />
<meta name="keywords""/>
{{/if}}
```

# /src/doc/sample/index.pc.hbs

各Pageのファイルを用意します。
Layoutファイル内の `{% body %}` の中が置き換わるイメージです。
Layoutにある `{{title}}` や `{{dir}}` を置き換える場合は

`hbs`の先頭に、変数とその中身を書いておくと適用されます。

変数の参照ができないと思ったら `:` の後ろに
半角スペースが入っているかを確認してください。

`:` の後ろのスペースを詰めて記述すると、
テキストが上手く表示されません

```handlebars:index.pc.hbs
---
title: 「ここでLayoutsの変数をかきかえる」
dir: directory
---
<h1>{{obj.pagename.h1}}</h1>
<h2>{{obj.pagename.h2}}</h2>
<div>{{obj.pagename.description}}</div>
```


`{{obj.pagename.h1}}` や `{{obj.pagename.h2}}` はどうするの？
という話ですが

`/src/data/data.json` を読みこんでおきます。

ちょっとしたサイトであれば、配列を手で編集できますが、
中規模以上のサイトでは手でJSONを更新するのは大変です。

そうなってきたらサーバーサイドのエンジニアの方に
`json` を出すURLを作ってもらうと良いです。

最初からJSONを読み込む前提で作っておけば、
後から本格的なデータバインディングに移行するのもラクですし。

全部ハードコーディングすると、
後で雪だるま式に、大変なことになります。

```json:data.json
{
   pagename:{
    h1:"h1にこれが入ります",
    h2:"h2にこれが入ります",
    description:"descriptionにコレが入ります。"
   }
}
```


#  gulp
色々インストールしていく。「```--save-dev```」は必ず書くようにする。
必要なプラグインが分かるようにするため。gulp-assembleは古いので使わない。

```node.js:terminal
$ npm install --save-dev gulp
$ npm install --save-dev gulp-htmlmin
$ npm install --save-dev gulp-rename
$ npm install --save-dev gulp-extname
$ npm install --save-dev assemble
$ npm install --save-dev gulp-plumber
$ npm install --save-dev path
$ npm install --save-dev gulp-webserver
$ npm install --save-dev gulp-livereload
```

【2018.03 追記】「fs」はNode.jsに含まれているので、要らないです。

```
$ npm install --save-dev fs　 ← コレ不要
```

【2018.06 追記】Handlebars自体がインストールされてないような気がしましたので追記しました。

```
$ npm install --save-dev handlebars
$ npm install --save-dev gulp-handlebars
$ npm install --save-dev require-dir
```
【2019.02 追記】 gulp-server-livereloadが、古いっぽい。gulp-livereloadにしました。

```
$ npm install --save-dev gulp-server-livereload ←コレ不要
```

#  gulpfile.js
プロジェクト直下に「gulpfile.js」というファイルを作る。
中身は「/gulptask」以下に収納しようと思うので、こんな書き方にする。
（設定ファイルが長くなってくると、メンテがしづらくなってgulp疲れが発生するため）
PCとSPでhtmlを別々にしたいので、タスクを別々に書きました。
（これも中で条件分岐するより2回書いた方が設定ファイル的にラクなため。）

```Javascript:gulpfile.js
var gulp = require("gulp");
var requireDir = require('require-dir');

requireDir('./gulptask',{recurse: true});

gulp.task('default',
  ['assemble_pc','assemble_sp','copy','watch']
);
```
【追記】※あら、gulp v4.0.0だと動かないっすね。

```Javascript:gulpfile.js
var gulp = require("gulp");
var requireDir = require('require-dir');

requireDir('./gulptask',{recurse: true});

gulp.task('default',
  gulp.series(
    gulp.parallel(
      'assemble_pc','assemble_sp','copy','watch'
    )
  )
);
// gulp v4.0.0ではこう書く。のかな？

```


まず1行ずつ丁寧説明していきますと・・・
↓このファイルを実行するのには、「gulp」が必要です。

```Javascript:gulpfile.js
var gulp = require("gulp");
```

↓「require-dir」っていうプラグインを使います。

```Javascript:gulpfile.js
var requireDir = require('require-dir');
```

↓「requireDir();」を使って「/gulptask」以下にタスクを小分けにして書けるようにする。
↓「{recurse:true}」はオプションで、さらにサブディレクトリを使えるようにするヤツです。

```Javascript:gulpfile.js
requireDir('./gulptask',{recurse: true});
```

↓下記は「gulp」の「task」です。
↓タスクの名前は「default」です。
↓タスクの内容は「assemble_pc」と「assemble_sp」と「copy」と「watch」です。

```Javascript:gulpfile.js
gulp.task('default',['assemble_pc','assemble_sp','copy','watch']);
```

実行する順番を先に書きました。次に中身を書いて行きます。

#  /gulptask/assemble_pc.js
gulptasksの下に「assemble_pc.js」というファイルを作ります。
中身はこんな感じで書きました。

```Javascript:assemble_pc.js
var gulp = require('gulp');
var htmlmin = require('gulp-htmlmin');
var rename = require('gulp-rename');
var extname = require('gulp-extname');
var assemble = require('assemble');
var plumber = require('gulp-plumber');
var webserver = require('gulp-webserver');
var livereload = require('gulp-livereload');
var path    = require('path');
var fs = require("fs");
var app = assemble();
var obj = JSON.parse(fs.readFileSync("./src/data/data.json", { encoding:"utf8" }));
gulp.task('load', function(cb) {
  app.partials('src/partials/*.hbs');
  app.layouts('src/layouts/pc.hbs');
  app.pages('src/doc/**/*.pc.hbs');
  cb();
});
gulp.task('assemble',['load'],function() {
  app.toStream('pages')
    .pipe(plumber())
    .pipe(app.renderFile({layout:'pc',obj:obj}))
    .pipe(htmlmin({collapseWhitespace: true}))
    .pipe(rename({basename:'index'}))
    .pipe(extname('.html'))
    .pipe(app.dest('dist/pc/'))
    .pipe(livereload());
});
```
【追記】※あら、gulp v4.0.0では動かないッスね。

```Javascript:assemble_pc.js
var gulp = require('gulp');
var htmlmin = require('gulp-htmlmin');
var rename = require('gulp-rename');
var extname = require('gulp-extname');
var assemble = require('assemble');
var plumber = require('gulp-plumber');
var webserver = require('gulp-webserver');
var webserver = require('gulp-livereload');
var path    = require('path');
var fs = require("fs");
var app = assemble();
var obj = JSON.parse(fs.readFileSync("./src/data/data.json", { encoding:"utf8" }));
gulp.task('load', function(cb) {
  app.partials('src/partials/*.hbs');
  app.layouts('src/layouts/pc.hbs');
  app.pages('src/doc/**/*.pc.hbs');
  cb();
});
gulp.task('assemble', gulp.series( gulp.parallel('load'),function(){
  //明示的なreturnが必要
  return app.toStream('pages')
    .pipe(plumber())
    .pipe(app.renderFile({layout:'pc',obj:obj}))
    .pipe(htmlmin({collapseWhitespace: true}))
    .pipe(rename({basename:'index'}))
    .pipe(extname('.html'))
    .pipe(app.dest('dist/'));
}));
//gulp v4.0.0ではこう書く。のかな？

```


数行ずつまとめて説明します。
下記の部分は必要なプラグインを記した箇所です。（varは今風にカンマでつなげて書いても大丈夫です。）

objにはJSONをパースしたものを格納しておきます。objにJSONを格納しておけば後で呼び出せるので簡易的なデータバインディングができます。JSONファイルではなく、API叩いてJSONを返すヤツをココに組み込んでも良い、と思いますが、それはサーバーサイドのエンジニアさんに手伝ってもらって下さい。

忘れがちですが、分割されたタスクには、もう一度「gulp」を読み込む必要があります。

```Javascript:assemble_pc.js
var gulp = require('gulp');
var htmlmin = require('gulp-htmlmin');
var rename = require('gulp-rename');
var extname = require('gulp-extname');
var assemble = require('assemble');
var plumber = require('gulp-plumber');
var webserver = require('gulp-webserver');
var livereload = require('gulp-livereload');
var path    = require('path');
var fs = require("fs");
var obj = JSON.parse(fs.readFileSync("./src/data/data.json", { encoding:"utf8" }));
```

assembleを格納しておきます。

```Javascript:assemble_pc.js
var app = assemble();
```

1つ目のタスク「load」は下処理です。色んなものを読み込ませます。
「partials」とは「パーツ」の事です。（headerとかfooterなど）
「layouts」とは「レイアウトファイル」の事です（bodyの外側）
「pages」は、今から組み立てるhtmlファイルの事です。(bodyの内側)
「```/**/*.pc.hbs```」は、その階層以下の末尾に.pcと付いてる拡張子hbsのファイル全部、という指定の仕方です。「index.pc.hbs」的な名前の付いたファイルのみ変換されます。「index.sp.hbs」と同じフォルダに置いて作業できるので、作業する時にあちこち行き来せずに済むためラクです。
「cb」は「callback」です。「load」が次に呼び出された場所に値を返す感じです。

```Javascript:assemble_pc.js
gulp.task('load', function(cb) {
  app.partials('src/partials/*.hbs');
  app.layouts('src/layouts/pc.hbs');
  app.pages('src/doc/**/*.pc.hbs');
  cb();
});
```

下記はいよいよ組み立てるタスクです。このタスクは['load']が完了した後に実行されます。
gulpは並列でタスクを処理するので、何も意識しないと読み込みと組み立てが同時に動いてしまいます。タスクの順番がおかしくなる場合は、必ずタスクの名前の後ろに['事前に行なうタスク']を入れておくと順番がバラバラにならずに助かります。

```Javascript:assemble_pc.js
gulp.task('assemble_pc',['load'],function() {
  app.toStream('pages')
    .pipe(plumber())
    .pipe(app.renderFile({layout:'pc',obj:obj}))
    .pipe(htmlmin({collapseWhitespace: true}))
    .pipe(rename({basename:'index'}))
    .pipe(extname('.html'))
    .pipe(app.dest('dist/pc/'))
    .pipe(livereload());;
});
```
【追記】※gulp v4じゃ動かないっすね。

```Javascript:assemble_pc.js
gulp.task('assemble', gulp.series( gulp.parallel('load'),function(){
  //明示的なreturnが必要
  return app.toStream('pages')
    .pipe(plumber())
    .pipe(app.renderFile({layout:'pc',obj:obj}))
    .pipe(htmlmin({collapseWhitespace: true}))
    .pipe(rename({basename:'index'}))
    .pipe(extname('.html'))
    .pipe(app.dest('dist/'));
}));
//gulp v4.0.0だったらこう書く。。のかな？
```

1個ずつ説明していきますと・・・
コレが「assemble_pc」の外側です。名前の後ろのオプション的な箇所は['load1','load2','load3']とつなげて書く事ができます。

```Javascript:assemble_pc.js
gulp.task('assemble_pc',['load'],function() {○○○});
```

コレはstream()の新しい書き方?です。
「assemble-stream」というモジュールを使って処理するようです。pagesに格納されたファイルに対して、以下の処理を行ないます。

```Javascript:assemble_pc.js
app.toStream('pages')
```

gulpのお作法は「.pipe」でタスクを繋いでいきます。
gulp.watchしている時にエラーで処理が止まらないようにする「gulp-plumber」を使っています。watchの使い方に付いては、省略します。

```Javascript:assemble_pc.js
.pipe(plumber())
```

レイアウトは「pc」を使ってhtmlをrenderingします。「obj:obj」はパースしたJSONをrenderfile内に持って入るために利用します。

```Javascript:assemble_pc.js
.pipe(app.renderFile({layout:'pc',obj:obj}))
```

htmlをミニファイします。

```Javascript:assemble_pc.js
.pipe(htmlmin({collapseWhitespace: true}))
```

ファイル名を「index」に変更します。

```Javascript:assemble_pc.js
.pipe(rename({basename:'index'}))
```

ファイルの拡張子を「.html」に変更します。

```Javascript:assemble_pc.js
.pipe(extname('.html'))
```

ファイルを書き出す場所は「/dist/pc/」以下を指定します。

```Javascript:assemble_pc.js
.pipe(app.dest('dist/pc/'))
```

livereloadで更新するようにします。

```Javascript:assemble_pc.js
.pipe(livereload());
```


同様のファイルをspにも作成します。（省略）

###このタスクだけを実行するには？

```npm:terminal
$ gulp assemble_pc
```


#  /gulptask/copy.js
gulptasksの下に「copy.js」というファイルを作ります。

```Javascript:copy.js
var gulp = require('gulp');
gulp.task('copy', function() {
  gulp.src('./src/asset/**')
    .pipe(gulp.dest('./dist/'));
});
```
【追記】※あ、gulp v4.0.0ではこれじゃ動かないかもですね。

```Javascript:copy.js
var gulp = require('gulp');
gulp.task('copy',
  gulp.series(
  // gulp.parallel(
    function(){
    return gulp.src('./src/asset/**')
      .pipe(gulp.dest('./dist/'));
    }
  )
);
//gulp v4.0.0ではこう書く。のかな？
```

/src/asset/以下にある画像などのファイルをコピーします。
画像はdistにコピーしてあげないと表示が確認できないので、必ずセットで実行して下さい。
scssやAltJSは別のタスクで処理すると思いますが、もしもトランスパイルしない感じであれば
/src/asset/内に置いて/dist/にコピーして下さい。


###このタスクだけを実行するには？

```npm:terminal
$ gulp copy
```


#上記、全部のタスクを実行するには？

```npm:terminal
$ gulp
```

または

```npm:terminal
$ gulp default
```

#サーバーの立て方（gulp-webserver）
node.jsの簡易的なサーバーを立てる方法は色々ありますが、gulpなら、gulp-webserverを使います。特に「livereload」は最近のモダンなコーディング事情的には、ほぼ必須の仕組みです。（ファイルの変更を監視して自動的にブラウザが自動リロードを書けるものです。ホットリロードとも言います。）これでhtmlやCSSを書き直した時に「F5」や「Ctrl+R」をポチポチ毎回叩かなくて良いです。もっと良いやり方があるかもしれませんが、自分は「webserver」が「livereload」のイベントを拾うのと、「watch」イベントが「ファイルの更新」を監視するイベントを一つにできなかったので、「webserver」のターミナルは、「gulp default」とはターミナル別窓で開いております。（追記：「gulp-connect」はもう古いらしいので「gulp-webserver」に書き換えました。）

```javascript:webserver.js
var gulp = require('gulp');
var webserver = require('gulp-webserver');

gulp.task('webserver', function() {
  gulp.src('dist')
   .pipe(webserver({
    host: 'localhost',
    port: 8888,
    livereload: true
  }));
});
```
【追記】※あっ、gulp v4.0.0で動かないかも・・

```javascript:webserver.js
var gulp = require('gulp');
var webserver = require('gulp-webserver');

gulp.task('webserver', gulp.series( function(){
  gulp.src('dist')
   .pipe(webserver({
    host: 'localhost',
    port: 8888,
    livereload: true
  }));
}));
//gulp v4.0.0 ではこう書く。のかな。
```

監視の説明がなかったので、追記しました。「gulp-webserver」は「dist」を監視しているので
「ｓｒｃ」を更新したら「dist」へ書き出し、「dist」の更新を「livereload」で拾う、という感じでできます。

```javascript:watch.js
var gulp = require("gulp");
gulp.task("watch",['assemble_pc','assemble_sp','copy'], function () {
     var watcher = gulp.watch("./src/doc/**",['assemble_pc','assemble_sp','copy']);
     watcher.on('change', function(event) {
       console.log(event.path + 'が変更されました。');
     });
});
```

※あっ、gulp v4.0.0で動かないかも・・

```javascript:watch.js
var gulp = require("gulp");
gulp.task('watch',function(){
    return gulp.watch('./src/doc/**',
      gulp.task(
        gulp.series(
          gulp.parallel(
            'assemble','copy'
          )
        )
      )
    );
});
//gulp v4.0.0 ではこう書く？のかな。。
```

サーバーを立てると「ｈttp://localhost:8888」をブラウザで見れるようになり、/dist/以下に書き出されたファイルを表示します。

```node.js:terminal
$ gulp webserver
```

#npmでタスクを実行するには？
最近はタスクランナー的な抽象化をしないで「\$ npm run」で何でもやることになった（らしい）ので、
「gulpをグローバルインストールしたくないよ〜」
「ターミナルに「`\$ gulp`」なんて打つの恥ずかしいよ〜」
「npmコマンドでやりたいよ〜」
という場合は、次のようにする。
「`package.json`」の「`scripts`」に、下記のような記述をする。
（下記、`webpack`やら`grunt`やらは説明用に入ってるだけです。適宜削って下さい。）

```json:package.json
"scripts": {
    "test": "echo \"Error: no test specified\" && exit 1",
    "build": "npm-run-all build:*",
    "build:webpack": "webpack",
    "build:gulp": "gulp default",
    "build:grunt": "grunt default"
  },
```

このモジュール（npm-run-all）をインストールしておくと
複数のbuild以下のスクリプトを実行できるようになります。

```node.js:terminal
$ npm install --save-dev npm-run-all
```

そして

```node.js:terminal
$ npm run build
```

とすれば良いです。（`build:`が付いているタスクを全部実行してくれます）

（2021.03.04追記） 
ただ`gulp`を実行したいだけなら、
`package.json`に記述を追加しなくても `npx` で良いんじゃないか？と思いました。
この辺はNode.jsの進化によって移り変わるので、好みでいいと思います。

```
$ npx gulp
$ npx gulp default
$ npx gulp assemble_pc
$ npx gulp copy
$ npx gulp watch
```

いかがでしたでしょうか？「非エンジニア」でも「タスクランナー」を導入することでWeb制作を効率化ができるんじゃないかなと思います。以上です。