備忘録 storybook7系とvuetify3の連携方法

この記事は[TechCommit Advent Calendar 2023](https://adventar.org/calendars/8839)の13日目の記事です｡


# 前置き 
* vueプロジェクトはすでに作成済みという前提で話を進めます
* storybookはすでにインストール済み､初期設定済みという前提で話を進めます
* 関係なさそうな箇所はほとんど省略しております｡
* この記事が100%正しいという保証はありません｡｢おかしいな?｣と思ったら別サイトを見ることを推奨します｡

# 環境
## 各バージョン
||バージョン|
|--|--|
|vite|7.6.3|
|vue|3.3.4|
|vuetify|3.4.4|
|storybook|7.6.3|

## ディレクトリ構造
**今回の説明に関係なさそうなやつは全て省略**
./storybook
&emsp;├ main.js
&emsp;├ preview.js
&emsp;├ StoryWrapper.vue
&emsp;└ withVeutifyTheme.decorator.js
./src
&emsp;├ plugins
&emsp;&emsp;├ vuetify.js

# 1. vuetifyをインストール
```
npm i vuetify
```

# 2. srcフォルダの中にpluginフォルダを作り､その中にvutify.jsファイルを作る
**目的**
* `main.js`のコード量を減らすため
* 1つのファイルに書いて置くことによって二度手間を防ぐ

(というか､storybook関係なしにvuetifyにしろ､vuexにしろ､routerにしろこのようにするのが主流らしい)

vuetify公式のコードをちょっとだけいじるだけでok
```javascript:src/plugins/vuetify.js
import 'vuetify/styles'
import { createVuetify } from 'vuetify'
import * as components from 'vuetify/components'
import * as directives from 'vuetify/directives'

export default createVuetify({
  components,
  directives,
})
```

# 3. srcフォルダの中にあるvueのmain.jsを編集
**目的**
vueでvuetifyを使えるようにするため

```diff_javascript:src / main.js
import './assets/main.css'

import { createApp } from 'vue'
import App from './App.vue'
+ import vuetify  from './plugins/vuetify'

- createApp(App).mount('#app')
+ createApp(App).use(vuetify).mount('#app')
```


# 4. ラッパーを作る
ここからstorybookの話になります｡
**目的**
vuetifyは`v-app`タグで囲まないと正しく動かないので[(公式でも言ってる)](https://v2.vuetifyjs.com/ja/components/application/#api)
storyのコンポーネントを`v-app`タグで囲む


```javascript:.storybook/StoryWrapper.vue
<template>
  <v-app>
    <v-main><slot name="story"></slot></v-main>
  </v-app>
</template>

<style>
.v-application__wrap {
  min-height: auto !important;
  /* これをしないと大きな余白が作られる */
  /* !importantしないと設定が上書きされるもよう */
}
</style>
```

# 5. withVuetifyTheme.decorator.jsを作る 
**目的**
ストーリーブックのストーリーコンポーネントにVuetifyのテーマを適用するため

```javascript:./storybook/withVuetifyTheme.decorator.js
import { h } from 'vue';
import StoryWrapper from './StoryWrapper.vue';

export const withVuetifyTheme = (storyFn, context) => {
  const story = storyFn();

  return () => {
    return h(
      StoryWrapper,
      {}, 
      {
        story: () => h(story, { ...context.args }),
      }
    );
  };
};
```

##### 何やってるかわからないのでchatgptに聞いてみた｡
コピペすると色々崩れるからスクショで
![スクリーンショット (7310).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/421721/06abbf52-5023-df01-4315-e4f01fcdea7c.png)

**`h()`について知りたい人は以下のサイトもどうぞ**

https://reffect.co.jp/vue/vue-js-render

https://ja.vuejs.org/guide/extras/render-function

https://vueframework.com/docs/v3/ja/guide/render-function.html#h-%E3%81%AE%E5%BC%95%E6%95%B0

# 6. 仕上げにpreview.jsに今まで作ったパーツを全部つなげる

```diff_javascript:.storybook/preview.js
/** @type { import('@storybook/vue3').Preview } */
+ import { setup } from '@storybook/vue3';
+ import { withVuetifyTheme } from './withVeutifyTheme.decorator';
+ import vuetify from '@/plugins/vuetify'

+ setup((app) => {
+   app.use(vuetify)
+ });

const preview = {
  parameters: {
+   decorators : {withVuetifyTheme},
    actions: { argTypesRegex: "^on[A-Z].*" },
    controls: {
      matchers: {
        color: /(background|color)$/i,
        date: /Date$/i,
      },
    },
  },
};


export default preview;
```
これで連携ができたはずです!
お疲れ様でした!

#### 別の書き方について
一部の方々は上記の書き方に対して
｢あれ?デコレーターは以下のように書くんじゃないの?｣という疑問を持っているかもしれません｡

```javascript
export const decorators = [withVuetifyTheme];
```
この書き方でも問題なく動きました｡

chatgptに質問したところ
この書き方は古いバージョンの書き方らしいです｡
**Storybook 6.0**以降では､previewオブジェクトを使うことが推奨されていて､このオブジェクトのparametersプロパティにdecoratorsを指定します。

どちらの書き方でも問題ないようです｡

# 次の記事紹介
14日目はやっしさんによる[LeetCodeでアルゴリズム力を鍛えよう！](https://qiita.com/Yasshi-cookie/items/7f730ad92beb42a163b1)です｡
# 駄文のコーナー(ここから先は編集後記みたいなものなので飛ばしても大丈夫です｡)
以下のサイトを参考にに作業していました｡

https://storybook.js.org/recipes/vuetify

ただこのサイトの`preview.js`で以下のようなことが出てきたんです｡
```javascript
import { registerPlugins } from '../src/plugins';

setup((app) => {
  // Registers your app's plugins into Storybook
  registerPlugins(app);
});
```

`registerPlugins`ってなに!?
`../src/plugins`ってなんのこと!?
ってなって｡これらが分からず色々調べてました｡

どうやら
`src`フォルダの中`plugin`フォルダの中に`index.js`ファイルを作成し､そのファイルの中に
`registerPlugins`関数を作成して、引数で渡された`Vue`のアプリケーションに対して、フォントやプラグインを適用するというやりかたがあるようです｡

例えばとしてこんな感じ
```javascript
import vuetify from './vuetify'
import pinia from './store'
import router from './router'

export function registerPlugins(app) {
    app.use(vuetify)
        .use(router)
        .use(pinia)
}
```

今まで私は､`preview.js`にも､`main.js`にもvuetifyの設定は直接書き込んでました｡

あと､私が作ったvueプロジェクトは､
storybookになれるために
storybookの公式サイトのvueチュートリアル以外のアプリを作ってstorybookを連携させて､
その後にvuetifyとの連携の仕方を勉強するためにvuetifyを当てただけなので｡`router`も`pinia`や`vuex`も使わず`vuetify`しか使わないので今回は`registerPlugins`を使わない方法を取りました｡