Laravel MixでTailwindとSassをビルドする設定ハンズオン

概要

Laravel Mix は webpack を薄くラップしたアセットビルドツールで、webpack.mix.js に数行書くだけで JS/CSS のトランスパイル・バンドル・ハッシュ付与ができます。本記事では、1つのプロジェクトで Tailwind CSS(ユーティリティ)と Sass(独自コンポーネントの構造的なスタイル)を併用 し、両者を1本の CSS にビルドするところまでをハンズオン形式で進めます。

「Tailwind を入れたら Sass の @use が効かない」「@apply が未定義だと怒られる」「本番で未使用クラスが落ちない」といった、併用時にハマりやすいポイントを順に潰していきます。

なお、Laravel Mix は現在メンテナンスモードで、新規プロジェクトは Vite が公式デフォルトです。ただし既存案件の多くは今も Mix で動いており、保守・改修の文脈で設定を触る機会は依然多いため、本記事はその「既存 Mix プロジェクトを触る」想定としています。

環境

  • Laravel 9 系(laravel-mix 同梱の既存プロジェクトを想定)
  • Node.js 18 LTS / npm 9
  • laravel-mix ^6.0
  • tailwindcss ^3.x
  • sass / sass-loader

バージョンは記事執筆時点のものです。Tailwind v3 系と Mix v6 系の組み合わせを前提としています(v4 系は設定方式が変わるため本記事の対象外)。

仕組み:なぜ併用でハマるのか

Laravel Mix における CSS の処理パイプラインは、ざっくり次の順序です。

.scss → sass-loader(Sass→CSS) → postcss-loader(Tailwind / Autoprefixer)→ 出力

最重要ポイントは Tailwind が PostCSS プラグインとして動く ことです。つまり Sass のコンパイルが終わった後の CSS に対して Tailwind が走ります。@tailwind base; のようなディレクティブは Sass にとってはただの通過点で、Sass はこれを素通しし、後段の PostCSS で Tailwind が展開します。

したがって Mix の設定で PostCSS のプラグイン配列に tailwindcss を明示的に渡す 必要があります。これを忘れると @tailwind が展開されません。@apply が落ちるのも同じ理屈で、@apply は Tailwind(PostCSS 段)が解決するため、Sass 段では素通しされます。

手順

1. パッケージの導入

npm install -D tailwindcss@^3 postcss autoprefixer
npm install -D sass sass-loader

laravel-mix 本体はプロジェクトに既にある前提です。無ければ npm install -D laravel-mix も追加します。

2. Tailwind の設定ファイルを生成

npx tailwindcss init

生成された tailwind.config.js の content に、クラスを使うテンプレートのパスを列挙します。ここが未使用クラス削除(purge)の要です。

// tailwind.config.js
/** @type {import('tailwindcss').Config} */
module.exports = {
  content: [
    './resources/**/*.blade.php',
    './resources/**/*.js',
    './resources/**/*.vue',
  ],
  theme: {
    extend: {},
  },
  plugins: [],
};

3. エントリ CSS を用意する

resources/sass/app.scss を作り、Tailwind のディレクティブと自前の Sass を同居させます。

// resources/sass/app.scss

// Tailwind のレイヤー(PostCSS 段で展開される)
@tailwind base;
@tailwind components;
@tailwind utilities;

// ここから下は自前の Sass
@use 'variables' as v;
@use 'components/card';

body {
  font-family: v.$font-base;
}
// resources/sass/_variables.scss
$font-base: 'Helvetica Neue', Arial, sans-serif;
$color-primary: #2563eb;
// resources/sass/components/_card.scss
.card {
  // @apply は Tailwind(後段)が解決する。Sass 段では素通し。
  @apply rounded-lg shadow p-4;

  // Sass ならではのネスト・変数も併用できる
  &__title {
    font-weight: 700;
  }
}

@use は Sass モジュールシステムで、古い @import の後継です。変数は名前空間(ここでは v.)経由で参照します。

4. webpack.mix.js を設定する

ここが本記事の核心です。.sass() で Sass をコンパイルし、.options() の postCss に Tailwind プラグインを明示的に渡します。

// webpack.mix.js
const mix = require('laravel-mix');

mix
  .js('resources/js/app.js', 'public/js')
  .sass('resources/sass/app.scss', 'public/css')
  .options({
    postCss: [
      require('tailwindcss'),
      require('autoprefixer'),
    ],
  });

if (mix.inProduction()) {
  mix.version(); // ハッシュ付与でキャッシュバスティング
}

.sass() の第1引数が .scss でも問題ありません(sass-loader が拡張子で判定)。postCss に require('tailwindcss') を渡すことで、Sass コンパイル後の CSS に Tailwind が適用されます。

postcss.config.js を別ファイルで置く方法もありますが、Mix の .options({ postCss: [...] }) と二重に設定するとどちらが効くか分かりにくくなります。どちらか一方に寄せるのが事故を防ぐコツです。本記事では Mix 側に寄せています。

5. ビルドスクリプト

package.json の scripts は Mix 標準のままで動きます。

{
  "scripts": {
    "dev": "mix",
    "watch": "mix watch",
    "prod": "mix --production"
  }
}

6. Blade から読み込む

{{-- resources/views/layouts/app.blade.php --}}
<link rel="stylesheet" href="{{ mix('css/app.css') }}">
<script src="{{ mix('js/app.js') }}" defer></script>

mix() ヘルパーは mix.version() が付けたハッシュを mix-manifest.json から引いてくれるので、本番のキャッシュバスティングが自動で効きます。

ビルドを実行する

# 開発(非圧縮)
npm run dev

# ファイル監視
npm run watch

# 本番(圧縮 + 未使用クラス削除 + ハッシュ付与)
npm run prod

本番ビルドでは Tailwind v3 の JIT が content に列挙したファイルを走査し、実際に使われているクラスだけを出力します。開発ビルドでは全ユーティリティが含まれ CSS が大きくなりますが、これは想定どおりで、--production で削られます。

確認方法

1. Tailwind が展開されているか

ビルド後の public/css/app.css にユーティリティ由来の定義が含まれるか確認します。

grep -c "border-radius" public/css/app.css

0 なら PostCSS に Tailwind が渡っていません(webpack.mix.js の postCss を見直す)。

2. @apply が解決されているか

.card の定義に border-radius / box-shadow / padding が展開されていれば成功です。@apply rounded-lg shadow p-4; が文字列のまま残っていたら失敗です。

3. 本番で未使用クラスが落ちているか

npm run dev と npm run prod で public/css/app.css のサイズを比較します。prod で桁違いに小さくなっていれば purge が効いています。

4. ハッシュ付与

npm run prod 後に public/mix-manifest.json を開き、app.css にハッシュが付いていれば OK です。

注意点

  • content のパス漏れに注意。 Blade だけ書いて JS/Vue を書き忘れると、JS 側で動的に付けるクラスが本番で消えます。動的クラス名は文字列連結('bg-' + color)で組むと purge に検出されないため、完全なクラス名を書くか safelist を使います。
  • @apply に自作 Sass の変数は渡せません。 @apply は Tailwind のクラス名しか受け付けません。Sass 変数を使いたい箇所は通常の CSS プロパティで書きます。
  • Node のバージョン差でビルドが落ちることがあります。 Mix v6 + sass-loader の組み合わせは Node のメジャーバージョンに敏感です。.nvmrc を置いてチームで固定するのが安全です。
  • 新規なら Vite を検討。 本記事は既存 Mix 資産の保守前提です。新規開発なら公式デフォルトの Vite のほうが設定が素直で、Tailwind 公式ドキュメントも Vite 前提が増えています。

まとめ

Laravel Mix で Tailwind と Sass を併用する勘所は、たった1つに集約されます。

  • Tailwind は PostCSS プラグイン。 Sass コンパイルの「後段」で動くので、webpack.mix.js の .options({ postCss: [require('tailwindcss'), ...] }) に明示的に渡す。
  • @tailwind / @apply は Sass にとって素通し対象で、評価は Tailwind 段。だから .scss に混在させて問題ない。
  • 本番の肥大化対策は tailwind.config.js の content 指定が全て。動的クラス名は safelist で救う。

この3点を押さえれば、ユーティリティファーストの速さと、Sass の構造的な書きやすさを1本のパイプラインで両立できます。

\ 最新情報をチェック /

コメントを残す

このサイトはスパムを低減するために Akismet を使っています。コメントデータの処理方法の詳細はこちらをご覧ください。