できるだけ楽にlaravel-mixから素のwebpackに移行する

# laravel-mixからwebpackに移行する

webpackのラッパーであるlaravel-mixは使い始めは死ぬほど便利なのですが、さまざまな事情によりやめたくなる瞬間がやって来ることがあります。

- 特定のnpm packageだけ別チャンクにしたい（例：正規表現等で別チャンクにするパッケージを指定する）
- プロジェクトが依存しているnpm packageの読み込みをもっとカスタマイズしたい（例：moment.jsから不要なロケールを消す、特定のパッケージは自力でビルドする）
- 複雑なsassビルドがしたい（例：共通変数の読み込み）
- mix.webpackConfigでカスタマイズし続けてたら、ある日突然「これ素のWebpackで書いたほうが楽なのでは？」と感じ始めた

とはいえ、laravel-mixをやめたくなる一方で、今後も使い続けたい非常に便利な機能もあります。
ひとつめは、分割されたファイルをいい感じに読み込んでくれる点、もうひとつは、バージョニングされたファイルも1行で読み込んでくれる点です。

```php:app.blade.php
<script src="{{ mix('js/manifest.js') }}"></script>
<script src="{{ mix('js/chunks/vendors.js') }}"></script>
<script src="{{ mix('js/app.js') }}"></script>
```

```json:mix-manifest.json
{
    "/app.js": "/app.js?id=1b4046accec02c510461",
    "/app.css": "/app.css?id=ed9d614f178e884779da",
    "/manifest.js": "/manifest.js?id=186d4d6bdb3251d7b7f2",
    "/vendor.js": "/vendor.js?id=331a3da9d77df744d791"
}
```

そこで本記事では、laravel-mixをやめつつ上記2点の便利ポイントをwebpackで実現する方法について書きます。

※webpack+vueで最低限のビルドを通す方法は以下の記事に譲ります。
https://ics.media/entry/16028/#webpack-babel-vue
（スーパー分かりやすい記事でおすすめです！）

# 1. webpackのCode Splittingを使って、特定のパッケージを別ファイルにする
ここでは、moment.jsをvendors.jsに分割してみます。

```js:webpack.config.js
entry: {
    app: path.join(__dirname, '/resources/assets/js/app.js'),
},
// optimizationのところが、ファイル分割をしている部分
optimization: {
    splitChunks: {
        cacheGroups: {
            default: false,
            // このvendorsの部分は、好きな名前にしてOK
            vendors: {
                test: /node_modules(?!\/moment)/,
                name: 'vendors',
                chunks: 'all',
            },
        }
    },
}
```

laravel-mixでは`.extract`を記述していましたが、webpackでは`splitChunks`と表現します。
上記の設定でビルドすると、app.jsに加えて、vendors.jsというファイルが出力できたはずです。

その他オプションなどの公式ドキュメントはこちら
https://webpack.js.org/plugins/split-chunks-plugin/

# 2. バージョニングしたファイルをいい感じに読み込む
## まずはバージョニングする
laravel-mixでは出力したファイルにバージョンごとの値を付与することを「versioning」と言い、`.version`で実行していましたが、webpackでは`caching`と表現します。地味に表現の仕方が違ってややこしい！

cachingするためには、outputオプションで出力ファイル名と[chunkhash]を指定します。こうすると、ファイル名の末尾に`/app.js?id=1b4046accec02c510461`といった感じでhashがつくようになります。

```js:webpack.confing.js
entry: {
    app: path.join(__dirname, '/resources/assets/js/app.js'),
},
output: {
    filename: 'js/[name].js?id=[chunkhash]',
    chunkFilename: 'js/chunks/[name].js?id=[chunkhash]',
    publicPath: '/',
    path: path.join(__dirname, '/public'),
},
optimization: {
    splitChunks: {
        cacheGroups: {
            default: false,
            vendors: {
                test: /node_modules(?!\/moment)/,
                name: 'vendors',
                chunks: 'all',
            },
        }
    },
}
```

詳細はこちら
https://webpack.js.org/configuration/output/#outputchunkfilename

## mix-manifest.jsonを、laravel-mixを使わずに自作する
app.blade.php側で`<script src="{{ mix('js/app.js') }}"></script>`のように簡単に読み込むためには、mix-manifest.jsonが必要です。ここでは、webpack-stats-pluginを使って自作してみます。本来はビルドした際の統計情報を書き出したりするプラグインなのですが、ビルド後の統計情報にはハッシュ付きファイル名も含まれているので、これを使ってしまおう！という作戦です。
https://github.com/FormidableLabs/webpack-stats-plugin

### インストール
```
$ npm install --save-dev webpack-stats-plugin
または
$ yarn add --dev webpack-stats-plugin
```

### webpackの設定
```js:webpack.config.js
// プラグインの読み込み
const { StatsWriterPlugin } = require("webpack-stats-plugin")

// mix-manifest.jsonをどのように書き出すかを指定
const MixManifest = data => {
    return JSON.stringify({
        "/js/app.js": '/' + data.assetsByChunkName.app,
        "/js/chunks/vendors.js": '/' + data.assetsByChunkName.vendors,
    })
}

module.exports = {
// （関連なさそうなオプションは省略）
    entry: {
        app: path.join(__dirname, '/resources/assets/js/app.js'),
    },
    output: {
        filename: 'js/[name].js?id=[chunkhash]',
        chunkFilename: 'js/chunks/[name].js?id=[chunkhash]',
        publicPath: '/',
        path: path.join(__dirname, '/public'),
    },
    optimization: {
        splitChunks: {
            cacheGroups: {
                default: false,
                vendors: {
                    test: /node_modules(?!\/moment)/,
                    name: 'vendors',
                    chunks: 'all',
                },
            }
        },
    },
    // ここで、書き出すファイル名を指定
    plugins: [
        new StatsWriterPlugin({
            filename: "mix-manifest.json",
            transform: MixManifest
        })
    ]
}

```
上記でビルドすると、laravel-mixが出力していたmix-manifest.jsonと同じ形式のJSONファイルが出力されます。

それぞれのビルド環境によってapp.jsやvendors.jsの他にもcssファイルを出力したりとさまざまなケースがあるかと思いますが、ビルド後のファイル名は`data.assetsByChunkName`オブジェクトの中に全て入っているので、そこから抽出できます。例えばこんな感じ。

```js:webpack.config.js
const MixManifest = data => {
    return JSON.stringify({
        "/css/app.css": '/' + data.assetsByChunkName.app[1],
        "/js/app.js": '/' + data.assetsByChunkName.app[0],
        "/js/chunks/vendors.js": '/' + data.assetsByChunkName.vendors,
        "/css/commons.css": '/' + data.assetsByChunkName.commons[1],
    })
}
```

これで、無事mix-manifest.jsonが出来ました！あとはbladeファイルに配置するだけです。

```php:app.blade.php
// manifestファイルは必要ありません
<script src="{{ mix('js/chunks/vendors.js') }}"></script>
<script src="{{ mix('js/app.js') }}"></script>
```

それでは、webpackでカスタマイズし放題のとっても楽しい（苦しい）ライフをお過ごしください🎉