概要
Atomic Designを用いたフロントエンドの開発を行う中で、
「このコンポーネントって既にあるんだっけ?」と思い、プロジェクトの中を探し回ることがありました。
また、それっぽいファイルを見つけても、このコンポーネントはブラウザ上でどのように表示されるのかはぱっと見わかりません。
現状では、Vue.js devtoolsなどを使ってコンポーネント名を特定して、
実際の.vueまたは.tsxファイル名を照らし合わせる必要があります。
そんな中、Storybookがいいという噂を見かけたので、早速試してみました。
作業環境
- Vue.js・・・3.2.45
- storybook/vue3・・・6.5.15
etc..
Storybookとは
UIコンポーネントとページを分離して構築するためのフロントエンドワークショップです。
今までは、コンポーネントの挙動を確認するには、アプリ全体を起動、ページを移動、UIを正しい状態にする必要がありました。これには時間がかかるため、開発速度の低下につながります。
しかし、Storybookを使用することでこの問題を解決できます。具体的なメリットは以下に記載します。
メリット
- コンポーネント単位でUI上での挙動を確認できる
- コンポーネント(ここではatomなど小さな単位)とページを分離してユースケースを検証できる
- UI上で到達しにくいエッジケースを検証できる
- UIテストが容易である
- コンポーネントやその状態をドキュメント化できる
- UIの動作を共有できる
特に注目している点
私は特に、最後の2つのメリットに注目しています。
- コンポーネントやその状態をドキュメント化できる
- UIの動作を共有できる
理由は、これによってデザイナーとの認識齟齬の発生を防ぐことができたり、同一のコンポーネントを再び実装してしまうことを防ぐことができるからです。
私のチームではFigmaでデザインを行なっており、
Figmaでもコンポーネントを作成して、それらを組み合わせてページをデザインします。
その中で、デザインを確認しながらフロントで実装したコンポーネントの状態がデザイナーの意図に沿っているかどうかなど、相談ごとがどうしても増えてしまいます。
相手が忙しい場合も多いため、ここのコミュニケーションコストをできる限り下げる必要があります。
そこで、Storybookを利用することでコンポーネントやその状態をドキュメント化し共有することで、
アプリを操作して該当の画面に遷移する等の操作なく、コンポーネントを確認してもらうことが可能になります。
これによって、相談により発生するコミュニケーションコストが大幅に低下することが期待できます。
静的サイトとしてデプロイが可能なようなので、S3等にアップロードしておけば
誰でもいつでも確認できるドキュメントとして利用できそうです。
実際に使ってみる
導入
ここを参考にします。
Introduction to Storybook for Vue
ちなみに、以下のようにReactやSvelteなど各ライブラリに応じたチュートリアルが紹介されています。
親切すぎる・・・
まずはViteでプロジェクトを生成します。
Storybookは空のプロジェクトでは動かないため、既にフレームワークがセットアップされている必要があります。
$ yarn create vite
✔ Project name: … sample
✔ Select a framework: › Vue
✔ Select a variant: › TypeScript
$ cd sample
$ yarn
$ yarn dev
$ vite
VITE v4.0.4 ready in 406 ms
➜ Local: http://127.0.0.1:5173/
➜ Network: use --host to expose
➜ press h to show help
✨ Done in 100.32s.
次に、用意したプロジェクトのルートディレクトリで以下のコマンドを実行し、Storybookをインストールします。
$ npx storybook init
中略
yarn install v1.22.19
[1/4] 🔍 Resolving packages...
success Already up-to-date.
✨ Done in 0.54s.
. ✓
🔎 checking possible migrations..
✅ migration check successfully ran
To run your Storybook, type:
yarn storybook
コマンド実行により以下のような変更が加えられます。
- Storybookの実行とビルドに必要なスクリプトをセットアップする。
- デフォルトのStorybookの設定を追加する。
- 開始するためのデフォルトのストーリーを追加する。
プロジェクトにはstoriesディレクトリが追加されました。
package.jsonのscriptにはstorybook
とbuild-storybook
が追加されています。
{
"name": "sample",
"private": true,
"version": "0.0.0",
"type": "module",
"scripts": {
"dev": "vite",
"build": "vue-tsc && vite build",
"preview": "vite preview",
"storybook": "start-storybook -p 6006",
"build-storybook": "build-storybook"
},
"dependencies": {
"vue": "^3.2.45"
},
"devDependencies": {
"@babel/core": "^7.20.12",
"@storybook/addon-actions": "^6.5.15",
"@storybook/addon-essentials": "^6.5.15",
"@storybook/addon-interactions": "^6.5.15",
"@storybook/addon-links": "^6.5.15",
"@storybook/builder-vite": "^0.3.0",
"@storybook/testing-library": "^0.0.13",
"@storybook/vue3": "^6.5.15",
"@vitejs/plugin-vue": "^4.0.0",
"babel-loader": "^8.3.0",
"typescript": "^4.9.3",
"vite": "^4.0.0",
"vue-loader": "^16.8.3",
"vue-tsc": "^1.0.11"
}
}
次に、以下のコマンドを実行してStorybookが正常に動作するかを確認します。
$ yarn storybook
yarn run v1.22.19
$ start-storybook -p 6006
info @storybook/vue3 v6.5.15
info
info => Loading presets
info => Ignoring cached manager due to change in manager config
ℹ 「wdm」: wait until bundle finished:
ℹ 「wdm」: Hash: 70239335d1b0f74c3dc2
Version: webpack 4.46.0
中略
Child HtmlWebpackCompiler:
Asset Size Chunks Chunk Names
__child-HtmlWebpackPlugin_0 6.32 KiB HtmlWebpackPlugin_0 HtmlWebpackPlugin_0
Entrypoint HtmlWebpackPlugin_0 = __child-HtmlWebpackPlugin_0
[./node_modules/html-webpack-plugin/lib/loader.js!./node_modules/@storybook/core-common/templates/index.ejs] 2.04 KiB {HtmlWebpackPlugin_0} [built]
ℹ 「wdm」: Compiled successfully.
╭──────────────────────────────────────────────────────╮
│ │
│ Storybook 6.5.15 for Vue3 started │
│ 9.08 s for manager and 1.07 s for preview │
│ │
│ Local: http://localhost:6006/ │
│ On your network: http://xxx.xxx.xxx.xxx:6006/ │
│ │
╰──────────────────────────────────────────────────────╯
このように表示されたら成功しています。
ブラウザでhttp://localhost:6006/
を開くと、以下の画面が表示されます。
Storyとは
ストーリーは、UIコンポーネントのレンダリング状態をキャプチャするために使用する、そのコンポーネントをどのようにレンダリングするかを記述した関数のことです。
コンポーネントごとに複数のストーリーを記述し、コンポーネントがサポートできるすべての状態を定義しておきます。
Storybookのインストール時に、CLIによってボタン、ヘッダー、ページのサンプルコンポーネントが作成されています。
各サンプルコンポーネントには、サポートする状態を示す一連のストーリーがあります。
UIでストーリーを参照し、.stories.js
または.stories.ts
で終わるファイルでその背後にあるコードを見ることができます。ストーリーは、コンポーネントのサンプルを記述するためのES6モジュールベースの標準であるComponent Story Format (CSF)で記述されています。
Storyを書いてみる
自動生成されたボタンコンポーネントのストーリーを書いてみます。
既にButton.stories.js
が生成されているので、mdx形式を試します。
ちなみにmdxとは、markdownの中に、Reactで用いられるjsxを埋め込むことができるフォーマットです。
<template>
<button type="button" :class="classes" @click="onClick" :style="style">{{ label }}</button>
</template>
<script>
import './button.css';
import { reactive, computed } from 'vue';
export default {
name: 'my-button',
props: {
label: {
type: String,
required: true,
},
primary: {
type: Boolean,
default: false,
},
size: {
type: String,
validator: function (value) {
return ['small', 'medium', 'large'].indexOf(value) !== -1;
},
},
backgroundColor: {
type: String,
},
},
emits: ['click'],
setup(props, { emit }) {
props = reactive(props);
return {
classes: computed(() => ({
'storybook-button': true,
'storybook-button--primary': props.primary,
'storybook-button--secondary': !props.primary,
[`storybook-button--${props.size || 'medium'}`]: true,
})),
style: computed(() => ({
backgroundColor: props.backgroundColor,
})),
onClick() {
emit('click');
}
}
},
};
</script>
以下のように記述します。
import { Meta, Story } from '@storybook/addon-docs';
import Button from './Button.vue';
<Meta title="Button" component={Button} />
# Button
<Story name="Primary">
{() => {
return {
components: { Button },
template: `<Button primary label="Button" />`,
};
}}
</Story>
上記のStoryを追加することで、ブラウザでドキュメント化され確認することができます。
この時点ではボタンを押下しても何も起こりません。
次にargsを使用して、ボタンに対して引数を渡します。
ここで、
import { action } from "@storybook/addon-actions";
したうえでargsにonClick: action("clicked")
のようにイベントに対するアクションを設定しています。
import { Meta, Story } from '@storybook/addon-docs';
import { action } from "@storybook/addon-actions";
import Button from './Button.vue';
<Meta title="Button" component={Button} />
# Button
export const Template = (args) => ({
components: { Button },
setup() {
return { args };
},
template: '<Button v-bind="args" />',
});
<Story
name="Primary"
args={{
primary: true,
label: 'Button',
onClick: action("clicked")
}}>
{Template.bind({})}
</Story>
argsを設定することで、以下のようにControlsタブが追加され、ボタンのがどのような状態を持つのかを画面上で確認できるようになります。
また、クリック時にActionsタブで詳細を確認することができます。
まとめ
Storybook、非常に良さそうですね。
特に、いちいちアプリを操作せずにコンポーネント単位で確認できるので、エンジニアもデザイナーも幸せに慣れそうな気がします。
他にも多くの機能があるので今後も触っていきたいです。
それでは!