【Laravel】 Mixを使用して様々なスクリプトやSassなどをビルドする

Laravel Mixは、webpackビルド手順を定義するAPIを提供するものです。Webpackを用いるよりもより手軽に書くことができます。

環境構築

まずは、Laravel Mixを使ってビルドできる状態にしましょう。内部ではwebpackが使用されているため、Node.jsが必要です。

Laravel Sailなどで導入すると、最初からビルドに必要な依存がpackage.jsonで定義されているので、package.jsonから適当に導入します。

# package.jsonにある全ての依存をインストール
npm i

webpack.mix.jsにはすでに最小限の定義が書かれているので、インストールしたら早速npm run devしましょう。webpack compiled successfullyのようなメッセージが出れば完了です。

開発初期によくあるのですが、動かないと思ったらビルドだけしてビューで読み込んでいないことがあるので、気をつけましょう。

とりあえずビルド

初期設定では、webpack.mix.js

webpack.mix.js
mix.js('resources/js/app.js', 'public/js')
    .postCss('resources/css/app.css', 'public/css');

のようになっています。ビルドすると、public/js/app.jspublic/css/app.cssが作成されます。ブラウザでこれらを読み込む際は、

<html lang="ja">
<head>
    <link rel="stylesheet" href="{{ asset('css/app.css') }}">
    <title>{{ $title }}</title>
</head>
<body>
    {{ $slot }}
    <script src="{{ asset('js/app.js') }}"></script>
</body>
</html>

のように、assetを使用することをおすすめします。assetは、public内のファイルを参照できるようにURLを自動的に組んでくれるものです。

よくあるビルド

ビルドに関する設定は、webpack.mix.jsに書かれています。

const mix = require('laravel-mix');

/*
...
 */

mix.js('resources/js/app.js', 'public/js')
    .postCss('resources/css/app.css', 'public/css', [
        //
    ]);

なんとなくresources/js/app.jsresources/css/app.cssがビルドの起点で、それぞれ出力先が指定されているなあ、ということはわかると思います。

sassをビルドする

しばしばcssではなくSassを使用することがあると思います。その場合、postCssの代わりにsassします。

webpack.mix.js
mix.js('resources/js/app.js', 'public/js')
    .sass('resources/css/app.scss', 'public/css')
    ;
resources/css/app.scss
.hoge {
    .fuga {
        color: red;
    }
}

まずは何も導入せずにnpm run devします。そうすると不足しているパッケージを導入するためのコマンドが表示されるので、表示されたら再度npm run devしてください。

Additional dependencies must be installed. This will only take a moment.
 
Running: npm install sass-loader@^12.1.0 sass resolve-url-loader@^5.0.0 --save-dev --legacy-peer-deps

Finished. Please run Mix again.
npm run dev

あとはpublicディレクトリにファイルが出力されているか確認して実際に表示してみましょう。publicディレクトリ内のファイルのURLは、asset関数を用いることを推奨します。

<head>
    <!-- asset関数でpublicディレクトリからのURLを取得 -->
    <link rel="stylesheet" href="{{ asset('css/app.css') }}">

    <title>{{ $title }}</title>
</head>

Vueの導入とビルド

laravel/uiの導入

app.jsなどの一部のファイルが書き換わるため、すでに開発進行している場合は省略して大丈夫です。

Laravelでのvueの導入は、直接npm i vue等をしてもよいのですが、laravel/uiを経由すると、最小限のサンプルも入っているため楽に導入できます。

# laravel/uiの導入
composer require laravel/ui

# vue を有効化
php artisan ui vue

最後にPlease run "npm install && ..."と表示されれば、それを実行してVueの導入は完了です。これと同時にpackage.jsonへの依存の追加や、webpack.mix.js.vue()の追加が行われています。

npm run devすると、おそらく追加のパッケージの導入が行われるので、その際は再度npm run devします。

この手法ではvue2が導入されます。vue3を使用する場合は、npm i vue@latestする必要があります。

vue-loadervue-template-compilerも古いバージョンなので、npm i vue-loader@latest等で更新しておきましょう。

ビルドする

webpack.mix.jsで、ビルド対象となるjsに.vue()を追加します。laravel/uiを使用した場合は既についていると思いますが、手動で導入した場合やエントリを追加する場合は付け忘れないようにしましょう。

mix.js('resources/js/app.js', 'public/js')
    .vue();

続いて、laravel/uiで導入した場合のapp.jsを見ると、コードが追加されています。

window.Vue = require('vue').default;

/**
...
 */

// const files = require.context('./', true, /\.vue$/i)
// files.keys().map(key => Vue.component(key.split('/').pop().split('.')[0], files(key).default))

Vue.component('example-component', require('./components/ExampleComponent.vue').default);

/**
...
 */

const app = new Vue({
    el: '#app',
});

コメントアウトされたコードが追加されています。これを利用すると全てのコンポーネントを読み込め、自動的に適切な名前をつけて使えるようにますよ、と言っています。スクリプトは1エントリないし数エントリで済ませ、SPAでなくても複数のページ分のコンポーネントを全部読み込みといったことは実際にあるため、その場合はこれを利用するのはありです。

vueコンポーネントを読み込んだら、

<div id="app">
    <example-component></example-component>
</div>

をビューに書いて、npm run devでスクリプトをビルドして表示できるか試してみましょう。

laravel/uiを使用していない場合かつ必要なパッケージが入っていない場合、裏で自動的に必要なパッケージが導入されます。その場合は再度npm run devが必要です。

vue3の場合、require等を書き換える必要があります。

const { createApp } = require('vue');
const ExampleComponent = require('./components/ExampleComponent.vue').default;

const app = createApp({
    components: {
        ExampleComponent
    }
}).mount("#app");

Reactの導入とビルド

laravel/uiの導入

app.jsなどの一部のファイルが書き換わるため、すでに開発進行している場合は省略して大丈夫です。

Laravelでのvueの導入は、直接npm i react等をしてもよいのですが、laravel/uiを経由すると、最小限のサンプルも入っているため楽に導入できます。

# laravel/uiの導入
composer require laravel/ui

# react を有効化
php artisan ui react

最後にPlease run "npm install && ..."と表示されれば、それを実行してReactの導入は完了です。これと同時にpackage.jsonへの依存の追加や、webpack.mix.js.react()の追加などが行われています。

npm run devすると、おそらく追加のパッケージの導入が行われるので、その際は再度npm run devします。

最新に近いバージョンが導入されると思いますが、ベータ版などを使用したい場合、reactreact-dom@babel/preset-reactそれぞれnpm i react@betaなどで更新してください。

ビルド

webpack.mix.jsで、ビルド対象となるjsに.react()を追加します。laravel/uiを使用した場合は既についていると思いますが、エントリを追加する場合は付け忘れないようにしましょう。

mix.js('resources/js/app.js', 'public/js')
    .react();

あとはnpm run devでビルドし、<div id="example"></div>をビューに追加して表示できるか試してみましょう。

Typescriptでビルド

Typescriptを使用したい場合、.ts()を付けるだけです。

mix.ts('resources/js/app.ts', 'public/js')
    .react();

ここでnpm run devすると必要なパッケージが自動でインストールされます。

続いて、tsconfig.jsonを用意する必要があります。Laravelが入っているディレクトリで、

npx tsc --init

を実行するとtsconfig.jsonが作成されます。この状態で改めてnpm run devするとビルドできます。

URLの処理

CSSをコンパイルする際に、url('...')で相対パスで書かれているものは、resourcesから自動的に該当の画像をpublicにコピーし、URLの末尾にはキャッシュ対策としてランダムな文字列が付け加えられます。

ビルド前
.hoge {
    .fuga {
        background-image: url("../img/hoge.png");
    }
}
ビルド後
.hoge .fuga {
    background-image: url(/images/hoge.png?faad03f50cf898542b5471e8e972d0ed);
}

url("/hoge/fuga.jpg")のように絶対パスの場合は書き換わらず、かつ画像のコピーもされません。https://.../hoge/fuga.pngが直接参照されます。

もしこのURLが自動的に書き換わるのと画像が自動でコピーされる挙動を無効化する場合、webpack.mix.jsoptionsprocessCssUrls: falseを付けて無効化します。

mix.js('resources/js/app.js', 'public/js')
    .sass('resources/sass/app.scss', 'public/css')
    .options({
        processCssUrls: false
    });

ソースマップ

ソースマップを有効化することで、デバッグ実行時にビルドされたスクリプトとビルド前のスクリプトのコードの対応を紐付けてくれます。これにより、ステップ実行をビルド前のスクリプトを使用して可能になるなど、デバッグ効率が格段に上がります。

ソースマップを有効化するには

wepack.mix.js
mix.js('resources/js/app.js', 'public/js')
    .sourceMaps();

のように.sourceMaps()を付けるだけです。npm run watchし直したら、動作しているかブラウザで試してみましょう。

ソースマップ適用時のデバッグ実行の例

もしソースマップのスタイルを変更したい場合、sourceMapsのメソッドの引数に設定します。第1引数はProduction環境の場合にソースマップを作成するかどうか、第2引数はDevelop環境でのソースマップスタイル、第3引数はProduction環境でのソースマップスタイルです。

// prodでもソースマップを作成し、devではeval-source-mapを、prodではsource-mapを使用するように設定
mix.js('resources/js/app.js', 'public/js')
    .sourceMaps(true, 'eval-source-map', 'source-map');

ソースマップのスタイル一覧はWebpack公式のページを参照するのが手っ取り早いです。

https://webpack.js.org/configuration/devtool/#devtool

Webpackの設定をオーバーライド

mix.webpackConfigを使用して、普通にWebpackの設定を記述することもできます。ここに書くと、設定がオーバーライド (書いた部分だけ上書きや追加) されます。

mix.ts('resources/js/app.ts', 'public/js')
    .webpackConfig({
        resolve: {
            alias: {
                "@": __dirname + "/resources/js"
            },
            extensions: ["tsx", "jsx"]
        }
    });

aliasはmix.alias({/* ... */})でも同じ書き方で可能です。

mix.ts('resources/js/app.ts', 'public/js')
    .alias({
        "@": __dirname + "/resources/js"
    });

バージョニング

普通にビルドすると、普通のファイル名で出力されます。何が問題かというと、そのままではキャッシュが破棄されるまでブラウザやCDNのキャッシュが更新されず、古いスクリプトを利用してしまうことです。

これを回避するには、.version()をつけて、バージョン番号を付与するように構築します。

mix.ts('resources/js/app.ts', 'public/js')
    .sass('resources/sass/app.scss', 'public/css')
    .version();

バージョニングを有効化してもファイル名自体はそのままですが、asset関数ではなくmix関数を使用することで、ランダム文字列を付与したURLを使用されます。ビルドするたびに変化するため、キャッシュ対策になります。

<head>
    <link rel="stylesheet" href="{{ mix('css/app.css') }}">
</head>
<body>
    <script src="{{ mix('js/app.js') }}"></script>
</body>
</html>

この時生成されるファイルのURLを変更したい場合 (例えばCDNのurlを指すなど)、config/app.phpmix_urlを追加します。

config/app.php
use Illuminate\Support\Facades\Facade;

return [
    // ...
    'mix_url' => env('MIX_ASSET_URL', null),
]; 

実際の値は.env

.env
MIX_ASSET_URL="https://cdn.example.com"

のように書きます。

保存時に自動ビルドする

ビルド対象のファイルを保存した際に自動的にビルドし直す事ができます。

# 保存時に自動的にビルド
npm run watch

これだけで保存時に自動的にビルドされます。

これをホットリロードといいます。

もしdocker内でビルドしている場合、うまくホットリロードが効かない場合があります。その場合、watch-pollします。

npm run watch-poll

ここで、ビルド対象のファイルに変更が加わると自動的にビルドし直されますが、webpack.mix.jsは変更してもビルドし直されません。改めてnpm run watchする必要があります。

Browsersync

Browsersyncを使用すると、watchで再ビルドされると同時に、ブラウザで自動でリロードされます。

まずはwebpack.mix.jsで有効化しましょう。

mix.browserSync('laravel.test');

が、docker内などで構築している場合はこれだけではうまく行かないので、他にも設定を入れる必要があります。

最低限、hostproxywatchOptionsの3つが必要でした。

mix.browserSync({
    host: 'localhost',
    proxy: {
        target: "http://localhost",
        ws: true
    },
    watchOptions: {
        usePolling: true,
        interval: 100
    },
});

次に、dockerで環境構築している場合は、laravelが入っているコンテナで3000、3001、3002ポートを開放します。

laravel.test:
    // ...
    ports:
        - '${APP_PORT:-80}:80'
        - '3000:3000'
        - '3001:3001'
        - '3002:3002'

書いたら、コンテナを立ち上げ直し、npm run watchwatch-pollをします。

済んだら、http://localhost:3000にアクセスしてページを表示し、適当なビルド対象のファイルを書き換えてみましょう。ブラウザのページが自動的にリロードされるはずです。

もしviewやcontrollerなどのファイルの変更も検知したい場合、filesにパス一覧を書きます。

mix.browserSync({
    // ...
    files: [
        './resources/**/*',
        './app/**/*'
    ],
    // ...
});

この場合はかなりのファイル数が検知対象となって重い可能性があるので、もう少し対象を絞っても良いかもしれません。

管理画面

http://localhost:30013002にアクセスすると、Browsersyncの管理画面を表示できます。とはいえ、そこまでたいした設定は並んでいないのでそこまで使用しないと思います。

CSSのグリッドレイアウトのアウトラインなど、一部使えそうなものはありますが、ブラウザでF12する方が情報も多くて楽です。

まとめ

Laravel Mixを使用すると、今まではwebpackで長々と設定を書いていたのを、簡単に書くことができるようになります。ReactやVueも複雑な設定を書かずに済むため、積極的にAPIを使用していきましょう。

laravel mix thumb

役に立ったらシェアしよう!