Laravelを使わなくても利用できるlaravel-mixでTypeScriptとSassの簡単なコンパイル環境を作ろう

# はじめに
フロントでTypeScriptやSass、テンプレートエンジンなどをコンパイルするときgulpやwebpackがよく使われます。
個人的な印象ですが、両者とも記述が複雑化しやすく長文化しやすく管理が大変そうです。
loaderの順番を気にしたり設定オプションも盛りだくさんで、時間をかけて理解しないといけないので積極的に進めたくはありません。
そこでおすすめしたいのが**laravel-mix**です。

https://laravel-mix.com/

ざっくり言うとlaravel-mixはwebpackを簡単に書けるようにしたものです。
webpackを使いやすくしてくれるので当然webpackのバンドルやコンパイルなどの機能を持っています。
その名の通り「Laravel」使うんじゃないの？と思われるかもしれませんが、Laravelを使わなくてもlaravel-mixは使えます！
Laravel知らないよって人でも大丈夫です。

本記事ではlaravel-mixを使ってTypeScriptとSassをコンパイルするような環境を作っていきます。


# 環境を作ろう
## 準備
まずは練習用のディレクトリを作ってlaravel-mixのファイルを編集できるところまでやってみます。
`mix-practice`というディレクトリを作って`npm init -y`で`package.josn`ファイルを作成します。
```bash:コマンドライン
mkdir mix-practice
cd mix-practice
npm init -y
```
これでlaravel-mixをインストールできるようになりました。
いざインストール！
```bash:コマンドライン
npm install laravel-mix --save-dev
```
インストールは開発環境だけでよいので`--save-dev`をお忘れなく。
インストールできたらlaravel-mixを記述するファイルを作ります。
webpackのファイル名が`webpack.config.js`であるように
laravel-mixのファイル名は`webpack.mix.js`です。
```bash:コマンドライン
touch webpack.mix.js
```
ではこのファイルの先頭で先ほどインストールしたlaravel-mixを読み込ませておきます。
```javascript:webpack.mix.js
const mix = require('laravel-mix');
```

ここまででディレクトリ構成はこうなってます。
```text:ディレクトリ構成
mix-practice
├─ node_modules/
├─ package-lock.json
├─ package.json
└─ webpack.mix.js
```

## laravel-mix編集(TypeScript)
次は実際にlaravel-mixの記述をしてみてwebpackとの違いを体験してみましょう。
ではwebpack.mix.jsに書いていきましょう。
まずはTypeScriptです。
```javascript:webpack.mix.js
const mix = require('laravel-mix');

mix.setPublicPath('dist');
mix.ts('src/ts/index.ts', 'dist/js');
```
とりあえずこれだけです。一瞬すぎる…
`.setPublicPath()`でコンパイル先(公開するディレクトリ)を指定します。
`.ts()`の第一引数にはコンパイル元を指定します。
バンドルしたいときは配列で指定します。
```javascript:例
mix.ts(['src/ts/index.ts', 'src/ts/sample.ts'], 'dist/js')
```
第二引数には出力先を指定します。
`src/ts/index.ts`をコンパイルして`dist/js`に出力しなさいということですね。

それでは`webpack.mix.js`を実行させるために`package.json`の`scripts`を書き換えましょう。
```json-doc:package.json
"scripts": {
  "build": "mix",
  "watch": "mix watch",
  "production": "mix --production"
},
```
これでひとまずTypeScriptをコンパイルできるところまできましたが
肝心のtypescriptやts-loaderをインストールしてません。
手動で
```bash:コマンドライン
npm install typescript ts-loader --save-dev
```
としてもいいのですがlaravel-mixに任せると`webpack.mix.js`の内容読んで依存解決して自動にインストールまでしてくれます。
ということで先のスクリプトを実行してみます。
```bash:コマンドライン
npm run build
```
実行後に以下のように出力されて
```text:コマンドライン
Additional dependencies must be installed. This will only take a moment.
Running: npm install ts-loader typescript --save-dev --legacy-peer-deps
Finished. Please run Mix again.
```
`package.json`にts-loaderとtypescriptが追加されています。
ちなみにここで`Please run Mix again.`の指示通りもう一回`npm run build`としても`src/ts/index.ts`がないのでエラーになります。

それでは`tsconfig.json`と`index.ts`を作りましょう。
まず`tsconfig.json`を作成します。
```bash:コマンドライン
tsc --init
```
`tsconfig.json`の設定は触り始めたらキリがない(嘘、勉強不足で把握しきれてない)ので設定しておけば良さげなところだけをピックアップしておきます。
```json-doc:tsconfig.json
{
  "compilerOptions": {
    "target": "es2015",
    "lib": ["DOM", "ESNext"],
    "module": "ES2015",
    "sourceMap": true,
    "strict": true,
  }
}
```
次にコンパイル元のディレクトリ`src/ts/`とファイル`index.ts`を作成します。

```text:ディレクトリ構成
mix-practice
├─ node_modules/
├─ src
│  └─ ts
│     └─ index.ts
├─ package-lock.json
├─ package.json
└─ webpack.mix.js
```
```typescript:index.ts
const greet = (text: string) => {
  console.log(text);
}

greet('hello, world');
```

それではコンパイルしてみます。
```bash:コマンドライン
npm run build
```
下記が表示されれば成功です。
```
webpack compiled successfully
```
`dist/js/index.js`ができていることを確認しましょう。
```text:ディレクトリ構成
mix-practice
├─ dist
│  ├─ js
│  │  └─ index.js
│  └─ mix-manifest.json
├─ node_modules/
├─ src
│  └─ ts
│     └─ index.ts
├─ package-lock.json
├─ package.json
└─ webpack.mix.js
```

## laravel-mix編集(Sass)
次はSassです。
これも一瞬です。
```javascript:webpack.mix.js
const mix = require('laravel-mix');

mix.setPublicPath('dist');
mix.ts('src/ts/index.ts', 'dist/js');
mix.sass('src/scss/style.scss', 'dist/css/style.css')
  .options({
    processCssUrls: false
  });
```
`options({processCssUrls: false})`はcssで使う`background-image: url()`などの`url()`に関する設定です。

scssのコンパイル元と出力先は別の場所です。
そのため以下のようなディレクトリ構成で
```text:例
root
├─ dist
│  └─ css
│     └─ style.css
└─ src
   ├─ img
   │  └─ sample.png
   └─ scss
      └─ style.scss
```
scssディレクトリからの相対パス記述で
```css:style.scss
background-image: url('../img/sample.png');
```
のようにしてしまうとコンパイル後に
```css:style.css
background-image: url('../images/sample.png?3a0834378861f9a4523f166fed2c0be9');
```
のように書き変わってしまい画像をうまく読み込めません。
そこで`processCssUrls: false`にしておくと`url()`の中身はscssに記述したままでコンパイルされます。
そのため画像は`dist/img`の方にコピーするようにしましょう。

手動でコピーするのは面倒なのでこれもlaravel-mixにやってもらいます。
```javascript:webpack.mix.js
const mix = require('laravel-mix');

mix.setPublicPath('dist');
mix.ts('src/ts/index.ts', 'dist/js');
mix.sass('src/scss/style.scss', 'dist/css/style.css')
  .options({
    processCssUrls: false
  });
mix.copy('src/img', 'dist/img');
```
これでimgディレクトリごとdistにコピーしてくれます。
あとは適当にscssファイルと画像ファイルを用意します。
```scss:style.scss
h1 {
  color: red;
  &.color-blue {
    color: blue;
  }
}
div {
  width: 100%;
  height: 250px;
  background-image: url('../img/sample.png');
}
```

```text:ディレクトリ構成
mix-practice
├─ dist
│  ├─ js
│  │  └─ index.js
│  └─ mix-manifest.json
├─ node_modules/
├─ src
│  ├─ img
│  │  └─ sample.png
│　├─ scss
│  │  └─ style.scss
│　└─ ts
│  　 └─ index.ts
├─ package-lock.json
├─ package.json
└─ webpack.mix.js
```
これでbuildしてみましょう。
```bash:コマンドライン
npm run build
```
最初はTypeScriptの時と同じように(未インストールの場合)sassとsass-loaderがインストールされます。
必要なパッケージが揃えばbuildができます。
コンパイルが成功すれば以下のようなディレクトリ構成になります。
```text:ディレクトリ構成
mix-practice
├─ dist
│  ├─ css
│  │  └─ style.css
│  ├─ img
│  │  └─ sample.png
│  ├─ js
│  │  └─ index.js
│  └─ mix-manifest.json
├─ node_modules/
├─ src
│  ├─ img
│  │  └─ sample.png
│　├─ scss
│  │  └─ style.scss
│　└─ ts
│  　 └─ index.ts
├─ package-lock.json
├─ package.json
└─ webpack.mix.js
```

確認のためにdist直下に`index.html`を入れます。
確認だけなので最低限だけ書いてます。
```html:index.html
<!DOCTYPE html>
<html lang="ja">
<head>
  <meta charset="UTF-8">
  <link rel="stylesheet" href="css/style.css">
  <title>Document</title>
</head>
<body>
  <h1>赤です</h1>
  <h1 class="color-blue">青です</h1>
  <div></div>
  <script src="js/index.js"></script>
</body>
</html>

```
ブラウザで確認して下記OKなら完了です！お疲れ様です！
-  「赤です」が赤
-  「青です」が青
-  　画像が表示
-  　デベロッパーツールのコンソールに「hello, world」が表示

## mix-manifest.jsonについて
いつの間にかしれっと`mix-manifest.json`というファイルが出力されています。
これはファイルを変更してもキャッシュが効いてしまって反映されないというのを防ぐキャッシュ回避用のファイルです。
Laravelを使うとscriptファイル呼び出し時の`mix()`関数でクエリパラメータを生成してキャッシュ回避してくれるようですが
今回のようにLaravelを使わない環境だとあまり意味がない(`mix-manifest.json`内のファイルにクエリパラメータを追加するプログラムを作るなどしないと)ので出力自体をさせないか、gitignoreなどで対処するのがよいでしょう。
ちなみに私は出力させないように以下のような設定をしています。
```javascript:webpack.mix.js
const fs = require('fs');
mix.then(() => {
    fs.unlinkSync('dist/mix-manifest.json')
  });
```


# さいごに
larave-mixだと簡単に書けそうな感じがしたでしょうか？
今回は`webpack.mix.js`の中で記述を分けて書きましたがメソッドチェーンにして一気に書くこともできます。
```javascript:webpack.mix.js
const mix = require('laravel-mix');
const fs = require('fs');

mix.setPublicPath('dist')
  .ts('src/ts/index.ts', 'dist/js')
  .sass('src/scss/style.scss', 'dist/css/style.css')
  .options({
    processCssUrls: false
  })
  .copy('src/img', 'dist/img');

mix.then(() => {
    fs.unlinkSync('dist/mix-manifest.json')
  });
```

laravel-mixでは他にもVueやReactなどといったフロントフレームワーク、ライブラリの他、EJSやNunjucksなどのテンプレートエンジン、ファイルの変更を監視してブラウザをホットリロードする機能(BrowserSync)、sourceMapなど色々追加できます。
これで面倒なwebpackやgulpから解放されましょう。

## 参照

https://laravel-mix.com/docs/6.0/api

https://readouble.com/laravel/8.x/ja/mix.html