【超簡単フロントエンド開発】Storybook6.1 + Nuxtjs (+Vuetify)

## はじめに
Web界隈は一つ案件が終わって戻ってくると、びっくりするくらい便利なものが開発されていて竜宮城から帰ってきておじいさんになった気分になりますね。

先週はNuxtjsの記事を書きましたが、Storybookも使った方がよさそうなので今週は[Storybook](https://storybook.js.org/)の記事を書きます。
Storybook は 2020年8月に6系になって**大変シンプルになりましたが** 大幅に変更になって古い記事が罠となってしまっているので**Google検索する時は「１ヶ月以内」**で期間指定するのがよさそうです。


## Storybook とは

> Storybook is an open source tool for developing UI components in isolation for React, Vue, Angular, and more. It makes building stunning UIs organized and efficient.

一言でいうと UI Component をコンポーネント単位で開発するのを簡単にするツールです。
画面を見た方が理解が早いと思いますので、仮のアプリのヘッダコンポーネントの「ログイン状態」と「非ログイン状態」をStorybookで開発中の画面を載せました。

![signin.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/136165/a3d35d29-fa05-5d48-8daa-163388d5d0b9.png)
![signout.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/136165/650ce69d-2f3f-5b69-d9e2-bcaacd381e15.png)

開発中のコンポーネントの並び[^1]に {Component名}.stories.js を配置して簡単な設定を書くだけでスクリーンショット通りのプロパティエディタ付きのプレビューが表示されます。

* 簡単な設定

``` AppHeader.stories.js
import AppHeader from './AppHeader';

// 左メニューに表示する項目
export default {
  title: 'Melecules/header/AppHeader',
  argTypes: {
    // property editor をスライダーなどにしたい場合はここに設定を記述
  },
};

// プレビュー用テンプレート：コピペで利用
const Template = (args, { argTypes }) => ({
  props: Object.keys(argTypes),
  components: { AppHeader },
  template: '<AppHeader v-bind="$props" />',
});

const args = {
  title: 'test'
}

// Signin 状態。user object を指定
export const Signin = Template.bind({});
Signin.args = { ...args, ...{
  user: {
    id: 'aaaa'
  }
}}

// Signout 状態。user object = null を指定
export const Signout = Template.bind({});
Signout.args = { ...args, ...{
  user: null
}}

```

ログイン/非ログイン状態のUIの開発は面倒な制御が入って面倒だったと思いますが、コンポーネント単位で簡単に動作確認しながら開発できれば随分簡単になりますよね？


## 導入

まずは、Vue, React, ... のプロジェクトを作って下さい。本記事では Nuxtjs (+ Vuetify) のプロジェクトを対象に解説します。

Nuxtjs のプロジェクトを作ってない方はこちらも参照ください。
[【2020年版】Vue + Vuetify + typescript でWeb開発するならNuxt.jsを使おう](https://qiita.com/kaku3/items/c4a9f72e73247c776c93)

プロジェクトが用意できたら、以下コマンドを実行してください。

### Storybook インストール

```
cd {projectFolder}
npx sb init
```

プロジェクトフォルダ内に以下フォルダができます。

```
{projectFolder}/
  .storybook/ # 設定が入っているフォルダ
    main.js # addon 設定などする
    preview.js # preview 設定などする。Vuetify を利用する場合はまるっと入れ替える
  storybook/ # サンプルコンポーネントとストーリーが入っている。捨てても良いがコピペ元として重宝する
```

### Storybook 実行

```
yarn storybook
```

インストールが済んだ状態で実行すると storybook/ フォルダのサンプルが表示される状態で、Storybookが実行され、http://localhost:6006 がブラウザで自動で開かれます。


### main.js の stories フォルダを変更

storybook から components に変更することで、開発中のコンポーネントのフォルダを Storybook の参照対象とします。

``` main.js
module.exports = {
  "stories": [
    // storybook -> components に変更
    "../components/**/*.stories.mdx",
    "../components/**/*.stories.@(js|jsx|ts|tsx)"
  ],
  "addons": [
    "@storybook/addon-links",
    "@storybook/addon-essentials"
  ]
}

```

古い記事を見ると色々な addon を足すと便利と書いてありますが addon-essentials に色々入ってる様ですので必要になったタイミングで色々足すとよいと思います。

### preview.js を差し替え(Vuetify利用時のみ)

質素なファイルが入っているので、Vuetify を利用するように修正します。
アイコンがレンダリングされなかったので、@mdi/font のインストールもしています。

```
yarn add @mdi/font -D
```

``` preview.js
import Vue from 'vue'
import Vuetify from 'vuetify'
import 'vuetify/dist/vuetify.css'
import colors from 'vuetify/es5/util/colors'
import '@mdi/font/css/materialdesignicons.css'

const vuetifyOptions = {}

Vue.use(Vuetify, {
  theme: {
    dark: false,
    themes: {
      dark: {
        primary: colors.blue.darken2,
        accent: colors.grey.darken3,
        secondary: colors.amber.darken3,
        info: colors.teal.lighten1,
        warning: colors.amber.base,
        error: colors.deepOrange.accent4,
        success: colors.green.accent3
      }
    }
  },
  icons: {
    iconfont: 'mdi'
  }
})

export const parameters = {
  backgrounds: {
    value: [
      { name: 'Sample BG 1', value: '#CCCCCC', default: true },
      { name: 'Sample BG 2', value: '#000000' },
    ],
  },
}

export const decorators = [
  () => {
    return (
    { vuetify: new Vuetify(vuetifyOptions), template: '<v-app><story/></v-app>' }
  )}
]
```


### components フォルダに *.stories.js を配置

サンプルを参考に stories.js ファイルを作成して配置してください。
設定は以上となります。**Webを検索すると色々出てきますが、以上です。**
Webpack の config をいじったり、.storybook/ に存在しないファイルを追加する必要はないです。


## おわりに

コンポーネント単位開発は人類の夢（大げさ）でしたが、分割の粒度で宗教戦争が起こったり、かえって管理が煩雑になったりデメリットが目立つ事が多かった様に思います。
ただ、このレベルでサポートされるようになってくれば、コンポーネント開発のメリットを享受できたり、デザイナーとの分業も視野に入れられたりするのかなと感じました。
導入も本当に簡単ですので是非試してみて下さい。

(おまけ)
Nuxtjsではコンポーネントの依存関係を自動で解決してくれるため、comonents の注入を書かなくてよかったのですが Storybook は解決してくれなかったため対応が必要なのが残念でしたが、そのうち対応されることを祈ってます。

``` parent.vue
<script lang="ts">
// Storybook のためだけに追記
import C from 'components/C.vue'

export default Vue.extend({
  // Storybook のためだけに追記
  components: {
    C
  }
  // ...
}
</script>
```

[^1]: 実際はファイル名もファイルの場所も制限はない。メンテナンス性を鑑みるとパスを書かないでよい場所に配置するのがよさそう。