webpackだけでpugをビルドする環境を作る

# 始めに
今までgulpでコーディングの環境を作って、JSだけwebpackするというものを使っていましたが、それを何とかwebpack側に全部移行できないかなと思っていました。ただpugをビルドする方法がなかなかいいのが見つからなくて結構苦労しましたが、ある程度形になったのでそれを記事にまとめたいと思います。

動作確認用のリポジトリはこちらになっているので参考にしてください。色々余計なコードは入っていますが・・・。
[コーディング環境リポジトリ](https://github.com/TakanoriOnuma/coding-webpack)

# pugのビルド
ReactやVue.jsの開発でお世話になっているHTMLWebpackPluginは動的のファイル追加に対応できないので今回は使用しません。
今回はpug-html-loaderというものを使います。以下のような感じでローダーを呼んでいくとpugでビルドしてHTMLを出力してくれます。

```js:pugのルール設定
module.exports = {
  // 他の設定は省略
  module: {
    rules: [
      {
        test: /\.pug$/,
        use: [
          {
            loader: 'file-loader',
            options: { name: '[name].html' }
          },
          'extract-loader',
          {
            loader: 'html-loader',
            options: {
              attrs: ['img:src', ':data-src']
            }
          },
          {
            loader: 'pug-html-loader',
            options: {
              pretty: true
            }
          }
        ]
      },
    ]
  }
};
```

このようにするとCSSと同じようにJS側でimport文を書くとHTMLを出力してくれます。

```js:CSSと同じ感じでimportするやり方
import './pug/index.pug';   // index.htmlが生成される
```

ただ今回は動的にimport出来るようにするため、`require.context`を使用します。

```js:require.contextでディレクトリ以下をまとめてimportするやり方
// pugディレクトリ以下のファイルを全部取得する
const req = require.context('../pug/', false, /\.pug/);
// ファイル一覧を取得して、それぞれrequireする
req.keys().forEach((fileName) => {
  req(fileName);
});
```

# pugのビルド後のリロード
これでめでたしめでたしといきたいところですが、なぜかリロードしないという問題にぶち当たってしまいました・・・。色々調べたところ、どうやらwebpack-dev-serverでhot reloadの設定をするとリロードしなくなるようです。ただこれを外すとCSSがホットリロードしなくなってしまうため、やりたくない。
そんなわけでbrowser-syncを使ってpugファイルが更新したときに強制リロードするという強行に出ます。

```js:browser-syncサーバーを使用してwebpackを使う
const browserSync = require('browser-sync');
const webpack = require('webpack');
const webpackDevMiddleware = require('webpack-dev-middleware');
const webpackHotMiddleware = require('webpack-hot-middleware');

const webpackConfig = require('../webpack.config.development');
const bundler = webpack(webpackConfig);

const webpackDevMiddlewareInstance = webpackDevMiddleware(bundler, {
  publicPath: webpackConfig.output.publicPath,
  stats: {
    colors: true
  }
});

const server = browserSync({
  port: 3000,
  ghostMode: false,
  server: {
    baseDir: 'dist',
    middleware: [
      webpackDevMiddlewareInstance,
      webpackHotMiddleware(bundler)
    ]
  },
  files: [
    {
      // pugファイルを更新してもなぜかリロードしてくれないので手動で更新する
      match: ['./src/pug/**/*.pug'],
      fn: (event, file) => {
        // 一応ビルドを待つイベントっぽいけど、確信はない
        webpackDevMiddlewareInstance.waitUntilValid(() => {
          console.log('finish');
          server.reload();
        });
      }
    }
  ]
});
```

これでなんとかpugをビルドしたらオートリロードしてくれました。mixinとかの変更にもちゃんとビルドしてくれるか心配でしたがサンプルの段階では一応大丈夫そうです。

# CSSのホットリロード
CSSのホットリロードは以下のような感じに書いたら動きました。最近のwebpack-dev-serverはentryにホットリロード用のモジュールを入れる必要はなかったのですが、browser-sync経由でビルドする場合は必要なようです。

```js:webpack.config.development.js
// entryにhot-middlewareを追加する
for (const key in config.entry) {
  if (config.entry.hasOwnProperty(key)) {
    config.entry[key].unshift('webpack/hot/dev-server');  // 多分こっちはいらない気がする
    config.entry[key].unshift('webpack-hot-middleware/client');
  }
}

config.module.rules.push({
  test: /\.(sass|scss)$/,
  use: [
    'css-hot-loader',
    MiniCssExtractPlugin.loader,
    {
      loader: 'css-loader',
      options: {
        sourceMap: true,
        // 0 => no loaders (default);
        // 1 => postcss-loader;
        // 2 => postcss-loader, sass-loader
        importLoaders: 2
      }
    },
    {
      loader: 'postcss-loader',
      options: {
        sourceMap: true,
        plugins: [
          autoprefixer({
            browsers: [
              'last 2 version',
              'IE 11'
            ]
          })
        ]
      }
    },
    {
      loader: 'sass-loader',
      options: {
        sourceMap: true,
        includePaths: [path.resolve('./node_modules/')]
      }
    },
  ]
});
```

# JSのリロード
JSはいつも通りbabel-loaderの設定だけでいいかなと思っていたのですが、browser-syncからのビルドによってなぜかホットリロードに失敗してもリロードしてくれなかったので強制的にリロードするコードを書きました。

```js:entry.js
// HMRに失敗してもJSがリロードしてくれないので強制的にリロードを実行する
if (module.hot) {
  module.hot.accept(console.error);
  module.hot.dispose(() => {
    window.location.reload();
  });
}
```

## Reactのホットリロード
余談ですが、今の環境ならReactもホットリロードできるんじゃないかと思って試してみました。
ホットリロードの更新は子から親へ伝搬し、ホットリロードが出来る場所で伝搬が止まるので、以下のようにしたら上手くReactだけホットリロードしてくれました。Vue.jsはどこでホットリロードの設定を書いているのかわかりづらいのですが、おそらく同じようにできるのではないでしょうか。
Reactの動作はブランチを切っているので、興味がある方はこちらのブランチの方を参考にしてみてください。

[コーディング環境+Reactのブランチ](https://github.com/TakanoriOnuma/coding-webpack/tree/feature/react)

```js:entry.js
import './pugImport';
import '../css/base.scss';

// reactのエントリーポイント
import './react/reactEntry';

console.log('test');

// HMRに失敗してもJSがリロードしてくれないので強制的にリロードを実行する
if (module.hot) {
  module.hot.accept(console.error);
  module.hot.dispose(() => {
    window.location.reload();
  });
}
```

```js:reactEntry.js
import React from 'react';
import { render } from 'react-dom';

import App from './App';

render(<App/>, document.getElementById('app'));
```

```js:App.jsx
import React from 'react';
import { hot } from 'react-hot-loader/root';

import MyComponent from './components/MyComponent';

class App extends React.Component {
  render() {
    return (
      <div>
        <div>Hello, World!</div>
        <MyComponent/>
      </div>
    );
  }
}

export default hot(App);
```

# おわりに
pugをwebpackでビルドしたいだけなのになかなかいい方法が見つからずかなり苦戦しました。しかし全部webpackでビルドできたことでfile-loaderやurl-loaderがHTMLやCSSにも適応できて、パスの解決やblob化などがしやすくなりました。最初は考えていなかったのですが、基本はjQueryだけど一部はReactで書くと言った場合に今まではフルリロードも止む無しと思っていたのがここだけホットリロードを効かせられたり、結構メリットは大きいんじゃないかなと思いました。
ちなみに今回は諦めていますが、pug側でもしrequireが使えるようになったらCSS Moduleが使えるようになり、パーツをmixinで作るとコーディングでもクラス名の汚染をかなり防げるんじゃないかなという夢も感じました。