まだ更新ボタン連打してるの？👀 BrowserSyncのお手軽ローカルサーバー

この記事は[ちょっとした工夫で効率化！02【PR】パソナテック Advent Calendar 2020](https://qiita.com/advent-calendar/2020/pasonatech-tips02)の13日目の記事です。

## はじめに
マークアップをする際、最初は[XAMPP](https://www.apachefriends.org/jp/index.html)で環境を作り、HTMLやCSSを更新するたびに、都度更新ボタンを押して表示確認を行っていました。
![internet_f5_attack.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/242655/c0976e9e-bedb-9a09-fbf3-a818c1ec786e.png)
また、バックエンド組み込み後のスタイルを修正していて、複雑な条件でしか表示されない部品のパーツのスタイルが崩れてると修正が非常につらかった記憶があります。(スタイル修正後、再現させるところまで持ってくのがたいへん。。。。)

その後、色々環境を試してみた中で、[BrowserSync](https://browsersync.io/)が気軽に利用でき、かつかなり便利だったので使い方を共有します。
※Webpackを利用するVue.jsやReactではもっと便利な`HMR(Hot Module Replacement)`が使えるのですが、最初からWebpackは敷居が高く、また利用できない案件まだまだあります。

### BrowserSyncとは
- NodeJS製の開発用サーバ
- ファイルの変更を検知してブラウザのリロードを実行可能
- CSSファイルの変更を検知して、ブラウザのリロードなしに変更を反映可能
- 複数のブラウザ上での操作を同期できる
- ミドルウェアを利用することで、リクエスト時にJSテンプレートエンジンのビルドを実行することが可能

## 使い方
検索するとgulpと組み合わせて利用する方法がよく出てきますが、
今回はシンプルにBrowserSyncだけで色々設定を行っていきます。

### 事前準備
この環境は、[Node.js](https://nodejs.org/ja/)を利用しますので、インストールしておいてください。

**補足**
Node.jsの普通のインストーラーを使ってもいいですが、バージョン管理ツールを使うことを推奨します。
※普通にインストールすると案件毎にバージョンが異なる場合につらい思いをするため:disappointed_relieved:
　**参考記事**
　Windows => [nodistでNode.jsをバージョン管理](https://qiita.com/satoyan419/items/56e0b5f35912b9374305)
　Mac => [Node.jsのバージョンを自動で切り替えられるnodenvが超便利](https://qiita.com/tonkotsuboy_com/items/5322d226b6783d25b5df)

### プロジェクトの作成
```shell:Console
# NodeJSプロジェクトを初期化
% npm init -y
# インストール
% npm install --save-dev browser-sync
# 設定ファイル(bs-config.js)を生成
% npx browser-sync init
```

<details><summary>生成されたbs-config.js</summary><div>

```js:bs-config.js

/*
 |--------------------------------------------------------------------------
 | Browser-sync config file
 |--------------------------------------------------------------------------
 |
 | For up-to-date information about the options:
 |   http://www.browsersync.io/docs/options/
 |
 | There are more options than you see here, these are just the ones that are
 | set internally. See the website for more info.
 |
 |
 */
module.exports = {
    "ui": {
        "port": 3001
    },
    "files": false,
    "watchEvents": [
        "change"
    ],
    "watch": false,
    "ignore": [],
    "single": false,
    "watchOptions": {
        "ignoreInitial": true
    },
    "server": false,
    "proxy": false,
    "port": 3000,
    "middleware": false,
    "serveStatic": [],
    "ghostMode": {
        "clicks": true,
        "scroll": true,
        "location": true,
        "forms": {
            "submit": true,
            "inputs": true,
            "toggles": true
        }
    },
    "logLevel": "info",
    "logPrefix": "Browsersync",
    "logConnections": false,
    "logFileChanges": true,
    "logSnippet": true,
    "rewriteRules": [],
    "open": "local",
    "browser": "default",
    "cors": false,
    "xip": false,
    "hostnameSuffix": false,
    "reloadOnRestart": false,
    "notify": true,
    "scrollProportionally": true,
    "scrollThrottle": 0,
    "scrollRestoreTechnique": "window.name",
    "scrollElements": [],
    "scrollElementMapping": [],
    "reloadDelay": 0,
    "reloadDebounce": 500,
    "reloadThrottle": 0,
    "plugins": [],
    "injectChanges": true,
    "startPath": null,
    "minify": true,
    "host": null,
    "localOnly": false,
    "codeSync": true,
    "timestamps": true,
    "clientEvents": [
        "scroll",
        "scroll:element",
        "input:text",
        "input:toggles",
        "form:submit",
        "form:reset",
        "click"
    ],
    "socket": {
        "socketIoOptions": {
            "log": false
        },
        "socketIoClientConfig": {
            "reconnectionAttempts": 50
        },
        "path": "/browser-sync/socket.io",
        "clientPath": "/browser-sync",
        "namespace": "/browser-sync",
        "clients": {
            "heartbeatTimeout": 5000
        }
    },
    "tagNames": {
        "less": "link",
        "scss": "link",
        "css": "link",
        "jpg": "img",
        "jpeg": "img",
        "png": "img",
        "svg": "img",
        "gif": "img",
        "js": "script"
    },
    "injectNotification": false
};
```

</div></details>


毎回コマンドを打つのがめんどくさいので、npm scriptsとして登録しておくことをおすすめします

```json-doc:package.json
{
  // ・・・省略
  "scripts": {
    // "test": "echo \"Error: no test specified\" && exit 1"
    "server": "browser-sync start --config \"./bs-config.js\""
  },
  // ・・・省略
}
```

これで、以下だけでサーバを起動できます

```shell:Console
% npm run server
```


## カスタマイズ例
ここからは用途に応じて、少しずつ設定を変更していきます。

### 基本の設定
```:ディレクトリ構成
ProjectRoot/
　├ node_modules/
　├ public/				・・・ 静的資産配置ディレクトリ
　│　├ assets/
　│　│　└ styles/
　│　│　　　└ style.css
　│　└ index.html
　├ bs-config.js			・・・ Browsersync設定ファイル
　├ package-lock.json
　└ package.json
```
```js:bs-config.js
// 全部書くと長くなるため、変更箇所のみ記載します
module.exports = {
    // CSSファイルの変更を検知し、リロードなしで反映します。
    // HTMLファイルの場合はブラウザがリロードされてしまうので、監視しないようにしています。
    // "files": false,
    "files": [
        "public/**/*.css",
        // "public/**/*.html",
    ],
    // ルートのディレクトリを指定します。
    // "server": false,
    "server":[
        "public",
    ],
    // サーバ起動のたびのブラウザが自動で起動すると邪魔なので、OFF
    // "open": "local",
    "open": false,
};
```

### 複数のディレクトリをルートディレクトリとして設定する
静的資産とビルド済み資産を、別ディレクトリに配置したい場合に以下のように設定することでいい感じに動いてくれます

```:ディレクトリ構成
ProjectRoot/
　├ node_modules/
　├ public/				・・・ 静的資産配置ディレクトリ
　│　└ index.html
　├ src/				・・・ ビルド資産配置ディレクトリ
　│　└ assets/
　│　　└ styles/
　│　　　└ style.css
　├ bs-config.js			・・・ Browsersync設定ファイル
　├ package-lock.json
　└ package.json
```
```js:bs-config.js
// 全部書くと長くなるため、変更箇所のみ記載します
module.exports = {
    // CSSファイルの変更を検知し、リロードなしで反映します。
    // HTMLファイルの場合はブラウザがリロードされてしまうので、監視しないようにしています。
    // "files": false,
    "files": [
        "src/**/*.css",
        // "src/**/*.html",
        "public/**/*.css",
        // "public/**/*.html",
    ],
    // ルートのディレクトリを指定します。
    // 複数指定した場合は、最初に指定されたディレクトリから順番にファイルを探索します。
    // 例：/index.htmlにアクセスした場合
    //   src/index.htmlを探索 -> 存在しない
    //   public/index.htmlを探索 -> 存在するためコンテンツを返却
    // 例：/assets/styles/style.cssにアクセスした場合
    //   src/assets/styles/style.cssを探索 -> 存在するためコンテンツを返却
    //   ※ public/assets/styles/style.cssにファイルを置いても反映されません。
    // "server": false,
    "server":[
        "src",
        "public",
    ],
    // サーバ起動のたびのブラウザが自動で起動すると邪魔なので、OFF
    // "open": "local",
    "open": false,
};
```

### バックエンド環境と組み合わせて利用する
```:ディレクトリ構成
ProjectRoot/
　├ node_modules/
　├ public/				・・・ ドキュメントルート(dockerやXAMPPでサーバが動いている前提)
　│　├ assets/
　│　│　└ styles/
　│　│　　　└ style.css
　│　└ index.php
　├ bs-config.js			・・・ Browsersync設定ファイル
　├ package-lock.json
　└ package.json
```
```js:bs-config.js
// 全部書くと長くなるため、変更箇所のみ記載します
module.exports = {
    // CSSファイルの変更を検知し、リロードなしで反映します。
    // PHPファイルの場合はブラウザがリロードされてしまうので、監視しないようにしています。
    // "files": false,
    "files": [
        "public/**/*.css",
        // "public/**/*.php",
    ],
    // バックエンド実行環境のサーバのURLを指定します
    // "proxy": false,
    "proxy": "localhost:8080",
    // サーバ起動のたびのブラウザが自動で起動すると邪魔なので、OFF
    // "open": "local",
    "open": false,
};
```

## 最後に
複数のブラウザを開いておいて同時に確認しながら、、、といった使い方もできるので、更新ボタンを押す回数を減らして生産性を上げていきましょう。

※ 今回紹介したもの以外に、かなりたくさんのオプションがありますので、気になる方は是非[公式ドキュメント](https://browsersync.io/docs/options)を見てみてください👀