【TailwindCSS】定数ファイルへのクラス名切り出しが失敗する理由と対処法💡

# 定数ファイルにクラス名を定義した際のTailwindCSSの最適化機能による問題と解決策

私は`Next.js`と`TailwindCSS`を使用し、フロントエンド開発を行っている初心者です。
ポートフォリオサイト開発において、TailwindCSSのクラス名を定数ファイルに切り出したところ、スタイルが適用されない問題に直面しました。この問題の解決過程で、TailwindCSSの最適化機能について深く学ぶことができました。今回は、その経験から得られた知見をまとめます。

## 目次
1. [はじめに](#はじめに)
1. [発生した問題](#発生した問題)
1. [原因の理解](#原因の理解)
1. [解決策](#解決策)
1. [よくあるエラーパターン](#よくあるエラーパターン)
1. [まとめ](#まとめ)
1. [謝辞](#謝辞)

## はじめに
TailwindCSSは、HTMLの要素に直接スタイルを適用できる便利なクラスが豊富に用意されたCSSフレームワーク(ユーティリティファーストなCSSフレームワーク)として人気を集めております。
TailwindCSSには最適化機能が組み込まれており、使用されていないクラスを自動的に除去することでウェブサイトの実行に必要なファイルの容量（バンドルサイズ）を最適化します。しかし、この便利な機能が時として意図しない動作を引き起こすことがあります。
今回は、特に定数ファイルにクラス名を切り出した際に発生する問題とその解決策について、実践的な例を交えながら解説していきます。

> 💡 Tips: TailwindCSSの最適化機能の知見は、@uasi様からご指摘いただいた内容です。当初の私の理解が間違っていたことで、他の開発者の方々の参考になればと思い、この記事を作成しました。

## 発生した問題

### コードの移動による問題
当初は以下のようにコンポーネントに直接スキルデータを記述していました。

```typescript
// components/Skills.tsx（動作する）
export const Skills = () => {
  // コンポーネント内に直接スキルデータを記述
  const skills = [
    {
      title: 'Next.js',
      textColor: 'text-black',
    },
    {
      title: 'React',
      textColor: 'text-blue-600',
    },
    // ... 他のスキルデータ
  ] as const;
  
  return (
    <div className='grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6'>
      {skills.map((skill) => (
        <div key={skill.title} className={skill.textColor}>
          {skill.title}
        </div>
      ))}
    </div>
  );
};
```

このコードは動作しましたが、以下の課題がありました。

- コードが長くなり、可読性が低下
- スキルデータの再利用が困難
- データとUIの関心の分離ができていない

### 定数ファイルへの切り出し
そこで、データを定数ファイルに切り出す実装を試みました。

```typescript
// constants/skills.ts（動作しない）
// 定数ファイルにTailwindのクラス名を記載
export const skills = [
  {
    title: 'Next.js',
    textColor: 'text-black', // このTailwindのクラス名が認識されない
  },
  {
    title: 'React',
    textColor: 'text-blue-600', // このTailwindのクラス名が認識されない
  },
  // ... 他のスキルデータ
] as const;

// components/Skills.tsx（動作しない）
import { skills } from '@/constants/skills';
export const Skills = () => {
  return (
    <div className='grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6'>
      {skills.map((skill) => (
        <div key={skill.title} className={skill.textColor}>
          {skill.title}
        </div>
      ))}
    </div>
  );
};
```

このコードは一見問題なさそうに見えますが、実際にアプリケーションを動かすと`textColor`で指定したクラスが適用されませんでした。

### カスタムフックでの実装試行
次に、カスタムフックを使用して実装を試みました。

```typescript
// hooks/useSkills.ts
type UseSkills = { skills: Skill[] };
export const useSkills = (): UseSkills => {
  // カスタムフックにTailwindのクラス名を記載
  const skills: Skill[] = [
    {
      title: 'Next.js',
      textColor: 'text-black', // このTailwindのクラス名が認識されない
    },
    {
      title: 'React',
      textColor: 'text-blue-600', // このTailwindのクラス名が認識されない
     },
    // ... 他のスキルデータ
  ] as const;
  return { skills };
};

// components/Skills.tsx（動作しない）
import { useSkills } from '@/hooks/useSkills';
export const Skills = () => {
  const { skills } = useSkills();
  return (
    <div className='grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6'>
      {skills.map((skill) => (
        <div key={skill.title} className={skill.textColor}>
          {skill.title}
          {/* 他のスキル情報の表示... */}
        </div>
      ))}
    </div>
  );
};
```

しかし、この方法でも同様の問題が発生しました。

## 原因の理解
### TailwindCSSの最適化の仕組み
TailwindCSSは最適化時に、設定された対象ファイルのみをスキャンしてクラス名を抽出します。デフォルトの設定では、主にコンポーネントファイルのみがスキャン対象となっており、定数ファイルやカスタムフックは対象外となっています。そのため、これらのファイルに記述されたクラス名は認識されず、スタイルが適用されない結果となっていました。

## 解決策
### 設定の修正
`tailwind.config.ts`に以下の設定を追加することで、定数ファイルもスキャン対象に含めることができます：

```typescript
import type { Config } from "tailwindcss";

export default {
  content: [
    "./src/pages/**/*.{js,ts,jsx,tsx,mdx}",
    "./src/components/**/*.{js,ts,jsx,tsx,mdx}",
    "./src/app/**/*.{js,ts,jsx,tsx,mdx}",
    "./src/constants/**/*.ts",  // 追加
  ],
  // ...
}
```

[Content Configuration - Tailwind CSS](https://tailwindcss.com/docs/content-configuration)


##  よくあるエラーパターン
1. スキャン対象のパスが間違っている
   - パスはプロジェクトルートからの相対パスで指定する必要があります
   - ワイルドカードの使用法に注意が必要です
```typescript
// このように指定すると
"./src/constants/*.ts"

// ✅ src/constants/skills.ts はスキャンされる
// ❌ src/constants/theme/colors.ts はスキャンされない

// このように指定すると
"./src/constants/**/*.ts"

// ✅ src/constants/skills.ts はスキャンされる
// ✅ src/constants/theme/colors.ts もスキャンされる
```

2. ファイル拡張子の指定漏れ
   - `.ts`や`.tsx`など、適切な拡張子を指定する必要があります
```typescript
"./src/constants/**/*.{ts,tsx}"  // .ts と .tsx の両方をスキャン
```

## まとめ
TailwindCSSの最適化機能は、プロジェクトのパフォーマンスを向上させる重要な機能です。ただし、その仕組みを正しく理解し、適切な設定を行うことが必要です。特に、定数ファイルやカスタムフックにクラス名を切り出す場合は、それらのファイルをスキャン対象に含めるよう設定することが重要です。

もし記事の内容に間違いがあれば、コメントでご指摘いただけますと幸いです。また、より良い方法や代替手段をご存知の方がいらっしゃいましたら、ぜひ共有していただければと存じます。例えば、TailwindCSSの最適化に関する他の設定オプションや、より効率的なクラス名の管理方法など、皆様の知見やベストプラクティスをお聞かせいただければ幸いです。

## 謝辞
この記事は、Qiitaでの質問に対して@uasi様から丁寧なご回答をいただいたことで実現しました。特に印象的だったのは、TailwindCSSの最適化機能とPurgeCSSの違いについてのご指摘です。私は当初、TailwindCSSの未使用クラスを除去する最適化の仕組みを「PurgeCSS」と誤認識していました。@uasi様のご指摘により、PurgeCSSは独立したツールであり、TailwindCSSの最適化機能とは異なる存在であることを理解することができました。この場をお借りして、深く感謝申し上げます。