webpackのみでビルドする環境を作る（pug, sass, babel）

# はじめに
はじめまして。qiita初投稿になります。主にツイッターとかで活動してます、itomiseと申します。
去年、SEとして新卒入社した会社を３か月でやめて、未経験でしたがweb系の会社に転職しました。
初めてhtmlを触ってから半年すぎたくらいなので、区切りとしてqiita投稿してみたいな、と思った次第です。

**<font color="Red">※2020/03/20 追記</font>**
最後にsassらへんをいじったせいでバグが起きてました。。。修正しました。
jsのchunkも追加しました。

#概要
この記事は[@wintyo](https://qiita.com/wintyo)さんの
[webpackだけでpugをビルドする環境を作る](https://qiita.com/wintyo/items/50e5a95e7dc10f237c5d)
を少し改変したものになってます。（@wintyoさん、勝手にすみません）

実際に案件等で使用してみて困ったことが多少起こったのですが、だんだん解決して形になってきたので記事としてまとめたいと思います。
開発環境を作っているだれかのためになれば幸いです。

git は[こちら](https://github.com/itomise/webpack-template)

# 環境
- windows 10 pro
- Node v12.14.1

## cssを外部出力に
cssをjsにバンドルしてしまうと、jsが読まれるまでhtmlにスタイルが当たっていない状態で表示されてしまうため、cssファイルとして生成するようにしました。
しかし、developmentの時にjsにバンドルしていないファイルはhot module replacementされません。
developmentの時はjsにバンドルし、cssファイルとして出力しないという方法もあるのですが、これはcss-hot-loaderで解決できました。

```javascript:webpack.dev.js
 module: {
    rules: [
      {
        test: /\.(sass|scss)$/,
        use: [
          'css-hot-loader',
          MiniCssExtractPlugin.loader,
          {
            loader: 'css-loader',
            options: {
              url: true,
              importLoaders: 2,
            }
          },
          {
            loader: 'postcss-loader',
            options: {
              plugins: [
                autoprefixer(),
              ]
            }
          },
          {
            loader: 'sass-loader',
          },
        ],
      },
    ]
  }
```
webpack configの`use: []` は後ろから適用されていくので、先頭に`'css-hot-loader'`を追加するだけです。これで、HMRがきくようになります。

## pugファイルをディレクトリ構造を保ったまま出力
pugのディレクトリ構造のままhtmlを生成できたら便利なので、jsとwebpack configを多少変えました。

**※2020/03/20 追記** 
**wintyoさんよりコメントでよりスマートな方法を教えていただいたので修正しました。**

pugファイルを読み込むときに、下層のファイルもすべて読み込むようにして、

```javascript:importPug.js
// pugをインポートする
const req = require.context('../../pages/', true, /\.pug/) // 第二引数を true に
req.keys().forEach((fileName) => {
  req(fileName)
})
```

~~結構強引ですが、pugファイルをfile-loaderで出力するときにディレクトリも含んだ文字列にして出力するようにします。~~
名前の出力を`[path][name].html`の形にすることでディレクトリ情報をもったまま出力できます。
しかし、そのままだと`pages/`からのパスになってしまうのですが、outputPathで変更することにより解決できました。

```javascript:webpack.common.js
{
  loader: 'file-loader',
  options: {
    name: '[path][name].html',
    outputPath: (url) => {
      return path.relative('src/pages', url)
    },
    url: false
  }
},
```

これで、ディレクトリ構造を保ったままhtmlファイルを吐き出せるようになりました。

## pugにjsから値を渡せるようにする
pug-html-loaderで`data`にオブジェクトを渡すと、pugファイル内で値を使用できるようになります。
現在はルートディレクトリからのパスをpugファイルで使用できるようにしています。
はじめは、ルートディレクトリを変更するたびにpugのほうも手動で書き換えていたのですが、webpack.common.jsの１か所だけ変更で済むようになったので便利ですね。
metaなどの情報もpug.config.json等で管理するようにしたほうがいいかなと思うので、あとあと変えていこうかなと思います。

```javascript:webpack.common.js
{
  loader: 'pug-html-loader',
  options: {
    pretty: true,
    data: {
      PUBLIC_URL: PUBLIC_URL
    }
  },
}
```

## jsのchunkを有効にする
共通で使うモジュールがある場合、そのまま複数ファイルでそれを読み込んで出力してしまうと、
同じ処理が複数ファイルに重複してしまうことになります。
同じ処理がある場合、`vendor.js`などにまとまっていたほうがキャッシュもされてより効率的な読み込みができるので、
chunkを追加しました。
また、chunkすると、ファイルを更新してもページで更新されなくなってしまったので、
productionで実行したときにのみchunkするようにしました。
（怠惰なのでまだ検証しておらず、いけるっぽい！って感じでいれてしまったのでダメそうだったら消してください。。）

参考：[webpack 4 の optimization.splitChunks の使い方、使い所 〜廃止された CommonsChunkPlugin から移行する〜](https://qiita.com/soarflat/items/1b5aa7163c087a91877d)

```javascript:webpack.prod.js
optimization: {

  splitChunks: {
    cacheGroups: {
      vendor: {
        test: /node_modules/,
        name: 'vendor',
        chunks: 'initial',
        enforce: true
      }
    }
  }
}

```
また、納品ファイル生成時のみvendor.jsを読み込むように変更しました。
各ページで使用するjsもページごとに設定できるようにpage_jsブロックを追加しました。

```html:layout.pug
body
  block content

  if (env === 'production')
    script(src= PUBLIC_URL + '/js/vendor.js')
  block page_js
```

## その他細かい変更
- ルートディレクトリを簡単に変えられるように整理
- 画像圧縮
- javascriptディレクトリ直下のjsを自動でエントリーポイントに
- publicフォルダは静的に出力したいファイルをそのままコピーするフォルダに

# さいごに
簡単なLPとかを作る用に使っていた本環境ですが、中規模等でも使えるようにもろもろ最適化していければと思います。
あと個人的に、scss記法よりsass記法（mixinとかfunctionはscssでかくけど）、ejsよりpugのほうが使いやすいと思ってるんですがどうでしょうか。