Histoireを用いた、Nuxt3+Vite環境でのStory管理
はじめに
はじめまして。フロントエンドエンジニアをしているむーと申します。
本稿が初投稿になります。何卒よろしくお願いします。
経緯
以前は、かの有名な「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環境作成済み前提でお話をさせていただきます。
導入は、以下の記事と公式ドキュメントを参考にさせていただきました。
非常に懇切丁寧に解説いただいており感謝です...!
インストール
利用したいプロジェクト直下へ移動し、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
を追加します。
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が提供するグローバルコンポーネントの型が有効になります。
/// <reference types="@histoire/plugin-vue/components" />
"include": [
"env.d.ts",
"src/**/*",
"src/**/*.vue"
]
Nuxtで利用可能にする
といってもこれも非常にシンプルです。
以下に記載されている@histoire/plugin-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
へ以下を追記します。
import { defineConfig } from 'histoire'
import { HstVue } from '@histoire/plugin-vue'
+ import { HstNuxt } from '@histoire/plugin-nuxt'
export default defineConfig({
plugins: [
HstVue(),
+ HstNuxt(),
],
})
たったこれだけです。本当にこれだけで動くのかってくらいこれだけです。笑
実際にStoryを書いてみる
@nuxt/ui
のコンポーネント「UAlert」の腐敗防止用として以下のように作成したとします。
<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
形式で以下のように作成します。
<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
コンポーネント直下のcontrols
slotへ追加した場合、当該StoryすべてのVariantにおいて共通のcontrolが追加されます。
また、Variant
コンポーネント直下にも同様のslotが用意されており、そちらへ追加することも可能です。その場合は前者とは異なり、Variant毎にcontrolを出しわけることができます。
最後に
VueのSFCと全く同一の記述方法でストーリーが記述できるので学習コストも低く、この手のライブラリでありがちな、現行に置いていかれてしまう要因の一つにもなるスイッチングコストという億劫さから解放されると思います。
素晴らしいライブラリなので、ご興味を持っていただけましたら是非お試しいただけますと幸いです!
ご覧いただきありがとうございました!
Discussion