最近、システム開発のフロント側を担当することが増えてきて、毎回コンポーネント設計に悩まされます。
また、チームで開発するときに、どのように運用していくのがいいのか探していると、Storybookの導入をしている記事を見つけて、今回試してみることにしました。
まだまだ手探り状態ですが、自分自身でさえ時間がたつとどうやって設計したっけ?となるので、自分のためにもよいツールだと思いました!
導入の備忘録として、残しているので、参考になればうれしいです。
開発環境
それぞれのバージョンはこちらです。
- Vue.js 3.5
- Storybook 8.4
下記でgitを上げているので、よければ参考にしてください。
Storybookをインストール
今回、Vueを使用した環境で、Storybookを導入しました。vueのプロジェクト作成は割愛します。
下記が、vueのプロジェクト作成から、Storybookの導入まで記述しているので、今回参考にさせていただきました!
公式サイトでは、下記のコマンドで導入とあります。サンプル用のコードも導入されるので、ありがたいです。
proceedしますか?と聞かれるので、yで進める。関連ファイルがインストールされ、同時に立ち上げもしてくれます。
npx storybook@latest init
![](https://storybook.js.org/opengraph-image.jpg?841db310ba5a3a1e)
いろいろと案内が進行したあと、このような画面になります。最初は使い勝手がよくわからなかったので、サンプルのコードを見ながらいろいろと調整していきました。
![](https://komamen.net/wp-content/uploads/2024/11/storybook-1024x483.png)
ちなみにstorybook立ち上げたいときのコマンドは、こちら。
npm run storybook
Vueのプロジェクトファイル構成、コンポーネントの構成
今回、管理者、ユーザー、編集者というような、3パターンのroleがあり、それぞれ構成は似ているが、色や一部ナビなどの構成が異なるというような構成のシステムでした。
そのため、コンポーネントの構成をどうしようか悩み、いろいろな方の記事を参考にして、独自の構成にすることにしました。
そのときに参考にした記事はこちら
![](https://cdn.image.st-hatena.com/image/scale/33c375d2223b65c80381a963617ab2ef26a4b4e6/backend=imagemagick;version=1;width=1300/https%3A%2F%2Fcdn-ak.f.st-hatena.com%2Fimages%2Ffotolife%2Fm%2Fmicroad-developers%2F20240301%2F20240301180008.png)
components
ーicons
ーparts(ボタンなど最小のコンポーネント)
ーtemplates(partsを組み合わせて作りたいもの)
ーviews(viewごとで固有のコンポーネント)
さらにviewの構成は下記で、vue-routerを使用して、viewのディレクトリごとに表示させるようにしました。
Viteのimport.meta.glob関数というのを使用して、vueのパスが動的に出力されるようにしました。
下記のように記述しました。
import { createRouter, createWebHistory } from 'vue-router'
const adminRoutes = Object.entries(import.meta.glob('../views/admin/*.vue')).map(
([path, component]) => {
const name = path.match(/\/admin\/(.+)\.vue$/)[1]
return {
path: `/admin/${name.toLowerCase()}`,
name: `Admin${name}`,
component,
}
},
)
const userRoutes = Object.entries(import.meta.glob('../views/user/*.vue')).map(
([path, component]) => {
const name = path.match(/\/user\/(.+)\.vue$/)[1]
return {
path: `/user/${name.toLowerCase()}`,
name: `user${name}`,
component,
}
},
)
const editorRoutes = Object.entries(import.meta.glob('../views/editor/*.vue')).map(
([path, component]) => {
const name = path.match(/\/editor\/(.+)\.vue$/)[1]
return {
path: `/editor/${name.toLowerCase()}`,
name: `editor${name}`,
component,
}
},
)
const router = createRouter({
history: createWebHistory(import.meta.env.BASE_URL),
routes: [...adminRoutes, ...userRoutes, ...editorRoutes],
})
export default router
こうすると、トップは何も表示されなくなります。。
App.vueは、下記のように<RouterView />でviews以下ファイルで作成していくようにします。
<script setup>
import { RouterView } from 'vue-router'
</script>
<template>
<div>
<RouterView />
</div>
</template>
スタイルを適応する
今回は、tailwindではなく、cssを記述して運用する方式を検討していました。
なので、共通のスタイルはcssを、コンポーネント単位で、スタイルを記述していくという方法にしました。
![](https://qiita-user-contents.imgix.net/https%3A%2F%2Fqiita-user-contents.imgix.net%2Fhttps%253A%252F%252Fcdn.qiita.com%252Fassets%252Fpublic%252Farticle-ogp-background-afbab5eb44e0b055cce1258705637a91.png%3Fixlib%3Drb-4.0.0%26w%3D1200%26blend64%3DaHR0cHM6Ly9xaWl0YS11c2VyLXByb2ZpbGUtaW1hZ2VzLmltZ2l4Lm5ldC9odHRwcyUzQSUyRiUyRnFpaXRhLWltYWdlLXN0b3JlLnMzLmFwLW5vcnRoZWFzdC0xLmFtYXpvbmF3cy5jb20lMkYwJTJGNDUxODclMkZwcm9maWxlLWltYWdlcyUyRjE1NjY1NDg4Nzg_aXhsaWI9cmItNC4wLjAmYXI9MSUzQTEmZml0PWNyb3AmbWFzaz1lbGxpcHNlJmZtPXBuZzMyJnM9N2Q2NDUyZjZjYTI0YWI0YWY3MWRkN2NkOGYzNGQ2Y2E%26blend-x%3D120%26blend-y%3D462%26blend-w%3D90%26blend-h%3D90%26blend-mode%3Dnormal%26mark64%3DaHR0cHM6Ly9xaWl0YS1vcmdhbml6YXRpb24taW1hZ2VzLmltZ2l4Lm5ldC9odHRwcyUzQSUyRiUyRnMzLWFwLW5vcnRoZWFzdC0xLmFtYXpvbmF3cy5jb20lMkZxaWl0YS1vcmdhbml6YXRpb24taW1hZ2UlMkYwYmIwMTM0YTk1MGVhODU3N2EyYmI0MzJhNTA2ZWRhOGE5OTRhNDVjJTJGb3JpZ2luYWwuanBnJTNGMTY3NTE0MTExMD9peGxpYj1yYi00LjAuMCZ3PTQ0Jmg9NDQmZml0PWNyb3AmbWFzaz1jb3JuZXJzJmNvcm5lci1yYWRpdXM9OCZib3JkZXI9MiUyQ0ZGRkZGRiZmbT1wbmczMiZzPWU0ZTE3YmJjMmUyZDdmNzdkZTVmNjljMDZkY2JhYjI5%26mark-x%3D186%26mark-y%3D515%26mark-w%3D40%26mark-h%3D40%26s%3D67367902170b216b08b351ac96a84778?ixlib=rb-4.0.0&w=1200&fm=jpg&mark64=aHR0cHM6Ly9xaWl0YS11c2VyLWNvbnRlbnRzLmltZ2l4Lm5ldC9-dGV4dD9peGxpYj1yYi00LjAuMCZ3PTk2MCZoPTMyNCZ0eHQ9VnVlLmpzJTIwJUUyJTlDJTk1JTIwQ1NTJTIwTW9kdWxlcyUyMCVFMyU4MSVBRSVFMyU4MiVCMyVFMyU4MyVCMyVFMyU4MyU5RCVFMyU4MyVCQyVFMyU4MyU4RCVFMyU4MyVCMyVFMyU4MyU4OCVFMyU4MSVBRSVFMyU4MSVBNCVFMyU4MSU4RiVFMyU4MiU4QSVFNiU5NiVCOSZ0eHQtYWxpZ249bGVmdCUyQ3RvcCZ0eHQtY29sb3I9JTIzMUUyMTIxJnR4dC1mb250PUhpcmFnaW5vJTIwU2FucyUyMFc2JnR4dC1zaXplPTU2JnR4dC1wYWQ9MCZzPWY2ZDhkMDRhNWU3NzYwN2ZhZGE3MTRkOWM4ZjFmODNm&mark-x=120&mark-y=112&blend64=aHR0cHM6Ly9xaWl0YS11c2VyLWNvbnRlbnRzLmltZ2l4Lm5ldC9-dGV4dD9peGxpYj1yYi00LjAuMCZ3PTgzOCZoPTU4JnR4dD0lNDBmdW1pX25hZ2Fva2EmdHh0LWNvbG9yPSUyMzFFMjEyMSZ0eHQtZm9udD1IaXJhZ2lubyUyMFNhbnMlMjBXNiZ0eHQtc2l6ZT0zNiZ0eHQtcGFkPTAmcz0zODU4ZjczZTg1YzM4ZTkxYmQ1ZGI4ZTE2N2MwNmNkMA&blend-x=242&blend-y=454&blend-w=838&blend-h=46&blend-fit=crop&blend-crop=left%2Cbottom&blend-mode=normal&txt64=5qCq5byP5Lya56S-44OT44K244K544Kv&txt-x=242&txt-y=539&txt-width=838&txt-clip=end%2Cellipsis&txt-color=%231E2121&txt-font=Hiragino%20Sans%20W6&txt-size=28&s=32fcfac0bcbcd0f5cf6b851b132b36f2)
また、storybookでもcssの適応が必要なのですが、importする場所は、.storybookディレクトリ以下のpreview.jsに記述が必要です。
/** @type { import('@storybook/vue3').Preview } */
import '../src/assets/main.css'
const preview = {
parameters: {
controls: {
matchers: {
color: /(background|color)$/i,
date: /Date$/i,
},
},
},
}
export default preview
Story作成してみる
storyディレクトリ以下に、.stories.jsの拡張子のファイルを作成します。
サンプルのコードは、vueのファイルもstoriesディレクトリ以下に入れているんですが、vueのcomponentsディレクトリ以下に配置して、imoprtをして、使用しています。
こんな感じです。
// import { fn } from '@storybook/test'
import Button from '@/components/parts/Button.vue'
export default {
title: 'Parts/Button',
component: Button,
// args: { onClick: fn() },
tags: ['autodocs'],
}
export const Default = {
args: {
border: false,
label: 'ボタン',
},
}
export const Border = {
args: {
border: true,
label: 'ボタン',
},
}
下記のように、表示されるようになります。
まず、Vueのファイルをimportして、componentに指定をします。
titleにいれた構造で、表示されます。
また、tags:[‘autodocs’]と設定することで、Docsというページが生成されます。
import Button from '@/components/parts/Button.vue'
export default {
title: 'Parts/Button',
component: Button,
// args: { onClick: fn() },
tags: ['autodocs'],
}
下記が、Buttonの記述なんですが、propsなどを検知して、Docsに出力してくれます。便利ですね!
<script setup>
const props = defineProps({
label: {
type: String,
required: true,
},
border: {
type: Boolean,
default: false,
},
})
</script>
<template>
<button type="button" :class="['c-button', props.border ? `is-border` : '']" @click="onClick">
{{ props.label }}
</button>
</template>
<style lang="scss">
.c-button {
color: #fff;
background: #4b4b4b;
border-radius: 6px;
padding: 14px;
font-size: 18px;
font-weight: bold;
min-width: 218px;
&.is-border {
color: #000;
border: 1px solid #000;
background: #fff;
}
}
</style>
また、下記で設定することで、どんなpropsを渡したら、どんな表示になるか、表示させておくことができます。詳しくは実際に触ってみたほうがわかりやすいと思います。
export const Default = {
args: {
border: false,
label: 'ボタン',
},
}
export const Border = {
args: {
border: true,
label: 'ボタン',
},
}
gitにファイルをアップしているので、よければ参考にしてください。
まとめ
システム構築をしていると、propsが何でわたってきているのか、このコンポーネントはどんな構成になっているのか、構築してしばらく立つと忘れたりとかするので、Storybookでまとめておくと、とても便利だと思いました!
またチームで開発の際も、コンポーネントを再利用をして構築できると思うので、次の案件で導入したいと思いました。