🎨

【tailwindcss】configから直接、型を取り出してみた

2024/08/05に公開

こんにちは〜、NITICのこうちゅけです。

皆さん、Tailwindcss使ってますか?
.cssファイルを作成せずにスタイルコーディングできるTailwindcssはとてもいいものですよね。

しかし、Typescriptでコーディングしていると、Tailwindcssのデザイントークンに関する型情報がなくてモヤモヤすることが時たまあります。

(実際にはデフォルトの設定のままでtailwindcssを使用しているのであれば公式から型が用意してあるのですが独自のテーマを設定しているとそれも込みの型が欲しいですよね。)

また、動的にclassを変更する関数を実装しようとした際に型情報がなく型をハードコードすることもあると思います。

今回は設定ファイルから直接、colorの型やpadding, marginなどで使用されているScale(lgや4,8など)の型情報を取得します。

0. 環境

この記事では以下の環境で開発、コード提案を行なっています。

ProductName:		macOS
ProductVersion:		14.5
BuildVersion:		23F79
❯ bun -v   
1.1.12
* typescript: 5.5.4
* tailwindcss: 3.4.7

1. 結論

結論を先に出すと以下のようにすればできます。

tailwindcss.config.ts
import type { Config } from "tailwindcss";

const config = {
  content: [],
  theme: {
    extend: {
      colors: {
        primary: "#ff0000",
        secondary: "#00ff00",
        tertiary: "#0000ff",
      },
    },
  },
  plugins: [],
} satisfies Config;

export default config;

例としてprimary, secondary, tertiaryを定義しています。

src/type.ts
import type resolveConfig from "tailwindcss/resolveConfig";
import type config from "../tailwind.config";

type ResolveConfig = ReturnType<typeof resolveConfig<typeof config>>;

type ResolveConfigTheme = ResolveConfig["theme"];

type Color = keyof ResolveConfigTheme["colors"];
// type Color = "primary" | "secondary" | "tertiary" | keyof DefaultColors
// --- 展開すると ---
// type Color = "primary" | "secondary" | "tertiary" | "inherit" | "current" |
//              "transparent" | "black" | "white" | "slate" | "gray" | "zinc" | 
//              "neutral" | "stone" | "red" | "orange" | ... 20 more

type FontSize = keyof ResolveConfigTheme["fontSize"];
// type FontSize = "xs" | "sm" | "base" | "lg" | "xl" | "2xl" | "3xl" | 
//                 "4xl" | "5xl" | "6xl" | "7xl" | "8xl" | "9xl"

type ZIndex = keyof ResolveConfigTheme["zIndex"];
// type ZIndex = "0" | "10" | "20" | "30" | "40" | "50" | "auto"

tailwindcss.config.tsで定義しているcolor情報などもあせて取得することができます。

つまり、configが変わったから型を変える…というようなことをしなくて済むようになります。

2. 詳細

さて、結論だけで終わりにしても一体何してるん?ってなるだけなのでコードの詳細と注意事項を書いていきます。

tailwind.config.ts

ファイルの作成

tailwindcssのconfigファイルを作成します。

# 1
bunx tailwindcss init

# 2
bunx tailwindcss init --ts

1のコマンドで作成すると.jsファイルが生成されますが .ts に直してください。

型を取得するのに.jsだと取れるものも取れませんからね。

カスタマイズする

次に自分好みにconfigを書いていきます。
今回はcolorだけを定義しましたが、特に制約はないので好きに書いてください。

tailwind.config.ts
import type { Config } from "tailwindcss";

const config = {
  content: [],
  theme: {
    extend: {
      colors: {
        primary: "#ff0000",
        secondary: "#00ff00",
        tertiary: "#0000ff",
      },
    },
  },
  plugins: [],
} satisfies Config;

export default config;

ここで注意しないといけないのが型の付与の仕方です。
Configの型の変数を定義するのではなく、Configの型に沿ったオブジェクトを定義するようにします。
そのため、satisfies構文を使用して変数を定義します。

// ❌
const config: Config = {
  ...
}

// ⭕️
const config = {
  ...
} satisfies Config;

Config型のObjectデータにしてしまうと、後述する設定の結合時にカスタマイズした型情報がのらず型抽出ができないためです。

型を抽出する

configができたら次はデフォルトのconfigと結合する必要があります。
tailwindcssは設定の結合する関数をtailwindcss/resolveConfigで用意しているのでこれを使用します。

import resolveConfig from "tailwindcss/resolveConfig";
import config from "../tailwind.config";

resolveConfig(config);

このような記述でデフォルト設定とカスタマイズした設定を結合します。

しかし、欲しいのはこれの返り値の型情報です。
なので型だけになるように修正します。
tailwindcss/resolveConfigで用意されている関数の型情報はこのように定義されています。

tailwindcss/resolveConfig.d.ts
import { Config, ResolvableTo, ThemeConfig } from './types/config'
import { DefaultTheme } from './types/generated/default-theme'
import { DefaultColors } from './types/generated/colors'

type ResolvedConfig<T extends Config> = Omit<T, 'theme'> & {
  theme: MergeThemes<
    UnwrapResolvables<Omit<T['theme'], 'extend'>>,
    T['theme'] extends { extend: infer TExtend } ? UnwrapResolvables<TExtend> : {}
  >
}

type UnwrapResolvables<T> = {
  [K in keyof T]: T[K] extends ResolvableTo<infer R> ? R : T[K]
}

type ThemeConfigResolved = UnwrapResolvables<ThemeConfig>
type DefaultThemeFull = DefaultTheme & { colors: DefaultColors }

type MergeThemes<Overrides extends object, Extensions extends object> = {
  [K in keyof ThemeConfigResolved | keyof Overrides]: (K extends keyof Overrides
    ? Overrides[K]
    : K extends keyof DefaultThemeFull
    ? DefaultThemeFull[K]
    : K extends keyof ThemeConfigResolved
    ? ThemeConfigResolved[K]
    : never) &
    (K extends keyof Extensions ? Extensions[K] : {})
}

declare function resolveConfig<T extends Config>(config: T): ResolvedConfig<T>
export = resolveConfig

まぁ、要約するとresolveConfigの型引数に結合したいObjectの型を渡せばいいだけです。

import type resolveConfig from "tailwindcss/resolveConfig";
import type config from "../tailwind.config";

type ResolveConfig = ReturnType<typeof resolveConfig<typeof config>>;

これで結合後のconfigの型情報が取得できました。
例として、colorの型を取得しようと思います。colorはtheme/colorsにあるのでその通りに記述していきます。

import type resolveConfig from "tailwindcss/resolveConfig";
import type config from "../tailwind.config";

type ResolveConfig = ReturnType<typeof resolveConfig<typeof config>>;

type ResolveConfigTheme = ResolveConfig["theme"];

type Color = keyof ResolveConfigTheme["colors"];

これで抽出ができました!!
あとは欲しい型情報を自分で抽出してみてください。

終わりに

今回はtailwindcssのconfigから型情報を抽出してみました。
思ったより上手く抽出できたのでよかったです。

もっと良い記述があれば教えてください。

では、また!

GitHubで編集を提案

Discussion