前書き

はじめまして、プロダクツ本部 プロダクツエンジニアグループ のarakyawaです。Webikeのグローバルサイトで扱う商品データの運用をメインで行っています。
最近LaravelでWEBアプリを作っているのですが、CSSを書くのが億劫に感じています。
社内でしか使わないツールだけど見た目はきれいにしておきたい...そんな葛藤を抱えながらツールを作っています。
Bootstrapは使ったことがあるので今回もそれを使おうかなと思っていました。しかし、最近ではTailwind CSSというものが流行っているので少し調べてみました。

Tailwind CSSとは?

Rapidly build modern websites without ever leaving your HTML.
A utility-first CSS framework packed with classes like flex, pt-4, text-center and rotate-90 that can be composed to build any design, directly in your markup.

Tailwind CSSより

ユーティリティファーストという言葉の通り、CSSファイルを編集することなく見た目をいじれるみたいです。
通常CSSを当てたいところにクラス名やidを設定して、CSSファイルの中にプロパティを書くと思います。
しかし、Tailwind CSSだとクラス名に対応するプロパティのみをCSSファイルに書き出す事ができます。これによってCSSを直接書くことなく、必要最低限のCSSしか生成されないのでファイル量の削減にも繋がります。

仕組み


上の画像のように、CSSを当てたい部分にクラスを入れたあと、ビルドをするとCSSが生成されます。書けるクラス名は決まっているのでチートシートを参考にすると良いかもしれません。

Tailwind CSSとLaravelの開発環境構築

Tailwind CSSはビルドが必要ということなのですが、実際の動きがわからないので開発環境を作ってみました。Windows11とDocker Desktopでローカル環境に作成します。
用意するファイルとフォルダはこんな感じです。

  • Project
    • app
    • docker
      • nginx
        • default.conf
      • Dockerfile
    • docker-compose.yml
default.conf
server {
    listen 80;
    root /var/www/app/public;
    index index.php;
    location / {
        try_files $uri $uri/ /index.php?$query_string;
    }
    location ~ \.php$ {
        fastcgi_pass app:9000; 
        fastcgi_param SCRIPT_FILENAME $realpath_root$fastcgi_script_name;
        include fastcgi_params;
    }
}

nginx用の設定ファイルです。

Dockerfile
FROM php:8.1.11-fpm 
COPY --from=composer:latest /usr/bin/composer /usr/bin/composer
RUN apt-get update && apt-get install -y \
    git \
&& docker-php-ext-install pdo_mysql
RUN curl -sL https://deb.nodesource.com/setup_18.x | bash -
RUN apt-get install -y nodejs
WORKDIR /var/www/app

php用の設定ファイルです。
php8.1.11、composerは最新版、nodeは18.xになっています。

docker-comopse.yml

8080ポートを使います。

version: "3.8"
services:
  app:
    build: ./docker
    volumes:
      - ./app:/var/www/app
  nginx:
    image: nginx
    ports:
      - 8080:80
    volumes:
      - ./app:/var/www/app
      - ./docker/nginx/default.conf:/etc/nginx/conf.d/default.conf

Projectディレクトリに移動したら

docker compose up -d

↑を実行します。
最初は時間がかかりますが、以下のような表示になればOKです。

次にappのコンテナの中に入り、Laravelのプロジェクトを作成します。

docker compose exec app bash
root@1234567879:/var/www/app# composer create-project "laravel/laravel" . --prefer-dist

appディレクトリ配下にファイルが作成されていて、localhost:8080にアクセスしてこのような画面になっていたら成功です。

storageフォルダに権限がないので付与してあげます。

root@1234567879:/var/www/app# chmod -R 777 storage

もう一度localhost:8080にアクセスするとLaravelの画面が表示されます。

viteからwebpackへ戻す

LaravelのビルドツールはLaravel Mixだと思っていたのですが最近はviteというものに変わっているそうです。
うまく動かすことが出来なかったのでwebpackに戻します。
コマンドを実行してlarvel-mixをインストールします。

root@1234567879:/var/www/app# npm install --save-dev laravel-mix

エディター上でvite.config.jsというファイルを削除してwebpack.mix.jsというファイルを作成します。

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

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

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

ファイルはこのように変更してください。

package.json
{
    "private": true,
    "scripts": {
        "dev": "npm run development",
        "development": "mix",
        "watch": "mix watch",
        "watch-poll": "mix watch -- --watch-options-poll=1000",
        "hot": "mix watch --hot",
        "prod": "npm run production",
        "production": "mix --production"
    },
    "devDependencies": {
        "axios": "^0.27",
        "laravel-mix": "^6.0.49",
        "laravel-vite-plugin": "^0.6.0",
        "lodash": "^4.17.19",
        "postcss": "^8.1.14",
        "vite": "^3.0.0"
    }
}

package.jsonのscriptsからviteの記述を消してこのようにします。

.env

- VITE_PUSHER_APP_KEY="${PUSHER_APP_KEY}"
- VITE_PUSHER_HOST="${PUSHER_HOST}"
- VITE_PUSHER_PORT="${PUSHER_PORT}"
- VITE_PUSHER_SCHEME="${PUSHER_SCHEME}"
- VITE_PUSHER_APP_CLUSTER="${PUSHER_APP_CLUSTER}"
+ MIX_PUSHER_APP_KEY="${PUSHER_APP_KEY}"
+ MIX_PUSHER_APP_CLUSTER="${PUSHER_APP_CLUSTER}"

viteに関する記述は削除してLaravel Mix用の変数を追加します。

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

welcome.blade.phpにLaravel Mixでビルドしたファイルを読ませるように記述します。
そしてviteをアンインストールします。

root@1234567879:/var/www/app# npm remove vite laravel-vite-plugin

Tailwind CSSのインストール

Laravel Mix同様にnpmでインストールします。

root@1234567879:/var/www/app# npm install -D tailwindcss@latest postcss@latest autoprefixer@latest
root@1234567879:/var/www/app# npx tailwindcss init

tailwind.config.jsというファイルが生成されます。Tailwind CSS用の設定ファイルです。

tailwind.config.js
/** @type {import('tailwindcss').Config} */
module.exports = {
  purge: [
    './resources/**/*.blade.php',
    './resources/**/*.js',
   ],
   darkMode: false, // or 'media' or 'class'
   theme: {
     extend: {},
   },
   variants: {
     extend: {},
   },
   plugins: [],
 }

bladeとjsファイルでTailwind CSSを参照できるようにします。

webpack.mix.js
mix.js("resources/js/app.js", "public/js")
  .postCss("resources/css/app.css", "public/css", [
    require("tailwindcss"),
  ]);

先程作ったwebpack.mix.jsを編集します。

app/resources/css/app.css
@tailwind base;
@tailwind components;
@tailwind utilities;

Tailwind CSSを読み込むようにします。
やっと準備ができました!

Tailwind CSSを使ってみる

welcome.blade.phpを編集してみましょう。body部分は全部消してwebikeの商品ページと似たものを作ってみます。

welcome.blade.php
    <div class="py-6 bg-white sm:py-8 lg:py-12">
            <div class="max-w-screen-xl px-4 mx-auto md:px-8">
              <div class="grid gap-8 md:grid-cols-2">
                <div class="space-y-4">
                  <div class="relative overflow-hidden border rounded-lg">
                    <img src="https://img.webike-cdn.net/catalogue/images/113261/RAPIDE-NEO_REACT_DARK-MOCHA_P.jpg" loading="lazy"  class="object-cover object-center w-full h-full" />
                  </div>
                  <div class="grid grid-cols-3 gap-3">
                    <div class="overflow-hidden bg-gray-100 border rounded-lg">
                      <img src="https://img.webike-cdn.net/catalogue/images/113261/RAPIDE-NEO_REACT_DARK-MOCHA_B_s.jpg" loading="lazy"  class="object-cover object-center w-full h-full" />
                    </div>
                  </div>
                </div>
                <div class="md:py-8">
                  <div class="mb-2 md:mb-3">
                     ブランド : <span class="inline-block text-gray-500 mb-0.5">Arai</span>
                    <h2 class="text-2xl font-bold text-gray-800 lg:text-3xl">RAPIDE-NEO REACT</h2>
                  </div>
                  <div class="flex items-center mb-6 md:mb-4">
                    <div class="flex gap-0.5 -ml-1">
                      <svg xmlns="http://www.w3.org/2000/svg" class="w-6 h-6 text-yellow-400" viewBox="0 0 20 20" fill="currentColor">
                        <path d="M9.049 2.927c.3-.921 1.603-.921 1.902 0l1.07 3.292a1 1 0 00.95.69h3.462c.969 0 1.371 1.24.588 1.81l-2.8 2.034a1 1 0 00-.364 1.118l1.07 3.292c.3.921-.755 1.688-1.54 1.118l-2.8-2.034a1 1 0 00-1.175 0l-2.8 2.034c-.784.57-1.838-.197-1.539-1.118l1.07-3.292a1 1 0 00-.364-1.118L2.98 8.72c-.783-.57-.38-1.81.588-1.81h3.461a1 1 0 00.951-.69l1.07-3.292z" />
                      </svg>
          
                      <svg xmlns="http://www.w3.org/2000/svg" class="w-6 h-6 text-yellow-400" viewBox="0 0 20 20" fill="currentColor">
                        <path d="M9.049 2.927c.3-.921 1.603-.921 1.902 0l1.07 3.292a1 1 0 00.95.69h3.462c.969 0 1.371 1.24.588 1.81l-2.8 2.034a1 1 0 00-.364 1.118l1.07 3.292c.3.921-.755 1.688-1.54 1.118l-2.8-2.034a1 1 0 00-1.175 0l-2.8 2.034c-.784.57-1.838-.197-1.539-1.118l1.07-3.292a1 1 0 00-.364-1.118L2.98 8.72c-.783-.57-.38-1.81.588-1.81h3.461a1 1 0 00.951-.69l1.07-3.292z" />
                      </svg>
          
                      <svg xmlns="http://www.w3.org/2000/svg" class="w-6 h-6 text-yellow-400" viewBox="0 0 20 20" fill="currentColor">
                        <path d="M9.049 2.927c.3-.921 1.603-.921 1.902 0l1.07 3.292a1 1 0 00.95.69h3.462c.969 0 1.371 1.24.588 1.81l-2.8 2.034a1 1 0 00-.364 1.118l1.07 3.292c.3.921-.755 1.688-1.54 1.118l-2.8-2.034a1 1 0 00-1.175 0l-2.8 2.034c-.784.57-1.838-.197-1.539-1.118l1.07-3.292a1 1 0 00-.364-1.118L2.98 8.72c-.783-.57-.38-1.81.588-1.81h3.461a1 1 0 00.951-.69l1.07-3.292z" />
                      </svg>
          
                      <svg xmlns="http://www.w3.org/2000/svg" class="w-6 h-6 text-yellow-400" viewBox="0 0 20 20" fill="currentColor">
                        <path d="M9.049 2.927c.3-.921 1.603-.921 1.902 0l1.07 3.292a1 1 0 00.95.69h3.462c.969 0 1.371 1.24.588 1.81l-2.8 2.034a1 1 0 00-.364 1.118l1.07 3.292c.3.921-.755 1.688-1.54 1.118l-2.8-2.034a1 1 0 00-1.175 0l-2.8 2.034c-.784.57-1.838-.197-1.539-1.118l1.07-3.292a1 1 0 00-.364-1.118L2.98 8.72c-.783-.57-.38-1.81.588-1.81h3.461a1 1 0 00.951-.69l1.07-3.292z" />
                      </svg>
          
                      <svg xmlns="http://www.w3.org/2000/svg" class="w-6 h-6 text-gray-300" viewBox="0 0 20 20" fill="currentColor">
                        <path d="M9.049 2.927c.3-.921 1.603-.921 1.902 0l1.07 3.292a1 1 0 00.95.69h3.462c.969 0 1.371 1.24.588 1.81l-2.8 2.034a1 1 0 00-.364 1.118l1.07 3.292c.3.921-.755 1.688-1.54 1.118l-2.8-2.034a1 1 0 00-1.175 0l-2.8 2.034c-.784.57-1.838-.197-1.539-1.118l1.07-3.292a1 1 0 00-.364-1.118L2.98 8.72c-.783-.57-.38-1.81.588-1.81h3.461a1 1 0 00.951-.69l1.07-3.292z" />
                      </svg>
                    </div>
          
                    <span class="ml-2 text-sm text-gray-500">4.2</span>
          
                    <a href="#" class="ml-4 text-sm font-semibold text-indigo-500 transition duration-100 hover:text-indigo-600 active:text-indigo-700">view all 47 reviews</a>
                  </div>
                  <div class="mb-3 md:mb-3">
                    <span class="text-white bg-pink-600 text-xs font-semibold mr-2 px-2.5 py-0.5 rounded">NEW</span>
                    <span class="text-white bg-blue-800 text-xs font-semibold mr-2 px-2.5 py-0.5 rounded">送料無料</span>
                </div>
                  <div class="mb-4">
                    <div class="flex items-end gap-2">
                      <span class="text-xl font-extrabold text-red-600 md:text-2xl">
                        <span class="text-sm">販売価格 : </span>¥63,241(税込)
                    </span>
                      <span class="text-gray-600 line-through mb-0.5">¥64,900(税込み)</span>
                    </div>
                  </div>
                  <div class="mb-8 md:mb-5">
                    <select id="countries" class="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500">
                        <option selected>サイズを選択してください</option>
                        <option value="S">S</option>
                        <option value="M">M</option>
                        <option value="L">L</option>
                        <option value="XL">XL</option>
                      </select>
                    </div>
                  <div class="flex items-center gap-2 mb-6 text-blue-800 border-blue-700"> 
                    <span class="">納期 2-4 日</span>
                  </div>
                  <div class="flex gap-2.5">
                    <a href="#" class="flex-1 inline-block px-8 py-3 text-sm font-semibold text-center text-white transition duration-100 bg-indigo-500 rounded-lg outline-none sm:flex-none hover:bg-indigo-600 active:bg-indigo-700 focus-visible:ring ring-indigo-300 md:text-base">買い物かごに入れる</a>
          
                    <a href="#" class="inline-block px-4 py-3 text-sm font-semibold text-center text-gray-500 transition duration-100 bg-gray-200 rounded-lg outline-none hover:bg-gray-300 focus-visible:ring ring-indigo-300 active:text-gray-700 md:text-base">
                      <svg xmlns="http://www.w3.org/2000/svg" class="w-6 h-6" fill="none" viewBox="0 0 24 24" stroke="currentColor">
                        <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4.318 6.318a4.5 4.5 0 000 6.364L12 20.364l7.682-7.682a4.5 4.5 0 00-6.364-6.364L12 7.636l-1.318-1.318a4.5 4.5 0 00-6.364 0z" />
                      </svg>
                    </a>
                  </div>
                  <div class="mt-10 md:mt-16 lg:mt-20">
                    <div class="mb-3 text-lg font-semibold text-gray-800">商品情報</div>
          
                    <p class="text-gray-500">
                        ダクトのない帽体、蘇る3本スリットとクラシカルなスタイルをオマージュしながらも最新の安全性能・快適性の全てを搭載した、大人気のネオクラシック・フルフェイス〈ラパイドNEO〉にNEWデザイナーズシリーズ〈RAPIDE-NEO REACT〉の登場です。
                        デザインを担当したのは、人気カラー「オーバーランド」を手掛けた「加藤ノブキ」氏。
                        第一弾同様にNEOでしか表現できないARAIオリジナルロゴとシャドーを入れたデザイン&配色により、小顔効果とアーバンでクールなグラフィックが盛り込まれた2色展開。
                        最高水準の安全性とスマートなデザインが融合された大人ネオレトロが〈REACT〉のテーマとなっています
                    </p>
                  </div>
                </div>
              </div>
            </div>
          </div>


結構似てるものが出来た気がします。CSSは一切書いてません!!!

通常NEWのようなバッヂを作る場合

  .new-badge{
  margin-top: 2px;
  margin-right: 8px;
  border-radius: 4px;
  background: #FF3399;
  box-shadow: 0 2px 2px rgba(0, 0, 0, .4);
  padding: 4px 5px;
  overflow: hidden;
  color: #ffffff;
  font-size: 12px;
  font-weight: bold;
}

↑のようなCSSを書きますがTailwind CSSであればwelcome.blade.phpに

<span class="text-white bg-pink-600 text-xs font-semibold mr-2 px-2.5 py-0.5 rounded">NEW</span>

と書いたあとコンテナ上で

root@1234567879:/var/www/app# npm run dev

を実行すればpublic/css/app.cssにCSSが自動で生成されます。
bladeファイルを保存後に毎回コマンドを叩くのは嫌なのでファイルに更新があったときに自動でビルドを行うように設定します。

root@1234567879:/var/www/app# npm run watch-poll

↑このコマンドを実行することでファイルを保存したときに自動でビルドを行い、バッヂが出来上がります。

コンポーネントを作る

Tailwind CSSを触っていると、似たような部品を作るときに毎回クラスに長文を入れるのが面倒になると思います。
さっきのバッヂNEWを作るときも、形はそのままで色と文言だけ変えたいときがあると思います。
そんなときはコンポーネントを作りましょう。

app/resources/css/app.css
@layer components {
    .badge-shape {
        @apply text-white  text-xs font-semibold mr-2 px-2.5 py-0.5 rounded;
    }
}

app.cssにバッヂのbg-pink-600以外のクラス名をbadge-shapeとして設定してあげます。
あとはwelcome.blade.phpに背景色を設定してあげれば、色だけ変更できるバッヂが出来上がります。

welocome.blade.php
<span class="text-white bg-pink-600 text-xs font-semibold mr-2 px-2.5 py-0.5 rounded">NEW</span>
<span class="bg-green-500 badge-shape">NEW !!</span>

NEWNEW !!

部品に統一性を持たせることができるので、設計次第で非常に使いやすくなると思います。

総評

良いところ

    • CSSファイルを書かなくてよい

今回感じた一番大きなメリットです。HTMLとCSSを両方書くのは結構大変です。

    • クラス名を考えなくて良い

クラス名がそのままCSSのプロパティとなるので、悩む時間がなくなります。

    • コンポーネント化できる

必要なクラスを集めて、新たなクラスとして実装できるのは開発効率の向上につながると思います。

    • CSSファイルの最小化

使用したクラスのみCSSが生成されるので、CSSファイルは最小になリます。

悪いところ

    • クラス名が長くなりがち

しょうがない点ではありますが、いろんなプロパティを当てたいとなるとクラス名が長くなります。コンポーネント化をしっかり使いこなしたいです。

デメリットを差し引いてもTailwind CSSの恩恵は大きいと思いました。今回紹介した以外も便利な機能はいっぱいあるので、使いこなせるように勉強したいです。