『Viteで環境構築』  Vue3, TypeScript, Vuetify, StoryBook

## はじめに
`npm create vuetify@latest`を使わずにviteを利用してvue3 vuetifyの環境を作成し、StoryBookを追加します。

## Vueプロジェクトの作成

1 vue projectの作成
```
$ npm create vite@latest
  - Vue → TypeScript
```

## VueとVuetifyの統合

1 vuetifyのinstall
[『Vuetify公式』 既存プロジェクトへの導入](https://vuetifyjs.com/ja/getting-started/installation/#section-65e25b5830d730ed30b830a730af30c830674f7f304658345408)

```
$ npm install vuetify
```

2 vite-plugin-vuetifyのinstall
[『Vuetify公式』 TreeShaking(不要コードの削除)](https://vuetifyjs.com/ja/features/treeshaking/#automatic-treeshaking)
[『npm』vite-plugin-vuetify](https://www.npmjs.com/package/vite-plugin-vuetify)
```
$ npm i vite-plugin-vuetify
```

3 fileを変更

  3-1 Vueアプリケーションを作成するファイルに以下を追加 (main.ts) (createAppするところ)
```ts
import { createApp } from 'vue'

// Vuetify
import 'vuetify/styles'
import { createVuetify } from 'vuetify'

// Components
import App from './App.vue'

const vuetify = createVuetify()

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

  3-2 vite.config.ts
```ts
import vue from '@vitejs/plugin-vue'
import vuetify from 'vite-plugin-vuetify'

export default {
  plugins: [
    vue(),
    vuetify(), // vue()の後ろに設定すること
  ],
}
```


## error解決
  main.tsの`import 'vuetify/styles'`で『モジュール 'vuetify/styles' またはそれに対応する型宣言が見つかりません。』というerrorが発生する方がいるかもしれません。
  [副作用の発生するかもしれないimportを警告する機能がONになっているのが原因です](https://www.typescriptlang.org/ja/tsconfig/#noUncheckedSideEffectImports)

解決策1: インラインでnoUncheckedSideEffectImportsのルールを無効化する
```ts
// @ts-expect-error -- side effects import
import 'vuetify/styles'
```

解決策2: `tsconfig.app.json`で`"noUncheckedSideEffectImports": true`のルールを削除する
```json
{
...
// 以下を消す
"noUncheckedSideEffectImports": true
...
}
```


4 お好みで
[Iconを使う場合はinstall](https://vuetifyjs.com/en/features/icon-fonts/#usage)
[Sass変数を利用してVuetifyのデフォルトスタイルをカスタマイズ](https://vuetifyjs.com/ja/features/sass-variables/#section-30a430f330b930c830fc30eb)

5 App.vueで動作確認
styleの当たったchipが表示されれば成功です
```
<v-chip>Chip Component</v-chip>
```

## StoryBook

1 rootディレクトリで以下を実行 (自動で必要な初期設定をやってくれる)
[『StoryBook公式』 StoryBook with Vite](https://storybook.js.org/docs/get-started/frameworks/vue3-vite?renderer=vue)
```
$ npx storybook@latest init
```

2 vuetify componentを使えるように設定する 
[『StoryBook公式』 VuetifyとStorybookを統合する](https://storybook.js.org/recipes/vuetify)
  
  2-1 StoryBookにVuetifyを登録
  今回の作成方法だとregisterPlugins関数を使用していないので、公式ページの例と若干違います
```ts
// .storybook/preview.ts

// 追加
import { setup } from "@storybook/vue3";
import "vuetify/styles";
import { createVuetify } from "vuetify";

...

// 追加
setup((app) => {
  const vuetify = createVuetify();
  app.use(vuetify);
});
```

  2-2 ストーリーラッパーコンポーネントの作成
  storybookでvuetifyを扱うには`<v-app></v-app>`でラップする必要があります。
```ts
<!-- .storybook/StoryWrapper.vue -->
 
<template>
  <v-app>
    <v-main>
      <slot name="story"></slot>
    </v-main>
  </v-app>
</template>


// 公式では記載されていませんが、 setup lang="ts"を記述しないと後にimportした際にtsのerrorが表示されました
// 公式はtsではなくjsを前提に解説されています
<script setup lang="ts"></script>
```

  2-3 .withVuetifyThemeデコレータを作成する
```ts
// .storybook/withVeutifyTheme.decorator.ts

import { h } from 'vue';
// ここでtsのerrorが出る場合はStoryWrapper.vueでsetupやlang="ts"を記述してみてください
import StoryWrapper from './StoryWrapper.vue';

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

  return () => {
    return h(
      StoryWrapper,
      {}, // Props for StoryWrapper
      {
        // Puts your story into StoryWrapper's "story" slot with your story args
        story: () => h(story, { ...context.args }),
      }
    );
  };
};
```

  2-4 作成したでコレータをpreview.jsのStorybookに渡す
```ts
// 追加
import { withVuetifyTheme } from "./withVuetifyTheme.decorator";

// 追加
export const decorators = [withVuetifyTheme];
```

 最終的なprevie.ts
 初期設定で生成されたコードも含めると以下のようになります。
```ts
import type { Preview } from "@storybook/vue3";

// 以下importは全部追加したコード
import { setup } from "@storybook/vue3";
import "vuetify/styles";
import { createVuetify } from "vuetify";
import { withVuetifyTheme } from "./withVuetifyTheme.decorator";

const preview: Preview = {
  parameters: {
    controls: {
      matchers: {
        color: /(background|color)$/i,
        date: /Date$/i,
      },
    },
  },
};

// 追加したコード
setup((app) => {
  const vuetify = createVuetify();
  app.use(vuetify);
});

export default preview;

// 追加したコード
export const decorators = [withVuetifyTheme];
```


## 動作確認

1 適当にお好きなコンポーネントを作成
```ts
// src/components/BaseChip.vue
<script setup lang="ts">
withDefaults(
  defineProps<{
    color?: string;
    closable?: true;
    onClick?: () => void;
    onClickClose?: () => void;
  }>(),
  {
    color: "primary",
    onClick: undefined,
    onClickClose: undefined,
  }
);
</script>

<template>
  <v-chip
    :color="color"
    @onClick="onClick"
    @click:close="onClickClose"
    :closable="closable"
>      
    <slot></slot>
  </v-chip>
</template>
```

2 簡易的ですが表示確認用のStoryを作成
```ts
// src/components/BaseChip.stories.ts

import type { Meta, StoryObj } from "@storybook/vue3";

import BaseChip from "./BaseChip.vue";

const meta: Meta<typeof BaseChip> = {
  title: "BaseChip",
  component: BaseChip,
  argTypes: {
    color: {
      options: ["primary", "secondary", "success", "error", "warning"],
      control: { type: "select" },
    },
  },
};

export default meta;
type Story = StoryObj<typeof BaseChip>;

export const Primary: Story = {
  args: {
    default: "IT事業促進部",
    color: "primary",
  },
};
```

## おわりに
1 モチベーション
`tsconfig.app.json`の初期設定はviteでの始め方によって設定がまちまちです。
当記事投稿時点では**npm crreate vite@latest** → **vue** → **typescrip**で作成すると`noUncheckedSideEffectImports": true`が含まれており上記errorが発生します。
実際にはPrettier, EsLint, Vue-Routerを使いたいケースがほとんどだと思うので、Vue → Customize with create-vueで作成するとtsconfig.app.jsonに`noUncheckedSideEffectImports": true`が含まれませんので、当エラーは発生しません。 加えてvuetify公式で紹介されている`npm create vuetify@latest`を使った場合も発生しませんので、そちらの利用を検討するのも良いでしょう。 `npm create vuetify@latest`を利用する場合、当記事で紹介した設定以外にも様々設定が加えられ、最適化されているようです。

当記事は学習目的で**vue** → **typescrip**でプロジェクトを開始した際に上記でハマったのが投稿のモチベーションだったりします。

2 小話
Vue x StoryBookは環境構築が難しいと聞いたことがあるのですが、だいぶやりやすくなっているみたいです。先人達に感謝です。

## 参考

Vuetify

『Vuetify公式』プロジェクトへの導入
https://vuetifyjs.com/ja/getting-started/installation/#section-30a430f330b930c830fc30eb

『Vuetify公式』 不要コードの削除(Tree Shaking)
https://vuetifyjs.com/ja/features/treeshaking/

『NPM』 vite-plugin-vuetify
https://www.npmjs.com/package/vite-plugin-vuetify


StoryBook

『StoryBook公式』 VuetifyとStorybookを統合する
https://storybook.js.org/recipes/vuetify

『StoryBook公式』 StoryBook with Vite
https://storybook.js.org/docs/get-started/frameworks/vue3-vite?renderer=vue

『StoryBook公式』 StoryBook Component Vue
https://storybook.js.org/docs/get-started/frameworks/vue3-vite?renderer=vue