Panda CSSで動作をカスタマイズする

## はじめに
[Panda CSS](https://panda-css.com/)は[Chakra UI](https://www.chakra-ui.com/)が手がける型安全なCSS in JSのツールです。

Panda CSSでスタイリングを行うときは、パッケージを直接使うのではなく、生成したファイルを参照して利用します。
[Playground](https://play.panda-css.com/)を見るとわかるように、`@pandacss/dev`ではなく、`styled-system`を参照してスタイリングを行なっています。

この記事ではこのようには吐き出されるファイルに標準を超えて手を出したいときに利用できるhooksとそれをプラグインとして切り出す方法を紹介します。

## hooks
`hook`はPanda CSSがファイルを生成する特定のライフサイクルで新たな動作と既存の動作の変更を追加するものです。

`hook`はPanda CSSの設定ファイルである`panda.config.ts`で`defineConfig`の中に`hooks`をキーとして配列で付与します。

指定可能なライフサイクルは10個あります(プライベートなものも合わせると11個です)。

### `config:resolved`
`config:resolved`は`panda.config.ts`が解決された後に呼ばれるhookです。hookの中で1番最初に呼び出されます。

#### 例
`config:resolved`を使って解決済みの設定に変更を加えます。
```ts
import { defineConfig } from '@pandacss/dev';
 
export default defineConfig({
  // ...
  hooks: {
    'config:resolved': ({ config, utils }) => {
      return utils.omit(config, ['eject']);
    },
  },
});
```
これによって、`panda.config.ts`で設定された`eject`を削除した状態に設定が変わります。

### `tokens:created`
[`token`](https://panda-css.com/docs/theming/tokens)を生成するエンジンが生成されたときに呼ばれます。

#### 例
`tokens:created`を使ってCSS変数の名前やトークンの名前に対して共通の操作を行います。
```ts
import { defineConfig } from '@pandacss/dev';
 
export default defineConfig({
  hooks: {
    'tokens:created': ({ configure }) => {
      configure({
        formatTokenName: (path) => operation(path, 'token'),
        formatCssVar: (path, config) => ({
          var: operation(path, 'var', config.prefix),
          ref: operation(path, 'ref', config.prefix),
        }),
      });
    },
  },
});
```

### `utility:created`
クラス名を生成するエンジンが生成されたときに呼ばれます。



#### 例
`utility:created`を使って生成するクラス名をハッシュ化する方法を指定します。
```ts
import { defineConfig } from '@pandacss/dev';
 
export default defineConfig({
  hooks: {
    'utility:created': ({ configure }) => {
      configure({
        toHash: (paths, toHash) => {
          return toHash(paths.join('-'));
        },
      }),
    },
  },
});
```
有無を言わさずハッシュ化するわけではなく、`hash`の設定が`true`の時のみ処理が走ります。

### `context:created`
ファイルに書き出す内容を処理する準備が完了したタイミングで呼ばれます。

#### 例
`context:created`から一連の処理で利用する`logger`を取り出します。
```ts
import { defineConfig } from '@pandacss/dev';
import { LoggerInterface } from '@pandacss/types';

let logger: LoggerInterface;
export default defineConfig({
  hooks: {
    'context:created': (context) => {
      logger = context.logger;
    },
  },
});
```
`console.log`ではログが出ないことがあるので、これを用いてログ出力を行います。

### `config:change`
`panda.config.ts`やPanda CSSを利用しているファイルに変更があったときに呼ばれます。

#### 例
`config:change`を用いて変更内容を確認します。
```ts
import { defineConfig } from '@pandacss/dev';
import { LoggerInterface } from '@pandacss/types';

let logger: LoggerInterface;
export default defineConfig({
  hooks: {
    'context:created': (context) => {
      logger = context.logger;
    },
    'config:change': (context) => {
      logger.debug(
        'plugin',
        message(contenxt.changes.diffs),
      );
    },
  },
});
```
どちらかといえばデバックで利用しやすいhookだと思っています。

### `parser:before`
Panda CSSを利用しているファイルを解析する前に呼ばれます。

#### 例
`parser:before`で解析の対象となるファイルを確認します。
```ts
import { defineConfig } from '@pandacss/dev';
import { LoggerInterface } from '@pandacss/types';

let logger: LoggerInterface;
export default defineConfig({
  hooks: {
    'context:created': (context) => {
      logger = context.logger;
    },
    'parser:before': (context) => {
      logger.debug(
        'plugin',
        context.filePath,
      );
    },
  },
});
```
大量のログが出るので、見つけたいファイルの種類が決まっている場合は絞り込むと良いです。

### `parser:after`
Panda CSSを利用しているファイルを解析した後に呼ばれます。

#### 例
`parser:after`で解析の結果を確認します。
```ts
import { defineConfig } from '@pandacss/dev';
import { LoggerInterface } from '@pandacss/types';

let logger: LoggerInterface;
export default defineConfig({
  hooks: {
    'context:created': (context) => {
      logger = context.logger;
    },
    'parser:after': (context) => {
      logger.debug(
        'plugin',
        message(context.result),
      );
    },
  },
});
```
これもファイルごとに出力されるので、確認したいファイルの種類が決まっている場合は絞り込むと良いです。

### `codegen:prepare`
`codegen`で生成した内容をファイルへ書き込む前に呼ばれます。

### `codegen:done`
`codegen`で生成した内容をファイルへ書き込んだ後に呼ばれます。
### `cssgen:done`
`done`ですが、最終的なCSSをファイルへ書き込む前に呼ばれます。
#### 例
利用しないコードを削除して軽量化を促進します。
```ts
import { defineConfig } from '@pandacss/dev';

export default defineConfig({
  hooks: {
    'cssgen:done': ({ artifact, content }) => {
      if (artifact === 'styles.css') {
        return removeUnusedCss(content);
      }
    },
  },
});
```

## プラグイン
作成したhookはプラグインとして共有できます。型をつけたい場合は`@pandacss/types`を利用します。
```ts
import { PandaPlugin } from '@pandacss/types';

const examplePlugin: PandaPlugin = {
  name: 'example',
  hooks: {
    'config:change': configChangedHook,
  },
}
```
作成したプラグインは`panda.config.ts`でインポートします。

```ts:panda.config.ts
import { defineConfig } from '@pandacss/dev';
 
export default defineConfig({
  plugins: [examplePlugin],
});
```

## おわりに
Panda CSSで動作をカスタマイズする方法を紹介しました。
細かなライフサイクルごとにカスタマイズ可能なので、Panda CSS本体ではやってくれないが自分たちのワークスペースでは行ないたい何かを作りやすいです(パフォーマンスの悪さを感じた時の調査でも使えます)。このような自由度の高さはPanda CSSの特徴の1つだと思うので、気になった方はぜひPanda CSSを使ってみましょう。