Nuxt.jsとElementUIにStoryBookを導入してみた

[iRidge Advent Calendar 2018](https://qiita.com/advent-calendar/2018/iridge) の9日目の記事です。
Nuxt.jsとElementUIを使ったSPA開発にStroyBookを導入した話を書きます。

## はじめに
チームでコンポーネント開発する場合、レビュー時にデザインを確認しにくい課題がありました。
コンポーネント自体はAtomic Designに則っているため、Atomsなどの実装はページに書き出されていないです。
そのため、レビューして確認するときは以下のフローを取っていました。

1. レビュー対象のブランチをpullする
2. コンポーネントをページに書き出す
3. 開発環境を起動する
4. デザインを確認する

コンポーネントごとにレビューできるツールを探していたら、StoryBookに出会ったため導入方法をまとめます。


## セットアップ
StoryBookはCLIで簡単にセットアップできます。  

#### StoryBook CLIのインストール
```bash
$ yarn add -g @storybook/cli
```

#### Nuxtプロジェクトに追加
プロジェクトルートで `getstorybook` コマンドを実行します。

```bash
$ cd nuxtproject/
$ getstorybook

   sb init 

 sb init - the simplest way to add a storybook to your project. 

 • Detecting project type. ✓
 • Adding storybook support to your "Vue" app. ✓
 • Preparing to install dependencies. ✓

yarn install v1.10.1
[1/4] 🔍  Resolving packages...
[2/4] 🚚  Fetching packages...
[3/4] 🔗  Linking dependencies...
[4/4] 📃  Building fresh packages...
success Saved lockfile.
✨  Done in 97.05s.

 • Installing dependencies. ✓
```

完了するとプロジェクト配下に `.storybook` および `stories/` が作成されます。
`.storybook/config.js` はプラグインの追加やストーリーファイルの対象を設定できます。
ストーリーは`stories/` 以下に記述していきます。

#### webpackの設定
Vue(Nuxt)を利用するにはwebpackの設定を追加します。
以下ではSFCの `style` やElementUIのフォント系をバンドルするための設定を追加してます。

```javascript
const path = require("path");
const rootPath = path.resolve(__dirname, '../');

module.exports = {
  module: {
    rules: [
      {
        test: /\.css?$/,
        loaders: ['style-loader', 'css-loader']
      },
      {
        test: /\.scss$/,
        loaders: ['style-loader', 'css-loader', 'sass-loader']
      },
      {
        test: /\.(png|jpe?g|gif|svg)$/,
        loader: 'url-loader',
        query: {
          limit: 1000, // 1kB
          name: 'img/[name].[hash:7].[ext]'
        }
      },
      {
        test: /\.(otf|eot|svg|ttf|woff|woff2)(\?.)?$/,
        loader: 'url-loader'
      }
    ]
  },
  resolve: {
    extensions: ['.vue'],
    alias: {
      '@': rootPath,
      '~': rootPath,
    }
  }
};
```

#### StoryBookを起動する
ここまできたら`package.json` に登録されている `storybook` コマンドを実行してみましょう。

```
$ yarn storybook
```

Exampleのコンポーネントがストーリーに登録されているのを確認できると思います。

## ElementUIを組み込む

StoryBookにElementUIを組み込むにはNuxtのプラグインと同様の設定をする必要があります。
`.storybook/config.js` にElementUIのJS部分とスタイルのインポートを行います。

```javascript
import { configure } from '@storybook/vue';

+ import '!style-loader!css-loader!element-ui/lib/theme-chalk/index.css';   // ElementUIのスタイルのロード
+ import ElementUI from '../plugins/element-ui';  // ElementUIのコア部分のロード
+ ElementUI();

// automatically import all files ending in *.stories.js
const req = require.context('../stories', true, /.stories.js$/);
function loadStories() {
  req.keys().forEach(filename => req(filename));
}

configure(loadStories, module);
```


##  ストーリーを書く
ElementUIの設定が完了したら、実際にストーリーを書いていきましょう。
ストーリーを書く前にElementUIのボタンコンポーネントを作成します。

```javascript
<template>
  <div>
    <el-button :type="type">
      {{ title }}
    </el-button>
  </div>
</template>

<script>
export default {
  name: 'ButtonEl',
  props: {
    type: {
      type: String,
      required: true,
      default: ''
    },
    title: {
      type: String,
      required: true,
      default: ''
    }
  }
}
</script>
```

ストーリーは以下になります。

```javascript
import { storiesOf } from '@storybook/vue'

import ButtonEl from '@/components/atoms/ButtonEl.vue'

storiesOf('ButtonEl', module)
  .add('default button', () => ({
    components: { ButtonEl },
    template: '<button-el title="タイトル" type="default" />'
  }))
  .add('custom button', () => ({
    components: { ButtonEl },
    template: '<button-el title="新規登録" type="primary"/>'
  }))
```

StoryBookを起動するとストーリーが追加されているのがわかります。

## StoryBookをデプロイする
レビュー時にローカル環境でStoryBookを起動するだけでコンポーネントのデザインが確認できるところまできました。
しかし、ローカル環境で起動するのは手間です。
そこでGitLab CIを使ってリポジトリにPushするとStoryBookをデプロイするフローを作ってみました。

完成した `.gitlab-ci.yml` は以下になります。

```yaml
stages:
  - storybook-build
  - storybook-deploy

build_storybook:
  image: node:10.13.0
  stage: storybook-build
  script:
    - yarn install
    - yarn build-storybook
  artifacts:
    paths:
      - storybook-static

deploy_storybook:
  image: python:3.7
  stage: storybook-deploy
  before_script:
    - pip install awscli
  script:
    - aws s3 sync ./storybook-static/ s3://{{バケット名}}/$CI_COMMIT_REF_NAME/
```

StoryBookのビルドコマンドは`package.json`に登録されています。
ビルドが完了すると `storybook-static` ディレクトリ以下にファイルが作成されます。

ホスティング先にはS3バケットを使用しました。
リポジトリにPushしたら新しいディレクトリを作成してデプロイするようにしています。
ディレクトリ名はブランチ名を流用していて、GitLab CIの環境変数 `CI_COMMIT_REF_NAME` を使っています。

## まとめ
NuxtのプロジェクトにStoryBookを導入してみました。
導入は思ったよりも簡単ですが、`getstorybook` 時にwebpackの設定を追加してくれるとありがたいです。
今後は`reg-suit` と組み合わせてVisual Regression Testing を実施してみたいです。