はじめてのLaravel Mix

## はじめに
この記事は`Laravel Mix`初導入に挑戦した際、勉強したことをまとめたものです。
webpackやNode.jsに関する知識などが少ないひよっ子ですので、間違いなどありましたらご教示頂けるとうれしいです:bow:



## 1. Laravel Mixとは
SassやES6のコンパイルなどが簡単にできる便利なビルドツール。
webpackのラッパーAPI。（=`"webpackを操作しやすくしたもの"というイメージ`）

Laravel Mixという名前から勘違いしてましたが、Laravel無しでも使えるんですね。
↓こちらでの説明がとてもわかりやすかったです。
[初めてのLaravel Mix \| アールエフェクト](https://reffect.co.jp/laravel/first-laravel-mix)



## 2. やりたいこと
  - Sassのコンパイル
  - Pugの導入、コンパイル
  - ES6+からES5へのコンパイル、バンドル、IE11対応
  - ファイル変更を監視してブラウザをオートリロード



## 3. いざ導入
**開発環境**

- Mac OS Mojave 10.14.6
- Node.js 10.16.3
- npm 6.9.0


**ディレクトリ構造**
以下のように、コンパイル前のファイル（`src`）とコンパイル後のファイル（`dist`）に分けていきます。

~~~bash
project root
├── dist
└── src
~~~


### 3-1. Laravel Mixのインストール

今回はyarnを使ってインストールしました。

~~~bash
// プロジェクトのルートディレクトリで
$ yarn
$ yarn add laravel-mix --dev

// npmの場合
$ npm init -y
$ npm i laravel-mix -D
~~~
 
インストールできたら、laravel-mixの設定ファイルの雛形をプロジェクトのルート直下にコピーしてきます。

~~~bash
$ cp node_modules/laravel-mix/setup/webpack.mix.js ./
~~~
　
`webpack.mix.js`の中身を覗いてみると、、

~~~javascript:webpack.mix.js
let mix = require('laravel-mix');

/*
 |--------------------------------------------------------------------------
 | Mix Asset Management
 |--------------------------------------------------------------------------
 |
 | Mix provides a clean, fluent API for defining some Webpack build steps
 | for your Laravel application. By default, we are compiling the Sass
 | file for your application, as well as bundling up your JS files.
 |
 */

mix.js('src/app.js', 'dist/').sass('src/app.scss', 'dist/');

// Full API
// mix.js(src, output);
// mix.react(src, output); <-- Identical to mix.js(), but registers React Babel compilation.
// mix.preact(src, output); <-- Identical to mix.js(), but registers Preact compilation.
// mix.coffee(src, output); <-- Identical to mix.js(), but registers CoffeeScript compilation.
// mix.ts(src, output); <-- TypeScript support. Requires tsconfig.json to exist in the same folder as webpack.mix.js
// mix.extract(vendorLibs);
// mix.sass(src, output);
// ~以下省略~
~~~
チュートリアル的にシンプルな設定がされている状態です。
この設定ファイルをカスタマイズしていきます。

### 3-2. 基本設定
#### NPM Scriptの追加
まずはNPM scriptを`package.json`に追加しておきます。
:bulb: Laravelをインストールした場合、このスクリプトはあらかじめ記述された状態になっています。今回はLaravel Mix単体でインストールしているので手動でスクリプトを追加します。

~~~json:package.json
"scripts": {
    "dev": "npm run development",
    "development": "cross-env NODE_ENV=development node_modules/webpack/bin/webpack.js --progress --hide-modules --config=node_modules/laravel-mix/setup/webpack.config.js",
    "watch": "npm run development -- --watch",
    "hot": "cross-env NODE_ENV=development node_modules/webpack-dev-server/bin/webpack-dev-server.js --inline --hot --config=node_modules/laravel-mix/setup/webpack.config.js",
    "prod": "npm run production",
    "production": "cross-env NODE_ENV=production node_modules/webpack/bin/webpack.js --no-progress --hide-modules --config=node_modules/laravel-mix/setup/webpack.config.js"
}
~~~

上記スクリプトでは様々な環境に対応できるよう`cross-env`を利用しているそうなので、`cross-env`をインストールします。

~~~bash:terminal
yarn add cross-env --dev

// npmの場合
npm i cross-env -D
~~~

#### ワイルドカードを使えるようにする
PugやSassファイルのコンパイル時に、『ファイル名が`_`から始まるものは全て除外する』といった条件を付けたいので、それができるように`glob`をインストールします。
:warning: デフォルトだとワイルドカードは使えません。

~~~bash:terminal
yarn add glob --dev
~~~

~~~javascript:webpack.mix.js
const mix = require('laravel-mix');
const glob = require('glob'); //　<- 追加する。
~~~

#### publicpathの設定
Laravel Mix単体使用の場合、`mix.setPublicPath()`で公開用ディレクトリを指定する必要があります。
:warning: これを忘れると上手くビルド出来ません…！
今回の場合だとdistディレクトリがこれにあたりますので設定します。

~~~javascript:webpack.mix.js
const mix = require('laravel-mix');
const glob = require('glob');

const srcDir = './src';
const distDir = './dist';
const paths = {
  html: {
    src: `${srcDir}/templates/`,
    dist: `${distDir}/`,
  },
  css: {
    src: `${srcDir}/styles/`,
    dist: `${distDir}/styles/`,
  },
  js: {
    src: `${srcDir}/scripts/`,
    dist: `${distDir}/scripts/`,
  },
};

mix
  .setPublicPath(distDir) // <- Laravel Mix単体で使用する場合必要。
~~~

### 3-3. BrowserSync（ファイル変更監視＋ブラウザオートリロード）
ファイルの変更を監視してブラウザを更新してくれるBrowsesrSyncの設定をします。

~~~javascript:webpack.mix.js
mix
  .setPublicPath(distDir)
  .browserSync({
    files: 'dist/**/*',
    server: 'dist/',
    proxy: false
  })
~~~

### 3-3. JavaScript（ES5へのコンパイル、バンドル、IE11対応）
今回はIE11対応にしたいので[polyfill](https://laravel-mix.com/extensions/polyfill)をインストールします。

~~~bash:terminal
$ yarn add laravel-mix-polyfill --dev
~~~

インストール出来たら、設定を書いていきます。

~~~javascript:webpack.mix.js
require('laravel-mix-polyfill');

glob.sync(`${paths.js.src}*.js`).map(file => {
  mix.js(file, `${paths.js.dist}bundle.js`)
    .polyfill({
      enabled: true,
      useBuiltIns: "usage",
      targets: {
        "firefox": "50",
        "ie": 11
      }
    })
    .sourceMaps();
});
~~~
`src/scpripts`内の全てのJSファイルを`bundle.js`としてまとめて、`dist/scripts`へ吐き出すようになっています。


### 3-4. Sass

~~~javascript:webpack.mix.js
// Sassの設定
glob.sync(`${paths.css.src}*.scss`, {ignore: `${paths.css.src}_*.scss`}).map((file) => {
  mix
    .sass(file, paths.css.dist, {})
    .sourceMaps()
});
~~~

頭に`_`が付くscssファイルはコンパイルしないようにしています。
Laravel Mixでは、特に何も指定せずともデフォルトでautoprefixerが機能してくれます。
細かい設定が必要な場合は`options()`で指定してあげます。

~~~javascript:webpack.mix.js
glob.sync(`${paths.css.src}*.scss`, {ignore: `${paths.css.src}_*.scss`}).map((file) => {
  mix
    .sass(file, paths.css.dist, {})
    // 細かい設定をしたい場合
    .options({
      autoprefixer: {
        options: {
          browsers: [
            'last 6 versions',
          ]
        }
      }
    })
    .sourceMaps()
});
~~~

参考：https://laravel-mix.com/docs/5.0/css-preprocessors#postcss-plugins

### 3-5. Pug
まずはPugを使えるようにするためにインストールします。

~~~bash:terminal
$ yarn add laravel-mix-pug --dev
~~~

~~~javascript:webpack.mix.js
mix
  .setPublicPath(distDir)
  .browserSync({
    files: 'dist/**/*',
    server: 'dist/',
    proxy: false
  })
  .pug = require('laravel-mix-pug'); // <- 読み込みを追加

// 以下Pugの設定
glob.sync(`${paths.html.src}*.pug`, {ignore: `${paths.html.src}_*.pug`}).map((file) => {
  mix.pug(file, path.relative(paths.html.src, paths.html.dist), {
    pug: {
      pretty: true
    }
  })
  .sourceMaps()
});
~~~
scssファイル同様、`_`から始まるpugファイルはコンパイルしないようにしています。

### 3-6. おまけ
#### ビルドやコンパイルエラー発生時にだけデスクトップ通知を有効にする
`mix.disableSuccessNotifications()`で設定できます。

~~~javascript:webpack.mix.js
mix
  .setPublicPath(distDir)
  .browserSync({
    files: 'dist/**/*',
    server: 'dist/',
    proxy: false
  })
  .disableSuccessNotifications() // <- エラー時のみデスクトップ通知を表示
  .pug = require('laravel-mix-pug');
~~~


### 3-7. 完成！
~~~javascript:webpack.mix.js
const mix = require('laravel-mix');
const glob = require('glob');
require('laravel-mix-polyfill');

// paths
const srcDir = './src';
const distDir = './dist';
const paths = {
  html: {
    src: `${srcDir}/templates/`,
    dist: `${distDir}/`,
  },
  css: {
    src: `${srcDir}/styles/`,
    dist: `${distDir}/styles/`,
  },
  js: {
    src: `${srcDir}/scripts/`,
    dist: `${distDir}/scripts/`,
  },
};

mix
  .setPublicPath(distDir)
  .browserSync({
    files: 'dist/**/*',
    server: 'dist/',
    proxy: false
  })
  .disableSuccessNotifications()
  .pug = require('laravel-mix-pug');

// JS
glob.sync(`${paths.js.src}*.js`).map(file => {
  mix.js(file, `${paths.js.dist}bundle.js`)
    .polyfill({
      enabled: true,
      useBuiltIns: "usage",
      targets: {
        "firefox": "50",
        "ie": 11
      }
    })
    .sourceMaps();
});

// Sass
glob.sync(`${paths.css.src}*.scss`, {ignore: `${paths.css.src}_*.scss`}).map((file) => {
  mix.sass(file, paths.css.dist, {})
  .sourceMaps();
});

// Pug
glob.sync(`${paths.html.src}*.pug`, {ignore: `${paths.html.src}_*.pug`}).map((file) => {
  mix.pug(file, path.relative(paths.html.src, paths.html.dist), {
    pug: {
      pretty: true
    }
  })
  .sourceMaps()
});
~~~

一通りの設定ができました！早速ビルドしてみます。

~~~bash:terminal
$ yarn dev

# ファイル変更監視する場合は
$ yarn watch
~~~

この時に必要なモジュールがあれば自動でインストールされ、もう一回runするように言われるので、
その場合は再度`yarn dev(watch)`すればOKです:thumbsup:


## 4. つまづいたこと
### pugファイルの出力先がおかしい
なぜかpugファイルの出力先は`webpack.mix.js`ファイルからの相対ではなく、**ソースとなるpugファイルからの相対パスになっているよう**で、何も考えずmix.sassやmix.jsと同じノリで設定するとエラーになります。

~~~javascript:ダメな例
const srcDir = './src';
const distDir = './dist';
const paths = {
  html: {
    src: `${srcDir}/templates/`,
    dist: `${distDir}/`,
  },
  css: {
    src: `${srcDir}/styles/`,
    dist: `${distDir}/styles/`,
  },
  js: {
    src: `${srcDir}/scripts/`,
    dist: `${distDir}/scripts/`,
  },
};

glob.sync(`${paths.html.src}*.pug`, {ignore: `${paths.html.src}_*.pug`}).map((file) => {
  // これだとエラーになる
  mix.pug(file, paths.html.dist, {})
  .sourceMaps()
});
~~~

これでコンパイルしようとすると…

~~~bash:terminal
Pug Compilation Failed!

Error: ENOENT: no such file or directory, open 'project/src/templates/dist/index.html'
~~~
`project/dist`に出力したいのに、`project/src/templates/dist`なんて無いよと怒られてしまいます。

出力先を`../../${paths.html.dist}`に変えればいいのですが、もう少しスマートに対処したい！と思い調べたところ、Githubのissuesで`path.relative()`を使う方法で無事に解決できました。

~~~javascript:成功例
glob.sync(`${paths.html.src}*.pug`, {ignore: `${paths.html.src}_*.pug`}).map((file) => {
  mix.pug(file, path.relative(paths.html.src, paths.html.dist), {})
  .sourceMaps()
});
~~~

参考：https://github.com/matejsvajger/laravel-mix-pug/issues/7#issuecomment-401573734


## 5. 課題・疑問

- `setPublicPath()`の役割がいまいち理解できていない。`mix-manifest.json`は何のためのファイルなのか？
- develop環境時のみにソースマップを表示するように設定を変えてみる。
- JSについてはほとんど検証できていないので、別途babelの設定が必要かもしれない。

## 6. おわりに

つまづいたポイントもありましたが、初心者の私でも無事に環境構築することができました！
今後も分かったことなどあったら更新していきます。


[公式ドキュメント](https://laravel-mix.com/docs/5.0/installation)