Web production note

 【更新日 :

ViteでTailwind CSSを利用する(+最適化した独自styleが混在可能か検証)

Category:
開発環境

ViteでTailwind CSSを導入したサンプルです。
Tailwind CSSの最適化(リセットCSSの削除、変数の削減、独自拡張)や、最適化した独自styleを混在させることができるのか検証しました。

Tailwind CSSとは

ユーティリティファーストで考案されたオープンソースのCSS フレームワークです。

flex pt-4 text-center といったようにCSSのclass名と設定されるstyleが一意のため、プロジェクト毎に依存しないコードを書くことができます。

一方でclass設定が多くなりHTMLの可読性が下がったりHTMLが肥大化するなどデメリットもあるため、利用する場合には実装要件に合わせてよく検討する必要があるかもしれません。

Viteのベース構築

本記事は公式ドキュメントに沿ってPostCSSのTailwindプラグインを利用します。

ベース環境のインストールは完了していることが前提ですので基本の構築は以下の記事を参照してください。

npmパッケージ tailwindcss の設定

Vite環境にインストール

npm install -D tailwindcss

※ autoprefixer が未インストールの場合は追加することを推奨します。

npm install -D autoprefixer

postcss.config.cjsへ呼び出しコードを追記

postcss.config.cjs

module.exports = {
  plugins: {
    tailwindcss: {},
    autoprefixer: {},
  },
}

拡張子を.jsにする場合

postcss.config.js とする場合はエラー回避のため module.exports = {export default { に書き換えてください。

postcss.config.js

export default {
  plugins: {
    tailwindcss: {},
    autoprefixer: {},
  },
}

package.json内に記載されていますが、Viteは "type": "module" の形式です。"type": "module" を設定すると.jsファイルはES6モジュールとして扱うようになりますが、ES6モジュールではrequire、module、exportsなどの識別子が利用できなくなります。(代わりにimportとexportが利用できます。)

“type”: “module”設定でも従来のコードを利用したい場合は、JSファイルをCommonjsモジュールとして扱うように拡張子をcjsに変更します。(”type”: “module”に関係なくES6モジュールとして扱いたいJSファイルがある場合は拡張子をmjsにします。)

本記事ではベースの構築記事でpostcss.configの拡張子を.cjsにしているためそれに合わせています。

tailwind.config.jsの作成

tailwind.config.js を作成し以下の初期設定を記述してください。

tailwind.config.js

module.exports = {
  content: [ //監視するファイルの設定
  "./src/*.{js,html}",
  "./src/**/*.{js,html}",
  ],
  theme: {
    extend: {},
  },
  plugins: [],
}

監視するファイルの設定では{} を利用すると複数の拡張子を同時設定できます。
詳しくは以下をご参照ください。

SCSSにTailwind CSSのstyleを追加

style.scssにTailwind CSSのstyleを読み込むコードを追加します。

style.scss

@tailwind base;
@tailwind components;
@tailwind utilities;

ここまでで最低限の設定は完了したため、プロジェクトを開始してclassを追加するとstyleが有効化されるようになります。

<p class="text-3xl font-bold underline">
  Hello world!
</p>

Tailwind CSSの利便性を上げる

VS Codeの拡張機能

VS Code上でclass名のオートコンプリートなど便利な機能が追加されるTailwind CSS IntelliSenseや、Prettierでclassを自動的に並べ替えなどしてくれるPrettier pluginなど、公式から便利な拡張機能が配信されています。詳細は以下をご参照ください。

ブラウザの拡張機能

Tailwind Cheat Sheet ExtensionはChromeの拡張機能です。ブラウザ上でプロパティ名などからTailwind CSSのclass名を検索できるので、公式のチートシートを見に行く手間を省くことができます。

リセットCSSを無効化する

Tailwind CSSは単体でも利用できるようにデフォルトでリセットCSSが設定されていますが、独自のリセットCSSを利用したい場合は corePlugins.preflight: false とすると無効化することができます。

@tailwind base; にはリセット以外にも Tailwind CSS で利用するCSS変数が含まれるため削除は非推奨になっています。

tailwind.config.js

module.exports = {
  content: [],
  corePlugins: {
    //リセットCSSを無効化
    preflight: false,
  },
  theme: {
    extend: {},
  },
  plugins: [],
}

preflightの詳細は以下をご参照ください。

利用していない不要なCSS変数を削除する

Tailwind CSSを利用しビルドすると、利用していないstyleのCSS変数も大量に出力されるのが確認できます。不要なものはcorePluginsで無効化しておくと変数の出力を止めることができます。

以下は全てのCSS変数を無効化する例ですが、フロント側で利用したい場合には当然記述を削除する必要がありますので、設定する際にはプロジェクトにおいて利用していないstyleを精査して十分注意するようにしてください。

tailwind.config.js

module.exports = {
  content: [],
  corePlugins: {
    //リセットCSSを無効化
    preflight: false,

    //変数を生成する全てのstyleを無効化
    borderSpacing: false,
    backdropBlur: false,
    backdropBrightness: false,
    backdropContrast: false,
    backdropFilter: false,
    backdropGrayscale: false,
    backdropHueRotate: false,
    backdropInvert: false,
    backdropOpacity: false,
    backdropSaturate: false,
    backdropSepia: false,
    boxShadow: false,
    filter: false,
    fontVariantNumeric: false,
    gradientColorStops: false,
    ringOffsetWidth:false,
    ringWidth:false,
    scrollSnapType: false,
    touchAction: false,
    transform: false,
  },
  theme: {
    extend: {},
  },
  plugins: [],
}

無効化する際に渡す文字列のバリエーションなどは以下で参照できます。

:hoverにメディアクエリ(@media (hover: hover) and (pointer: fine))を追加する

future.hoverOnlyWhenSupported を有効化すると、ホバー系のclassを設定した際にメディアクエリ(@media (hover: hover) and (pointer: fine))を設定できます。

モバイル端末ではhover効果を無効化したい場合などに有効な設定です。

Tailwind v3.1以降で利用できる設定です。

tailwind.config.js

module.exports = {
  future: {
    hoverOnlyWhenSupported: true,
  },
}

メディアクエリのブレイクポイントを変更する

theme.screens でブレイクポイントの変更や拡張ができます。

tailwind.config.js

module.exports = {
  theme: {
    screens: {
      'sm': '640px',
      // => @media (min-width: 640px) { ... }

      'md': '768px',
      // => @media (min-width: 768px) { ... }

      'lg': '1024px',
      // => @media (min-width: 1024px) { ... }

      'xl': '1280px',
      // => @media (min-width: 1280px) { ... }

      '2xl': '1536px',
      // => @media (min-width: 1536px) { ... }
    }
  }
}

既存の内包そのままで設定を追加したい場合は以下のような省略記法もあります。

tailwind.config.js

const defaultTheme = require('tailwindcss/defaultTheme')

module.exports = {
  theme: {
    screens: {
      'xs': '475px',
      ...defaultTheme.screens,
    },
  },
  plugins: [],
}

複雑なブレイクポイントの設定

固定範囲ブレークポイント

tailwind.config.js

module.exports = {
  theme: {
    screens: {
      'sm': {'min': '640px', 'max': '767px'},
      // => @media (min-width: 640px and max-width: 767px) { ... }

      'md': {'min': '768px', 'max': '1023px'},
      // => @media (min-width: 768px and max-width: 1023px) { ... }

      'lg': {'min': '1024px', 'max': '1279px'},
      // => @media (min-width: 1024px and max-width: 1279px) { ... }

      'xl': {'min': '1280px', 'max': '1535px'},
      // => @media (min-width: 1280px and max-width: 1535px) { ... }

      '2xl': {'min': '1536px'},
      // => @media (min-width: 1536px) { ... }
    },
  }
}

複数範囲のブレークポイント

tailwind.config.js

module.exports = {
  theme: {
    screens: {
      'sm': '500px',
      'md': [
        {'min': '668px', 'max': '767px'},
        {'min': '868px'}
      ],
      'lg': '1100px',
      'xl': '1400px',
    }
  }
}

詳細は以下をご参照ください。

色のカスタマイズ

デフォルトパレットのカスタマイズ

theme.colors で色のカスタマイズが可能です。

tailwind.config.js

module.exports = {
  theme: {
    colors: {
      primary: '#5c6ac4',
      secondary: '#ecc94b',
      transparent: 'transparent',
      current: 'currentColor',
      'white': '#ffffff',
      'tahiti': {
        100: '#cffafe',
        200: '#a5f3fc',
        300: '#67e8f9',
        400: '#22d3ee',
        500: '#06b6d4',
        600: '#0891b2',
        700: '#0e7490',
        800: '#155e75',
        900: '#164e63',
        light: '#67e8f9',
        DEFAULT: '#06b6d4',
        dark: '#0e7490',
      },
      // ...
    },
  },
}

色を追加する

新しい色を追加したい場合は、theme.extend.colorsに追記します。

tailwind.config.js

module.exports = {
  theme: {
    extend: {
      colors: {
        brown: {
          50: '#fdf8f6',
          100: '#f2e8e5',
          200: '#eaddd7',
          300: '#e0cec7',
          400: '#d2bab0',
          500: '#bfa094',
          600: '#a18072',
          700: '#977669',
          800: '#846358',
          900: '#43302b',
        },
      }
    },
  },
}

詳細は以下をご参照ください。

余白のカスタマイズ

デフォルト余白のカスタマイズ

theme.spacing でカスタマイズ可能です。

tailwind.config.js

module.exports = {
  theme: {
    spacing: {
      sm: '8px',
      md: '12px',
      lg: '16px',
      xl: '24px',
      '1': '8px',
      '2': '12px',
      '3': '16px',
      '4': '24px',
      '5': '32px',
      '6': '48px',
    }
  }
}

余白を追加する

デフォルトで用意されていない設定を追加したい場合は、theme.extend.spacing に追記します。

tailwind.config.js

module.exports = {
  theme: {
    extend: {
      spacing: {
        '13': '3.25rem',
        '15': '3.75rem',
        '128': '32rem',
        '144': '36rem',
      }
    }
  }
}

数値をそのままpxに変換する

theme.spacing 内で関数を実行し機能拡張することも可能です。
以下の例は1〜300の余白の数値をそのままpxに変換します。(例:mt-1 → margin-top: 1px;)

tailwind.config.js

module.exports = {
  theme: {
    extend: {
      spacing: (() => {
        let spacing = {};
        for (let i = 1; i <= 300; i++) {
          // プロパティ名と値を動的に設定します
          spacing[i] = `${i}px`;
        }
        return spacing;
      })(),
    }
  }
}

独自関数を使ったtheme.extendでの拡張

直上の「数値をそのままpxに変換する」の項で掲載した方法ですが、各設定項目には独自関数の返り値を渡すことで同じような記述を自動化することも可能です。

以下の記事ではpx値のclass名を記載するとCSS上ではremに変換する方法(例:text-16ptr で font-size:1rem;)が掲載されています。

以下にいくつかカスタマイズ例を記載します。

tailwind.config.js

module.exports = {
  theme: {
    extend: {

      /*
        leading-1 → line-height: 1;

        leading-12 → line-height: 1.2;
        →2桁の場合は小数点以下1桁になる

        leading-145 → line-height: 1.45;
        →3桁の場合は小数点以下2桁になる
      */
      lineHeight: () => {
        let values = {};
        for (let i = 1; i <= 300; i++) {
          if (i > 10 && i < 100) {
            values[i] = i / 10;
          } else if (i >= 100) {
            values[i] = i / 100;
          } else {
            values[i] = i;
          }
        }
        return values;
      },

      /*
        tracking-1 → letter-spacing: 0.001em;
        〜
        tracking-999 → letter-spacing: 0.999em;
      */
      letterSpacing: () => {
        let spacing = {};
        for (let i = 1; i <= 999; i++) {
          spacing[i] = (i / 1000).toFixed(3) + 'em';
          console.log((i / 1000).toFixed(3) + 'em');
        }
        return spacing;
      },

      //z-1〜z-9までのz-indexを生成
      zIndex: (() => {
        let zIndex = {};
        for (let i = 1; i <= 9; i++) {
          // プロパティ名と値を動的に設定します
          zIndex[i] = i;
        }
        return zIndex;
      })(),

    }
  }
}

独自のユーティリティclassを追加する

Tailwind CSSが元々内包しているプラグイン addUtilities() を利用すると独自のユーティリティclassを追加できます。

addUtilities()で追加したclassはTailwind CSS IntelliSense によるオートコンプリートが効くので便利です。

tailwind.config.js

const plugin = require('tailwindcss/plugin')

module.exports = {
  plugins: [
    plugin(function({ addUtilities }) {
      addUtilities({
        '.flex-center': {
          'display': 'flex',
          'justify-content': 'center',
          'align-items': 'center'
        },
        '.flex-between': {
          'display': 'flex',
          'justify-content': 'space-between',
        },
      })
    })
  ]
}

詳細は以下をご参照ください。

独自のコンポーネントclassを追加する

Tailwind CSSが元々内包しているプラグイン addComponents() を利用すると独自のユーティリティclassを追加できます。

addComponents()で追加したclassはTailwind CSS IntelliSense によるオートコンプリートが効くので便利です。

tailwind.config.js

const plugin = require('tailwindcss/plugin')

module.exports = {
  plugins: [
    plugin(function({ addComponents }) {
      addComponents({
        '.btn': {
          padding: '.5rem 1rem',
          borderRadius: '.25rem',
          fontWeight: '600',
        },
        '.btn-blue': {
          backgroundColor: '#3490dc',
          color: '#fff',
          '&:hover': {
            backgroundColor: '#2779bd'
          },
        },
      })
    })
  ]
}

詳細は以下をご参照ください。

addUtilities()、addComponents()でのstyleの書き方

CSS-in-JS 構文を利用でき、SCSSファイルに近い書き方でネストできます。

addComponents({
  '.card': {
    backgroundColor: '#fff',
    borderRadius: '.25rem',
    boxShadow: '0 2px 4px rgba(0,0,0,0.2)',
    '&:hover': {
      boxShadow: '0 10px 15px rgba(0,0,0,0.2)',
    },
    '@media (min-width: 500px)': {
      borderRadius: '.5rem',
    }
  }
})

詳細は以下をご参照ください。

【検証】独自styleをPurgeCSSで除外しつつTailwind CSSも利用する

ここで言う独自styleとは直上の .test のようにTailwind CSS以外の方法で記述している独自styleを指しています。addUtilities()やaddComponents()でのstyle追加はTailwind CSSのPurge対象です。

現行のTailwind CSSは以前のバージョンから仕様変更があり、PurgeCSSの内蔵しておらず独自の方法でTailwind CSSで出力されるstyleのみ最適化するようになったため、Tailwind CSS以外の方法で追加した独自styleはPurgeの対象外になっています。

動的に追加されるclassを許容するにはsafelistを利用し工夫する必要があるため設定がやや複雑化します。細かい調整ができないプロジェクトではできる限りPurgeCSSの利用は控えた方が良いかもしれません。

postcss-purgecssをインストールし挙動を検証

インストール

npm install -D @fullhuman/postcss-purgecss

postcss.config.cjs に追記し有効化

module.exports = {
  plugins: {
    tailwindcss: {},
    autoprefixer: {},
    '@fullhuman/postcss-purgecss': {
      content: ['./src/**/*.html','./src/js/**/*.js'],
      safelist: []
    },
  },
}

contentの設定のみでファイルをビルドすると、PurgeCSSが利用しているTailwind CSSのclassも削除してしまいます。
Tailwind CSSのclass名は [&_li:after]:content-['|'] のような普通では利用しない特殊記号も用いているのでclass名と認識してくれないのが原因です。

ある程度の回避策としてPurgeCSSのdefaultExtractorオプションでセレクタの範囲を調整すると緩和が可能です。

動的に追加されるclass名と連動している場合は、safelistと組み合わせる必要があります。
(直下の項目に詳細を記載します。)

postcss.config.cjs

module.exports = {
  plugins: {
    tailwindcss: {},
    autoprefixer: {},
    '@fullhuman/postcss-purgecss': {
      content: ['./src/**/*.html','./src/js/**/*.js'],
      defaultExtractor: (content) => {
        /*
          動的に追加されるclass名と連動したclass名は
          該当classをsafelistに追加する必要がある
        */

        // class属性内の全ての文字列をマッチ
        const classAttrMatches = content.match(/class="([^"]*)"/g) || [];

        // マッチした文字列から `class=""` を取り除き、空白で分割してクラス名の配列を作成
        const cleanedMatches = classAttrMatches.flatMap(match => match.replace(/class="|"/g, '').split(/\s+/));

        // 特殊な文字を含むクラス名を抽出
        const specialCharMatches = content.match(/[^<>"`\s]*[^<>"`\s:]/g) || [];

        return cleanedMatches.concat(specialCharMatches);
      },
      safelist: []
    },
  },
}

動的に追加されるclassを許容するにはsafelistを利用し工夫する必要がある

以下のような動的に追加されるclassを想定してstyleを追記した場合、上記正規表現のみではclassが削除されてしまいます。

<p class="hidden [&.is-active]:block">is-activeが追加されると表示</p>

このような場合はsafelistに is-active を追加し、動的に追加されるclassを予め許可しておく必要があります。

module.exports = {
  plugins: {
    tailwindcss: {},
    autoprefixer: {},
    '@fullhuman/postcss-purgecss': {
      content: ['./src/**/*.html','./src/js/**/*.js'],
      defaultExtractor: (content) => {
        //省略
      },
      safelist: ['is-active']
    },
  },
}

【検証】SCSS内でTailwind CSSを独自styleの途中に挿入できるか

柔軟性の高い結論のみ参照したい場合は、末尾の項目 【解決案】scssファイルの読み込みをhtmlからjsに変更し、PostCSSのタスク実行を調整する をご参照ください。

通常Tailwind CSSはCSSの末尾に挿入されますが、独自styleの間にTailwind CSSを挿入できるのかを検証しました。(後ろに独自styleを挿入できた方が何らかの理由でstyleを上書きする際に都合が良いなどの理由があります。)

外部ファイルを読み込む @use はファイル上部に書く必要があるので、途中で @tailwind を挿入するとエラーが発生します。

読み込みエラーになる例

@use "_base";

@tailwind base;
@tailwind components;
@tailwind utilities;

@use "_page"; /* ここでエラーが出る */

これの回避方法として @tailwind を外部ファイル化し読み込ませるとエラーは出ませんが、メディアクエリ内のstyleはビルド時にcssの末尾に出力されるため、位置が保持されるのはメディアクエリなしのstyleのみです。

_tailwind.scss

@tailwind base;
@tailwind components;
@tailwind utilities;

外部ファイル化した@tailwindをテスト

@use "_base";

/*
  外部ファイル化するとエラーは出ないが
  md:xxxなどメディアクエリ内のstyleはcssの末尾に出力される
*/
@use "_tailwind";

@use "_page";

ビルド結果(@tailwind のメディアクエリのstyleは末尾へ出力される)

/* _base.scssに書いたstyle */
.base {}

/* @tailwind メディアクエリなしのstyle */
.text-center {}

/* _page.scssに書いたstyle */
.page {}

/* @tailwind メディアクエリがあるstyle */
@media (min-width: 768px){
  .md\:text-right{text-align:right}
}

postcss-sort-media-queriesでソートしてみる

postcss-sort-media-queriesはメディアクエリをソートして1つにまとめてくれるPostCSSのプラグインです。
末尾に出力されてしまうメディアクエリのstyleも含めソートするとどうなるか検証しました。

インストール

npm install -D postcss-sort-media-queries

postcss.config.cjs に追記し有効化

module.exports = {
  plugins: {
    tailwindcss: {},
    autoprefixer: {},
    'postcss-sort-media-queries': {},
    '@fullhuman/postcss-purgecss': {
      //省略
    },
  },
}

以下のような .test.md:text-right を上書きできるかテストしてみます。

表示テストhtml

<p class="md:text-right test">.testでTailwind CSSのstyleを上書きしたい</p>

表示テスト.scss

/* md:text-rightを出力する */
@use "_tailwind";

.test {
  @media (min-width: 768px) {
    text-align: center;
  }
}

以下のような結果になりました。
.md:text-right.test よりも後に出力されるため、このままでは自作classによる上書きは詳細度を上げる必要があります。

ビルド結果

@media (min-width: 768px) {
  .test {
    text-align: center;
  }
  .md\:text-right {
    text-align: right;
  }
}

代案:自作のメディアクエリを調整しTailwind CSSのメディアクエリとは分離させる

SCSSファイルのインポートの方法やpackage.jsonを調整できる場合は、直下の項目【解決案】scssファイルの読み込みをhtmlからjsに変更し、PostCSSのタスク実行を調整する をご参照ください。

Tailwind CSSのメディアクエリとは異なる書き方であれば、ソートしても分離されるので意図した挙動にすることができました。

表示テスト.scss

/* md:text-rightを出力する */
@use "_tailwind";

.test {
  @media not screen and (min-width: 768px) {
    text-align: center;
  }
}

ビルド結果(Tailwind CSSより下にソートされる)

@media (min-width: 768px) {
  .md\:text-right {
    text-align: right;
  }
}
@media only screen and (min-width: 768px) {
  .test {
    text-align: center;
  }
}

【解決案】scssファイルの読み込みをhtmlからjsに変更し、PostCSSのタスク実行を調整する

ViteでSCSSファイルを読み込む方法は2種類あり、これまでの執筆記事ではなるべく元のHTMLに近づけるためhtmlファイル内で読み込む方法を採用していましたが、Tailwindの出力位置をコントロールする場合はjsファイルでインポートする方が柔軟性がありました。

また、ソートや圧縮のタスクはViteでCSSファイルをビルドした後に実行するよう調整すると設定が効かない問題をクリアできます。

SCSSの読み込み方法を調整

HTML側の記述を削除し、全ページに共通して読み込ませているJavaScriptファイル内でインポートします。

index.html CSSの読み込みを削除(before)

<!DOCTYPE html>
<html lang="ja">
  <head>
    〜省略〜
    <!-- ↓共通して読み込んでいるCSSを削除 -->
    <link rel="stylesheet" href="./scss/style.scss">

    <script type="module" src="./js/main.js"></script>
  </head>
  <body>
    〜省略〜
  </body>
</html>

index.html CSSの読み込みを削除(after)

<!DOCTYPE html>
<html lang="ja">
  <head>
    〜省略〜

    <script type="module" src="./js/main.js"></script>
  </head>
  <body>
    〜省略〜
  </body>
</html>

共通して読み込ませている main.js でCSSをインポート

//Tailwind CSSのstyleよりも上に出力したいものは先にインポート
import '../scss/_reset.scss';

import '../scss/_tailwind.scss';

//Tailwind CSSのstyleよりも下に出力したいものは後でインポート
import '../scss/style.scss';


//main.jsのコード

ソートや圧縮などビルドの最後で実行したいタスクを無効化

postcss.config.cjsに記載しているとVite側で自動的に実行されてしまうため、コメントアウトなどで並び替えや圧縮のタスクを無効化します。

postcss-csso はcssを圧縮するプラグインです。以降複数タスクを追加するパターンの例として追記しています。

postcss.config.cjs

module.exports = {
  plugins: {
    tailwindcss: {},
    autoprefixer: {},
    // 'postcss-sort-media-queries': {},
    // 'postcss-csso': {},
    '@fullhuman/postcss-purgecss': {
      //省略
    },
  },
}

Viteのビルドが終わった後にPostCSSのコードを実行するため npm-run-allpostcss-cli をインストールします。

build_cssを&&でつないでそのまま実行するとエラーが出ることがあるため、エラー対策としてnpm-run-allを利用します。

npm install -D npm-run-all postcss-cli

本記事と環境をあわせる場合 cssoのインストール(任意)

npm install -D postcss-csso

package.jsonを編集し、build_cssのコマンドを追記します。
Viteのbuildコマンドが終了した後に、PostCSSの残タスクを実行できるよう "build" にもコマンドを追記します。

package.json

{
  〜省略〜
  "scripts": {
    "build_css": "postcss dist/assets/css/*.css --use postcss-sort-media-queries --use postcss-csso --replace --no-map",
    "build": "vite build && npm-run-all build_css",
  },
  〜省略〜
}
  • --use プラグイン名 を追記していくことで実行タスクを追加できます。
  • --プラグイン名-オプション名 値 でプラグインのオプション設定が可能です。

PostCSS CLIの詳細は以下をご参照ください。

Tailwind CSSの書き方を勉強する

上記の検証過程で少し構文を記載しましたが、Tailwind CSSはそれ単体で一通り実装ができるように非常に細かいカスタマイズが可能になっています。

簡単に内容を知りたい方は以下の記事などを参照すると良いかもしれません。

細かくどのような設定があるか参照したい場合は、公式ドキュメントのCore Concepts項目辺りを一読すると良いでしょう。

関連リンク

参考リンク