Vue 向けの Vite 製の UI コンポーネントカタログツール Histoire
Histoireはフランス語で「Story」という意味の単語であり、Storybook のように UI コンポーネントのカタログを作成するツールです。Vite にネイティブ対応、Vue の SFC 形式で Story を書けるといった特徴があります。
Histoire はフランス語で「Story」という意味の単語であり、Storybook のように UI コンポーネントのカタログを作成するツールです。
Histoire は以下のような特徴を謳っています。
- Vite にネイティブ対応
- Histoire は Vite 向けのツールであるので、
vite.config.ts
の設定を再利用できます。このあたりの特徴は Vitest と同様ですね
- Histoire は Vite 向けのツールであるので、
- Story をフレームワークそのままの書き方で作成できる
- Storybook の場合 Vue でコンポーネントを作成していたとしても Story を作成する場合には
.stories.ts
のような拡張子でファイルを作成して Storybook 向けのコンポーネントの記述をする必要があります。一方 Histoire は Story を作成する際にも.vue
や.svelte
のような拡張子を使用でき、フレームワークの特徴に合わせた書き方ができます。
- Storybook の場合 Vue でコンポーネントを作成していたとしても Story を作成する場合には
- 早くて軽い
- やはり Vite を使っているだけあってビルド速度は高速なようです
- 拡張性が高い
- すばらしい UX
Histoire をはじめる
それでは早速 Histoire をはじめてみましょう。Histoire は現在(2022/06/04)以下のフレームワークに対応しています。
Framework | Versions | Support | Auto CodeGen | Auto Docs |
---|---|---|---|---|
Vue | 3.2+ | ✅ | ✅ | Todo |
Svelte | - | Planned | - | - |
Solid | - | Planned | - | - |
Angular | - | TBD | - | - |
React | - | Alternative | - | - |
React の代替として Ladle があげられています。Ladle もまた Vite ベースなので Histoire が Vue 向け、Ladle が React 向けという立ち位置のように感じます。
インストール
以下コマンドで Histoire をインストールします。
$ npm i -D histoire
続いて package.json
に scripts を追加します。
{
"scripts": {
"story:dev": "histoire dev",
"story:build": "histoire build",
"story:preview": "histoire preview"
}
}
プロジェクトで TypeScript を使用している場合、env.d.ts
を作成して以下を記述します。
/// <reference types="histoire" />
グローバル CSS を設定する
グローバルに読み込む CSS がある場合 Histoire の設定ファイルを作成する必要があります。設定ファイルは vite.config.ts
の histoire
プロパティとして記載するか、histoire.config.ts
ファイルを新たに作成して設定を記載する 2 通りの方法があります。ここでは後者の方法を使用します。
// histoire.config.ts
import { defineConfig } from 'histoire'
export default defineConfig({
setupFile: '/src/histoire.setup.ts'
})
setupFile オプションは各ストーリープレビューの設定時にデフォルトで実行されるセットアップファイルを指定します。src/histoire.setup.ts
ファイルを作成してそこで CSS を読み込みます。
// src/histoire.setup.ts
import './index.css
Story を作成する
それでは実際に Story を作成しましょう。すべての Story は .story.vue
拡張子を使用します。
<script lang="ts" setup>
import AppButton from "./AppButton.vue";
</script>
<template>
<Story title="buttons">
<AppButton> I am a button </AppButton>
</Story>
</template>
上記のように通常の Vue の SFC ファイルと全く同じスタイルで Story を記述できます。Story は <template>
タグ内の <Story>
タグの中に記述する必要があります。
<Story>
タグは title
Props を受け取ることができ、これを指定すると任意のタイトルを付与できます。
Story を作成したら開発サーバーを起動しましょう。
$ npm run story:dev
http:://localhost:3000 にアクセスすると作成した buttons
ストーリーが表示されます。その他、Design System メニューも自動で作成されており、どうやら TailwindCSS の設定に基づき生成されているようです。
Storybook と同じように Controls タブが存在し、Props を変更できます。
その他の機能としてはデフォルトで以下を設定できるようです。
- ダークモードの切り替え
- レスポンスサイズの設定
- 背景色の設定
Story を複数作成する
<Variants>
タグを使用することで、1 つのコンポーネントに対して複数の表示を作成できます。<Variants>
タグも <Story>
タグと同様に title
Props を与えることができます。
<script lang="ts" setup>
import AppButton from "./AppButton.vue";
</script>
<template>
<Story title="buttons">
<Variant title="default">
<AppButton> I am a button </AppButton>
</Variant>
<Variant title="secondary">
<AppButton color="secondary"> I am a button </AppButton>
</Variant>
<Variant title="disabled">
<AppButton disabled> I am a button </AppButton>
</Variant>
</Story>
</template>
<Variant>
タグを追加したことにより、buttons
メニューの配下にサブメニューが追加されました。
レイアウトの変更
デフォルトでは <Variant>
タグを使用した場合、1 つの variant
につき 1 つのメニュが追加され、それぞれ別のページに表示されます。ですが、時にはすべての variant
を横に並べて表示して比較したいこともあるでしょう。
そのような場合には、<Story>
に layout
Props を渡して { type: 'grid' }
を指定することで、すべての variant
を 1 つのページにグリッドレイアウトで表示できます。
<script lang="ts" setup>
import AppButton from "./AppButton.vue";
</script>
<template>
- <Story title="buttons">
+ <Story title="buttons" :layout="{ type: 'grid' }">
<Variant title="default">
<AppButton> I am a button </AppButton>
</Variant>
<Variant title="secondary">
<AppButton color="secondary"> I am a button </AppButton>
</Variant>
<Variant title="disabled">
<AppButton disabled> I am a button </AppButton>
</Variant>
</Story>
</template>
コンポーネントに状態を渡す
デフォルトでは Controls タブで Props の状態を変更できますが、それ以外の状態をコンポーネントに渡して操作したいこともあるでしょう。例えば、次の例ではボタンコンポーネントのスロットに渡す文字列を状態として保持するようにしています。
<script lang="ts" setup>
import { ref } from "vue";
import AppButton from "./AppButton.vue";
const label = ref("Hello World");
</script>
<template>
<Story title="buttons">
<AppButton>{{ label }}</AppButton>
</Story>
</template>
あまり難しいことは考えずに、普段の Vue で行っている方法と同じく ref
で状態を定義しています。このままでは状態を更新できないので Controls タブに label
を更新できるようにします。
<Story>
タグまたは <Variant>
タグの controls
スロットを使用することで Controls タブにフォームを追加できます。<Story>
タグ直下にスロットを配置した場合すべての variant
の Controls タブにフォームが追加され、<Variant>
タグ配下にスロットを配置した場合にはその variant
の Controls タブのみにフォームが追加されます。
<HstText>
は Histoire にあらかじめ用意されているコンポーネントで、 Controls タグ向けの UI のフォームを利用できます。
<script lang="ts" setup>
import { ref } from "vue";
import AppButton from "./AppButton.vue";
const label = ref("Hello World");
</script>
<template>
<Story title="buttons">
<AppButton>{{ label }}</AppButton>
<template #controls>
<HstText v-model="label" title="label" />
</template>
</Story>
</template>
Controls タブに label
フォームが追加され、ボタン内部のテキストを操作できるようになりました。
イベント
Histoire の hstEvent
関数を使用することでコンポーネントが emit するイベントの一覧を Events タブに表示できます。
<script lang="ts" setup>
import AppButton from "./AppButton.vue";
import { hstEvent } from "histoire/client";
</script>
<template>
<Story title="buttons">
<AppButton @click="hstEvent('Click', $event)">I am a button</AppButton>
</Story>
</template>
ドキュメント
ファイルのルートレベルに <doc>
タグを追加することでコンポーネントのドキュメントをマークダウン記法で記述できます。ドキュメントは Docs タグに表示されます。
<script lang="ts" setup>
import AppButton from "./AppButton.vue";
</script>
<template>
<Story title="buttons">
<AppButton>I am a button</AppButton>
</Story>
</template>
<docs lang="md">
# Buttons
This is a button.
## Props
| Name | Type | Default | Description |
| -------- | ---------------------------- | --------- | ------------------------------ |
| color | "primary" | "secondary" | "primary" | The color of the button |
| disabled | boolean | false | Whether the button is disabled |
</docs>
ソースコード
デフォルトでは Story からソースコードを自動で生成して表示しますが、<Story>
または <Variant>
タグに source
Props を渡すか、source
スロットを使用することで表示されるソースコードを上書きすることでできます。
<script lang="ts" setup>
import AppButton from "./AppButton.vue";
</script>
<template>
<Story title="buttons">
<AppButton>I am a button</AppButton>
<template #source>custome source code</template>
</Story>
</template>
プラグイン
Storybook の Addons のようにプラグインにより機能を拡張できます。例としてスクリーンショットを撮影してビジュアルリグレッションテストを実施する https://histoire.dev/guide/plugins.html を使ってみましょう。
まずはパッケージをインストールします。
$ npm i -D @histoire/plugin-screenshot
Histoire の設定ファイル(この記事内では histoire.config.ts
)においてプラグインを追加します。
// histoire.config.ts
import { defineConfig } from 'histoire'
+ import { HstScreenshot } from '@histoire/plugin-screenshot'
export default defineConfig({
setupFile: '/src/histoire.setup.ts',
+ plugins: [
+ HstScreenshot({})
+ ]
})
ビルドコマンドを実行した際にスクリーンショットが撮影されるようになります。
$ npm run story:build
感想
Vite 製のツールはやっぱり早く動作するので良いですね。エコシステムはまだまだ充実してるとはいえないですが、一通りの機能は揃っているので試してみるには良さそうですね。
個人的には Vue の SFC ファイル形式で Story を記述できるところが気に入っています。Storybook 用の新しい書き方を学ぶコストが削減できていい感じです。
サンプルコードは以下のレポジトリにあります。