【Laravel5.5】Laravel Mix でPugを.blade.phpにコンパイルする方法

# はじめに
初投稿です。
最近FuelPHPからLaravelに乗り換えることにしたLaravel初心者。せっかくLaravelを用いることになったのでLaravel-Mixを使ってみようと試み、さらにどうせならPugも導入したいと戦った備忘録的なもの。
ご指摘等ございましたらコメントお願いします。

# お品書き
 + Laravel Mixとは
 + 環境構築
 + Pugのコンパイル
 + Pugを.blade.phpにコンパイル
 + おまけ　watchが効かない件


# [Laravel Mix](https://readouble.com/laravel/5.5/ja/mix.html)とは  
Laravelは5.5が最近リリースされたようで、せっかくなので最新版を使うことにしました。
**Laravel Mix**ですが、これは5.4から実装された機能で、WebpackのラッパーAPIのようなものです。基本的な使い方は見出しのリンクにしている公式サイトに載っている通りです。
超簡単に説明すると以下のコマンドを実行すれば、node.jsやscssのファイルを一括でjs,cssにコンパイルし、変更監視もしてくれるものです。

```command
npm run watch-poll
```

設定によってはソースマップとかも入れられます。ありがたいツールなのでどんどん使いたいです。  

以前は**Laravel Elixir**という名前だったようで、`gulp`を利用していた方針から、`npm script`を使用するように変わったそうです。
変更の経緯や違いは検索すれば結構見つかると思います。私はこの方のブログなど参照しました。（[Laravel Elixir から Laravel Mix へ](http://tech.innovator.jp.net/entry/laravel-mix)）   
  
# 環境構築
今回はサーバのアプリもLaravelで開発するため、とりあえずLaravelで開発できる環境を整っている前提とします。
Laravelの導入手順はこちらの記事などをご参照ください。必要なことはまとめられておりました。（[Laravel5.4の環境構築](http://qiita.com/Syo_pr/items/4994f71e8c9152ceee0d)）
私は5.5ですが環境構築手順はバージョンで違うことはほとんどありません。

※Laravel Mix単体でnpmパッケージとして配布されているため、Laravelを利用しない場合でもLaravel Mixは利用できます。が、私が未開拓のためその場合は今回紹介する手法が使えるのか解りかねます。たぶんいけると思いますが。
その場合の環境構築はこちらの方のブログが参考になると思います。（[Laravel Mix なら設定3行だけで webpack/Sass/JS のビルド環境ができました](https://www.tam-tam.co.jp/tipsnote/html_css/post13444.html)）  
　  


#### 参考：私の開発環境スペック
| ホストOS | ゲストOS | PHP| node | npm |
|:------:|:------:|:------:|:-----:|:-----:|  
| Windows 10 | CentOS7.1 | 7.1 | v6.11.1 | 3.10.10 |


### Laravel Mix使用準備
では早速**Laravel Mix**を使用できる環境構築をしましょう。
node、npmはインストールされている前提とします。参考までに私は yum からインストールしました。

 + Laravelのpackage.jsonをインストール。package.jsonのあるディレクトリで以下を実行。  
 
`npm install --force`  
**※この際、WindowsがホストOSである場合、node_modules内にシンボリックリンクが貼られようとしてエラーになる場合があります。詳細は及び回避策は[こちら](https://medium.com/@dtinth/isolating-node-modules-in-vagrant-9e646067b36)**（英語ですが書いてあるコマンドを見れば英語ダメな人でもわかると思います。私も英語はだめです。）  

インストールが終わればLaravel Mix自体はもう使用可能です！
試しにnode_modulesのあるディレクトリで以下のコマンドを実行してみましょう。  
`npm run dev`  
Compiled successfully と表示され、app/public/js/app.jsとapp/public/css/app.cssが作成されています。
このあたりの使い方は基本的に公式サイトに載っている通りで何ら問題なく使えるかと思います。
webpack.mix.jsを設定すれば何ファイルでもjs、scss、lessなどコンパイルできます。（当然ファイル数が増えれば一度のコンパイルが長くなるので、そこは各々の環境で最適化が必要かと思います。）
Typescriptも`tsconfig.json`の設定さえしておけば、すぐに`webpack.mix.js`へ以下のような記述でコンパイルできます。

```webpack.mix.js
mix.ts('hogehoge.ts' 'public/js');
```  


## Pugのコンパイル
JS、CSSとコンパイルができるならHTMLもコンパイルがしたい！
と思ってできないのかググりました。
結果公開されているnpmのパッケージにたどり着きました。

[laravel-mix-pug](https://www.npmjs.com/package/laravel-mix-pug)

 + 見つけたので早速インストール  
`npm install laravel-mix-pug --save-dev`    
　
 + 正常終了したので公開されている情報に則って使ってみる。

```webpack.mix.js
let mix = require('laravel-mix');
mix.pug = require('laravel-mix-pug');

mix.js('resources/assets/js/test.js', 'public/js')
   .sass('resources/assets/sass/test.scss', 'public/css')
   .pug('resources/assets/pug/test.pug', 'resources/views');
```

`npm run dev`を実行！

```
TypeError: Cannot read property 'seeds' of undefined
    at MixPugTask.run (/home/vagrant/src/node_modules/laravel-mix-pug/src/MixPugTask.js:25:32)
    at CustomTasksPlugin.runTask (/home/vagrant/src/node_modules/laravel-mix/src/plugins/CustomTasksPlugin.js:34:14)
    at Mix.tasks.forEach.task (/home/vagrant/src/node_modules/laravel-mix/src/plugins/CustomTasksPlugin.js:9:44)
    at Array.forEach (native)
....(中略)
```

エラーです。。。
TypeError: Cannot read property 'seeds' of undefined　とのこと。
私は最初これは「'seeds'が見つからないよ（undefinedだよ）。」ってエラーだと思ったんですけど
英語的には「見つからない（undefined）の'seeds'」なんですね。
つまり見つからないのは'seeds'ではなく'seeds'を呼び出している元の変数です。
該当のjsファイルを見てみましょう。

```MixPugTask.js
     17     run() {
     18
     19         let {files, dest, options} = this.data;
     20
     21         // Set destination folder
     22         this.dest = dest;
     23
     24         // Setup template seeder
     25         this.seedPath = options.seeds; // ←ここ！つまりoptionsがundefinedということ。
```

`laravel-mix-pug/src/index.js`を見ればわかるのですが、pugメソッドの第3引数にデフォルト値が設定されていません。
なのでエラーになってしまうようです。
つまり第3引数なしでは使えません。配布元のUsageが嘘をついているということですね(笑)

 + しっかりと第3引数を指定しまして

```webpack.mix.js
let mix = require('laravel-mix');
mix.pug = require('laravel-mix-pug');

mix.js('resources/assets/js/test.js', 'public/js')
   .sass('resources/assets/sass/test.scss', 'public/css')
   .pug('resources/assets/pug/test.pug', 'resources/views', {});
```

`npm run dev`を実行！

```
DONE  Compiled successfully in 48238ms

...(中略)

/resources/views/test.html  201 bytes          [emitted]
```

コンパイル完了！

・・・待てよ。これHTMLにしたけどLaravelで開発するなら.blade.phpの拡張子で出してくれないと恩恵が薄くないか？？
というわけでせめて拡張子は変えたい。


## Pugを.blade.phpにコンパイル

やっとタイトル。
結論から言うと現状の配布されているlaravel-mix-pugには.htmlで出力する機能しかありません。
が、出力の拡張子をちょっといじるだけなので、先ほど中のソースをちょっと読んだついでに、その処理を追加してしまいましょう。

余談ですがLaravel Mixの前段であるLaravel Elixirにも同じようなパッケージが配布されており、それにはbladeで出力するオプションがあったようです。（[laravel-elixir-pug](https://www.npmjs.com/package/laravel-elixir-pug)）
・・・なんでなくなったんだ？


では私が修正した内容をご紹介します。
対象ファイルは`laravel-mix-pug/src/MixPugTask.js`です。

```diff
//　17行目くらい
    run() {

        let {files, dest, options} = this.data;
+       options = options || {}; // ついでなので第3引数に空のオブジェクトを指定しなくてよいようにしておきます。

        // Set destination folder
        this.dest = dest;

        // Setup template seeder
        this.seedPath = options.seeds;
        this.locals = options.locals || {};
+       this.blade = options.blade || true; // 第3引数のオブジェクトでbladeと定義できるようにします。bladeを使うことが多いのでデフォルトtrueです。

...(中略)

//　146行目くらい
    prepareAssets(src) {
        let file = new File(src);
-       let output = path.join(this.dest, file.nameWithoutExtension() + '.html');
+       let extension = '.html';
+       // blade option
+       if(this.blade == true){
+           extension = '.blade.php';
+       }
+       let output = path.join(this.dest, file.nameWithoutExtension() + extension); // trueの場合は拡張子をblade.phpにして出力
        let asset = new File(output);

        Mix.addAsset(asset);

        return asset;
    }
```

できました！これでコンパイルしてみましょう！（ついでにちょっと気持ち悪かったpugの第3引数も消しましょう）

```
DONE  Compiled successfully in 48238ms

...(中略)

/resources/views/test.blade.php 201 bytes          [emitted]
```

完成です！
　  


ちなみに本件なのですが、すでにGithubの方にプルリクが上がっておりました。（[こちら](https://github.com/matejsvajger/laravel-mix-pug/pull/1)）
この方の修正だと第3引数に拡張子そのものを指定するオプションを追加してるっぽいですね。
マージされればこの修正は不要になります。（でも第3引数は必要・・・？）

ちなみにOptionsで定義されている`seeds`というのがどういう使い方をするのかいまいちわかっていません。
詳しい方がいらっしゃったらご教示いただきたいです。localsはなんとなくわかるんですが。。



## おまけ　watchが効かない件

確かにここまででpugをbladeテンプレート形式でコンパイルできるようになったのですが、
現状のままだとwatchオプションの監視の対象外になっております。

`npm run watch-poll`

で起動した場合、最初の一回はコンパイルされるのですが、`webpack.mix.js`に追記したPugファイルを変更してもコンパイルが走りません。
いろいろLaravel Mixのソースを追ってみたのですが、どうやら`webpack.config.js`のエントリポイントとしてPugのファイルが認識されていないようです。
また、`node_modules/laravel-mix/src/builder/webpack-entry.js`を見る限り、外部からエントリポイントの追加ができないように見えます。（node.jsは超初心者かつしっかり追っていないのでできるのかもしれないです。鵜呑みにしないでください。）

試しにソースをいじって無理矢理エントリポイントを追加してみました。
`node_modules/laravel-mix/src/builder/Entry.js`

```diff
// 14行目くらい
     get() {
+        this.structure['pug/test.pug'] = ['/home/vagrant/src/resources/assets/pug/test.pug'] //　エントリポイントをハードコーディングで追加してみる
         return this.structure;
     }
```

これで実行。
`npm run watch-poll`


```
 ERROR  Failed to compile with 1 errors

 error  in ./resources/assets/pug/test.pug

Module parse failed: /mnt/src/dim/resources/assets/pug/test.pug Unexpected token (2:7)
```

エラーになりました。原因は「pugってなんだよ」って言われることらしいので、loaderというものが必要になるそうです。
（このあたりエラーをググり続けただけなので正しい理屈は理解できていません。。。）

必要ならばloaderをインストールしてみましょう。

`npm install pug-loader --save-dev`

また、このインストールしたpug-loaderをLaravel Mixで読み取らせなければいけません。
以下のようなwebpackConfigの設定が必要です。

```webpack.mix.js
mix.webpackConfig({
            module: {
                rules: [{
                        test: /\.pug/,
                        enforce: "pre",
                        loader: 'pug-loader'
                    }]
            }
        })
        .pug('resources/assets/pug/test.pug', 'resources/views');
```

これで再実行！
`npm run watch-poll`

```
DONE  Compiled successfully in 48238ms

...(中略)

/resources/views/test.blade.php 201 bytes          [emitted]
```

動いてるっぽいので、pugファイルを適当に更新してみる。

するとまたコンパイルが走りました。おお、watchされている。。。


しかしこんな荒っぽい方法を常に採用するわけにもいかず、現在Laravel Mixのエントリポイントを**きれいに**追加する方法を探しています。なのでおまけという形で。
見つかればまた書こうかと思います。