1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

Angular 12 にシュッと導入してチャッと理解した tailwindcss のプラグインをサッと作る

Last updated at Posted at 2022-02-20

前回の記事 Angular 12 にシュッと導入してチャッと理解した tailwindcss をピャッとカスタマイズする からしばらく経過してしまいました。時が流れて手元のプロジェクトの Angular/tailwindcss ともにアップデートしてしまったのですが続きを書きます。

今回は tailwindcss でカスタムプラグインを作る方法をメインに紹介します。

記事執筆の環境は下記の通りです。

  • @angular/cli v13.1
  • node v16.10 (npm v7.24)
  • tailwindcss v3.0

プラグインとは

tailwindcss を使ったユーティリティクラスの幅を広げるための機能です。
プラグインには、公式などのプラグインを取り込む方法と、ユーザー定義のカスタムプラグインを自作する方法との 2 通りがあります。

プラグインを取り込む方法

例えば公式のプラグイン @tailwindcss/typography を取り込んでみましょう。

npm install -D @tailwindcss/typography
// tailwind.config.js
module.exports = {
  theme: { ... },
  plugins: [
    require('@tailwindcss/typography'),
  ],
}

このプラグインでは prose クラスが提供されています。
ビルトインのユーティリティと同じように CSS クラスとして記述します。

<article class="prose lg:prose-xl">
  <h1>...</h1>
</article>

カスタムプラグインを自作する方法

次はカスタムプラグインを見てみましょう。
カスタムプラグインは、設定ファイルの plugins に関数を渡します。

// tailwind.config.js
const plugin = require('tailwindcss/plugin');

module.exports = {
  plugins: [
    plugin(function({ addUtilities, addComponents, e, prefix, config }) {
      ...
    }),
  ]
}

引数の addUtilities addComponents e などはカスタムプラグインを作るためのユーティリティです。
必要な引数を 分割代入 で宣言して使います。

公式のプラグインや npm で公開されたプラグインはそれぞれドキュメントに使用方法が書いてあるので詳細な説明は割愛します。
ここから先はカスタムプラグインを主軸にした記事です。

addUtilities を使ってフェードインするボタンを作る

まず引数に addUtilities を宣言する例として、不透明度10%から100%にふわっと変化するボタンを作ってみましょう。

// tailwind.config.js
const baseConfig = {
...
plugins: [
  plugin(({ addUtilities }) => {
    const utilities = {
      '.fade-in': {
        transition: 'opacity 1s ease-in-out',
        opacity: '1'
      }
    };
    addUtilities(utilities);
  }),
],

名前は fade-in としました。
適用する CSS を示すオブジェクトの記法は CSS-in-JS syntax を使います。

const utilities = {
  // ユーティリティクラス名
  '.fade-in': {
    // 適用する CSS
    transition: 'opacity 1s ease-in-out',
    opacity: '1'
  }
};

CSS-in-JS ではクォートまたはキャメルケースでキーを指定します。
どちらも同じようにビルドするとダッシュケースに変換されます。

// クォート指定
'background-color': 'black'

// camelCase
backgroundColor: 'black'

// ビルド後:dashCase
background-color: 'black'

fade-in クラスはビルトインのユーティリティクラスと同じように CSS クラス名として使用します。
hover:fade-in とする事で、マウスホバーをトリガにして変化します。

<button 
  class="rounded-md bg-blue-300 p-2 opacity-10 hover:fade-in"
>サンプル</button>

addComponents を使ってアラート表示を作る

次は引数に addComponents を宣言して、アラート表示するパーツを作ってみましょう。

// tailwind.config.js
const baseConfig = {
...
plugin(({ addComponents }) => {
  const components = {
    '.alert': {
      backgroundColor: 'rgba(150, 0, 10, 1)',
      borderColor: 'rgba(255, 0, 0, 1)',
      borderRadius: '8px',
      color: 'rgba(255, 255, 255, 1)',
      padding: '1rem',
    },
  };
  addComponents(components);
}),

rgba() は CSS で関数表記により色を宣言するものです。
red, green, blue を 1〜255 で範囲指定し、続けて alpha(透過度) を 0.0〜1.0 で範囲指定します。

'.alert': {
  backgroundColor: 'rgba(150, 0, 10, 1)',
  borderColor: 'rgba(255, 0, 0, 1)',

宣言した alert はビルトインのユーティリティクラスと同じように CSS クラス名として使用します。

<div class="alert">エラーが発生しました</div>

addUtilities と addComponents は何が違うのか

addUtilities addComponents はどちらも引数で宣言し、JavaScript のオブジェクトを渡す記法も同じです。

plugins: [
  plugin(({ addUtilities }) => {
    const utilities = { ... };
    addUtilities(utilities);
  }),
],
plugins: [
  plugin(({ addComponents }) => {
    const components = { ... };
    addComponents(components);
  }),
],

tailwindcssには base components utilities の「バケット」があり、これらは CSS を適用するスコープを示しています。
addUtilities addComponents は登録先のバケットを区別するために異なる関数が用意されたものです。

plugin(({ addUtilities, addComponents, addBase }) => {
  // utilities バケットに登録
  const utilities = {
    '.fade-in': { ... }
  };
  addUtilities(utilities);

  // components バケットに登録
  const components = {
    '.alert': { ... }
  };
  addComponents(components);

  // base バケットに登録
  const base = {
    'h1': { ... }
  };
  addBase(base);
}),

tailwindcss のビルドコマンドを叩くと、それぞれのバケットの末尾にカスタムプラグインが出力される事がわかります。

./node_modules/.bin/tailwind build -o ./build.css 
/* build.css */

/* base バケット */
textarea { ... }
input { ... }
button { ... }
/* 追加したプラグイン */
h1 { ... }


/* components バケット */
.bg-gray-100 { ... }
.bg-gray-200 { ... }
/* 追加したプラグイン */
.alert { ... }


/* utilities バケット */
.transition { ... }
/* 追加したプラグイン */
.fade-in { ... }

tailwindcss では詳細度の高い CSS が優先して適用されるように、宣言した順に関わらずバケットの出力順は常に一定となっています。

カスタムプラグインをバリアント指定できるようにする

tailwindcss では hover: sm: dark: など、実行環境やユーザーアクションにより変化する状態を「バリアント」として指定します。
addUtilities などの関数にはカスタムプラグインをバリアントと組み合わせて使うためのオプションを指定する事ができます。

// tailwind.config.js
const baseConfig = {
...
  plugin(({ addUtilities }) => {
    const utilities = {
      '.full-screen': {
        height: '100vh',
        width: '100vw',
      },
      '.half-screen': {
        height: '50vh',
        width: '50vw',
      },
    };

    // "sm:" "md:" "lg:" などレスポンシブのバリアントを有効にする
    addUtilities(utilities, {
      variants: ['responsive']
    });
  }),
],
<div class="full-screen lg:half-screen">foobar</div>

わざわざオプションでバリアントを指定するのは、部分的に有効にする事でビルドサイズを肥大させないというねらいがあるようです。
手元で試したところ variants オプションを指定しなくとも hover:lg: などのバリアントはカスタムプラグインで使う事ができたため、標準で有効になっているものがあるかもしれません。

ビルトインのユーティリティクラスを参照する

text-2xl などビルトインのユーティリティクラスを参照する場合は、引数に theme を宣言して使います。

// tailwind.config.js
const baseConfig = {
...
  plugin(({ addUtilities, theme }) => {
    '.lead': {
      // "text-2xl" の外観を引用
      fontSize: theme('fontSize.2xl'),
    }

    addUtilities(utilities);
  }),
],

または config を宣言すると同じようにビルトインのユーティリティクラスが参照できます。

// tailwind.config.js
const baseConfig = {
...
  plugin(({ addUtilities, config }) => {
    '.lead': {
      // "text-2xl" の外観を引用
      fontSize: config('theme.fontSize.2xl'),
    }

    addUtilities(utilities);
  }),
],

themeconfig では得られる値のフォーマットに違いがあるようです。

theme('fontSize.2xl')
> '1.5rem'

config('theme.fontSize.2xl')
> [ '1.5rem', { lineHeight: '2rem' } ]

config のほうがより詳細な情報が得られますが、フラットなオブジェクトでないため適用にはひと手間かかりそうです。

plugin(({ addUtilities, theme, config }) => {
  '.lead': (([fontSize, options]) => {
      // { fontSize: '1.5rem', lineHeight: '2rem' } というオブジェクトに変換する
      return { fontSize, ...options };
    })(config('theme.fontSize.2xl')),
  }

  addUtilities(utilities);
}),

addVariant を使ってバリデーションエラーを表現するボタンを作る

addVariant を使うと foobar: のようなカスタムバリアントを作る事ができます。
最後に「フォームがバリデーションの要件を満たしていない時に submit ボタンの外観を変更する」というバリアントを作ってこの記事を締めくくりたいと思います。

const plugin = require('tailwindcss/plugin');

// tailwind.config.js
const baseConfig = {
  ...
  plugins: [
    plugin(({ addVariant, e }) => {
      addVariant('form-invalid', ({ modifySelectors, separator }) => {
        modifySelectors(({ className }) => {
          return `form:invalid .form-invalid${e(separator)}${className}`;
        });
      });
    }),
  ],
  variants: {
    extend: {
      backgroundColor: ['form-invalid'],
    }
  },

addVariant の最初の引数には form-invalid: のようにバリアント名として使うテキストを指定します。

addVariant('form-invalid', ({ modifySelectors, separator }) => {
  ...
}

addVariant は第二引数の関数の中で特別な引数を受け取ります。
modifySelectors とはビルドファイルに出力する CSS を作る役割を担っています。

addVariant('form-invalid', ({ modifySelectors, separator }) => {
  modifySelectors(({ className }) => {
    // return "{ ビルドファイルに出力する CSS }";
  });
});

HTML で form-invalid:bg-red-600 と記述した時、 separator には :className には bg-red-600 が代入されます。
eplugin 関数から受け取ったもので、記号をエスケープするために呼び出します。

addVariant('form-invalid', ({ modifySelectors, separator }) => {
  modifySelectors(({ className }) => {
    return `form:invalid .form-invalid${e(separator)}${className}`;
  });
});

background-color のバリアントとしてカスタムバリアントを使えるように variants に登録します。

variants: {
  extend: {
    backgroundColor: ['form-invalid'],
  }
},

結果、ビルドファイルには以下のような CSS クラスが出力されます。

form:invalid .form-invalid\:bg-red-600 {
  --tw-bg-opacity: 1;
  background-color: rgb(220 38 38 / var(--tw-bg-opacity));
}

HTML にフォームを記述してみましょう。

<form>
  <input type="text" required />
  
  <button
    class="rounded p-1 text-white bg-blue-600 form-invalid:bg-red-600"
    type="submit"
  >登録</button>
</form>

input[required] が要件を満たさない(空欄)の時、button には ".form-invalid\:bg-red-600" という CSS クラスの外観が適用されます。

CSS の 擬似クラス には form-invalid なんてものは存在しないので、: をエスケープして擬似クラスと解釈されないようにしている訳ですね。

ビルトインのユーティリティクラスでもこのようなビルドファイルへの出力が行われていました。この仕組みを知った時 tailwindcss の設計に感嘆の声を漏らしてしまいました。すごいですね tailwindcss。考え抜かれてますね。

おわりに

今回はカスタムプラグインの記述方法に焦点を当てて記事を書きました。
カスタムプラグインが増えてもっさりしてきたら、プロジェクト内で外部プラグイン化するなど工夫してみたいと思います!

1
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?