Histoireを用いた、Nuxt3+Vite環境でのStory管理

2023/12/09に公開

はじめに

はじめまして。フロントエンドエンジニアをしているむーと申します。
本稿が初投稿になります。何卒よろしくお願いします。

経緯

以前は、かの有名な「StoryBook」で担当のプロジェクトのStoryを記述してきましたが、StoryBook7へ移行を行った際に一部import文のエイリアスが読み込めない問題が発生していました。

そんな中、別の選択肢を探った結果たどり着いたこのライブラリが、本当に良かったので紹介したい気持ちが抑えられませんでした。笑

Histoireとは?

イストワール と読むらしいです。
StoryBook同様、コンポーネントのプロパティをGUIで触って挙動を確認できる、いわばコンポーネントのカタログです。

Vite製で爆速なのはもちろん、Vueメインで力を入れて作成されているため、記述方法も取っ付きやすいことに加え、公式から高品質なドキュメントも提供されています。

2023/12時点では以下のフレームワークとバージョンに対応しています。

  • Vue3
  • Vue2.7
  • Svelte3

環境構成

  • nuxt@^3.8.2
  • histoire@^0.17.6 (現時点最新)
  • pnpm@8.11.0
  • typescript@^5.3.3

導入手順

今回、Nuxt3+Vite環境作成済み前提でお話をさせていただきます。

導入は、以下の記事と公式ドキュメントを参考にさせていただきました。
非常に懇切丁寧に解説いただいており感謝です...!
https://zenn.dev/azukiazusa/articles/histoire-vite-ui-component-cataloging-tool-for-vue
https://histoire.dev/guide/vue3/getting-started.html

インストール

利用したいプロジェクト直下へ移動し、histoire@histoire/plugin-vueのパッケージを追加します。

# pnpm
pnpm i -D histoire @histoire/plugin-vue
# npm
npm i -D histoire @histoire/plugin-vue
# yarn
yarn add -D histoire @histoire/plugin-vue

次に、同階層へhistoire.config.js|tsを追加します。

histoire.config.ts
import { defineConfig } from 'histoire'
import { HstVue } from '@histoire/plugin-vue'

export default defineConfig({
  plugins: [
    HstVue(),
  ],
})

Nuxtを利用していない場合、必要なパッケージはなんとこれだけです。
StoryBookでは、しっかり使おうとすると大量の依存関係の導入を強いられるので、そこもありがたい部分です。

TSをご利用の場合

先ほどの階層へenv.d.tsを追加し、tsconfig.jsonでincludeします。
以下を行うことで、Histoireが提供するグローバルコンポーネントの型が有効になります。

env.d.ts
/// <reference types="@histoire/plugin-vue/components" />
tsconfig.json
  "include": [
    "env.d.ts",
    "src/**/*",
    "src/**/*.vue"
  ]

Nuxtで利用可能にする

といってもこれも非常にシンプルです。
以下に記載されている@histoire/plugin-nuxtを、以前の手順でパッケージをインストールした箇所へ追加でインストールします。
https://histoire.dev/guide/vue3/getting-started.html#nuxt

# pnpm
pnpm i -D histoire @histoire/plugin-nuxt
# npm
npm i -D histoire @histoire/plugin-nuxt
# yarn
yarn add -D histoire @histoire/plugin-nuxt

そして、先ほど追加したhistoire.config.tsへ以下を追記します。

histoire.config.ts
import { defineConfig } from 'histoire'
import { HstVue } from '@histoire/plugin-vue'
+ import { HstNuxt } from '@histoire/plugin-nuxt'

export default defineConfig({
  plugins: [
    HstVue(),
+   HstNuxt(),
  ],
})

たったこれだけです。本当にこれだけで動くのかってくらいこれだけです。笑

実際にStoryを書いてみる

https://ui.nuxt.com/elements/alert
今回は例で、基底コンポーネントを、Nuxt公式が提供している@nuxt/uiのコンポーネント「UAlert」の腐敗防止用として以下のように作成したとします。

BaseAlert.vue
<script setup lang="ts">
import type { AlertVariant } from "@nuxt/ui/dist/runtime/types";

type BaseAlertType = 'success' | 'danger' | 'warning' | 'info'
type Props = {
  title: string
  type?: BaseAlertType
  heroiconName?: string
}
const props = withDefaults(defineProps<Props>(), {
  type: 'success',
  heroiconName: '',
})
const color = computed(() => {
  switch (props.type) {
    case 'warning':
      return 'amber'
    case 'danger':
      return 'red'
    case 'info':
      return 'indigo'
    case 'success':
    default:
      return 'emerald'
  }
})
</script>

<template>
  <UAlert :title="title" :icon="heroiconName" :color="color">
    <template #description>
      <slot />
    </template>
  </UAlert>
</template>

このストーリーを、公式ドキュメントを参考に~.story.vue形式で以下のように作成します。

BaseAlert.story.vue
<script setup lang="ts">
const state = reactive({
  description: 'This is description.'
})
</script>

<template>
  <Story :layout="{ type: 'grid', width: '90%' }">
    <template #controls>
      <HstText v-model="state.description" title="description" />
    </template>
    <Variant title="success">
      <BaseAlert type="success" title="Success">{{ state.description }}</BaseAlert>
    </Variant>
    <Variant title="warning">
      <BaseAlert type="warning" title="Warning">{{ state.description }}</BaseAlert>
    </Variant>
    <Variant title="danger">
      <BaseAlert type="danger" title="Danger">{{ state.description }}</BaseAlert>
    </Variant>
    <Variant title="info">
      <BaseAlert type="info" title="Information">{{ state.description }}</BaseAlert>
    </Variant>
  </Story>
</template>

以下のコマンドで動かしてみます。

$ histoire dev

すると以下のように表示されます。

向かって画面左側がStory一覧、中央が選択したStoryのVariant一覧、右側がcontrols等の情報ですね。

コンポーネントのPropsを検出して自動的にcontrolsを追加してくれるので、slotに入れる値以外は自力でcontrolsを追加する必要がない部分も楽ちんですね。(オプションで無効にもできたはずです。場合によってはそちらの方が都合が良いと思っています。)

今回の例のように、Storyコンポーネント直下のcontrolsslotへ追加した場合、当該StoryすべてのVariantにおいて共通のcontrolが追加されます。
また、Variantコンポーネント直下にも同様のslotが用意されており、そちらへ追加することも可能です。その場合は前者とは異なり、Variant毎にcontrolを出しわけることができます。

最後に

VueのSFCと全く同一の記述方法でストーリーが記述できるので学習コストも低く、この手のライブラリでありがちな、現行に置いていかれてしまう要因の一つにもなるスイッチングコストという億劫さから解放されると思います。

素晴らしいライブラリなので、ご興味を持っていただけましたら是非お試しいただけますと幸いです!

ご覧いただきありがとうございました!

Discussion