Storybook for HTMLでレスポンシブUIを管理する

StorybookはすぐれたUIコンポーネント管理ツールですが、ReactやVueだとできるものがplain HTML用だと痒いところに手が届かない印象でした。でもやらないといけないということでまとめてみた。

## 要件

- Responsive UIが確認できる
- ソースコードをコピペで利用できるようにしたい

つまりaddonsをうまく利用して実現したい。（https://storybook.js.org/addons/）
今回は `addons-Viewport` と mdxファイルが利用できる `addons-Docs` を利用しました。

ということで、目次
1. 前提条件
2. Storybook for Htmlのインストール
3. SCSSを適用する設定
4. Addonのインストール

## 前提条件

今回は既存プロジェクトを以下のように管理しており、そこにStorybookを同居させます。
既存プロジェクトのディレクトリ構造は以下の通りとします。
ECTテンプレート (http://ectjs.com/) を利用してHTML側もコンポーネント管理し、Storybook側では `src/ect/_components` 以下のコンポーネントを管理することになります。
また、スタイルについては、 `src/scss/main.scss` をStorybook側でも読み込むようにします。
_* 実際は webpackによるビルド環境ですが今回は省略しています。_ 

```
src
├── ect
│   ├── _components <-- ここのファイルをStorybookで管理
│   │  ├── _a.ect
│   │  ├── _b.ect
│   │  └── _c.ect
│   ├── _layout.ect
│   └── index.ect
├── js
│   └── main.js
└── scss
    ├── _components
    │  ├── _a.scss
    │  ├── _b.scss
    │  └── _c.scss
    └── main.scss <-- 上記a, b, cコンポーネとスタイルをimportしている
```

## Storybook for HTML のインストール

https://storybook.js.org/docs/guides/guide-html/

以下を実行するだけで初期設定は完了です。

```
npx -p @storybook/cli sb init --type html
```

これでStorybookのベーシックUIが確認できます。

```
yarn storybook
```

<img width="1791" alt="Screen Shot 2020-03-30 at 9.04.45.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/7804/6966d570-eb36-8a3a-559c-a42c94e38036.png">

この時点でのディレクトリ構造

```bash
.
├── .storybook
│   └── main.js
├── package.json
├── src
|   ├── ect
|   │   ├── _components
|   │   │   ├── _a.ect
|   │   │   ├── _b.ect
|   │   │   └── _c.ect
|   │   ├── _layout.ect
|   │   └── index.ect
|   ├── js
|   │   └── main.js
|   └── scss
|       ├── _components
|       │   ├── _a.scss
|       │   ├── _b.scss
|       │   └── _c.scss
|       └── main.scss
└── stories
    └── index.stories.js
```

## SCSSを適用する設定

次に `src/scss/main.scss` をStorybook側で読み込む設定をします。

1. `node-sass`, `style-loader`,  `css-loader` , `sass-loader` パッケージのインストール
1. `.storybook/main.js` にStorybook側のwebpack設定を追加
1. `.storybook/preview.js` で対象のscssファイルをimport

```
yarn add -D node-sass style-loader css-loader sass-loader
```

`.storybook/main.js`

```javascript
const path = require('path');

module.exports = {
  stories: ['../stories/**/*.stories.js'],

  webpackFinal: async (config) => {
    config.module.rules.push({
      test: /\.scss$/,
      use: ['style-loader', 'css-loader', 'sass-loader'],
      include: path.resolve(__dirname, '../'),
    });

    return config;
  }
};
```

`.storybook/preview.js`

```javascript
import '!style-loader!css-loader!sass-loader!../src/scss/main.scss';
```

## Addonのインストール

### Storybook Viewport Addon

https://github.com/storybookjs/storybook/tree/master/addons/viewport
モバイルデバイスごとのViewを確認できるaddonです。レスポンシブコンポーネントの確認にはとても便利です。

1. `@storybook/addon-viewport` のインストール
2. `.storybook/main.js` の更新
3. `.storybook/preview.js` の更新


#### `@storybook/addon-viewport` のインストール

このaddonは `@storybook/react` に依存しているのでこちらもインストールします。

```
yarn add -D @storybook/addon-viewport @storybook/react
```

#### `.storybook/main.js` の更新

Addon定義を追加します。

```javascript
const path = require('path');

module.exports = {
  stories: ['../stories/**/*.stories.js'],

  webpackFinal: async (config) => {
    config.module.rules.push({
      test: /\.scss$/,
      use: ['style-loader', 'css-loader', 'sass-loader'],
      include: path.resolve(__dirname, '../'),
    });

    return config;
  },

  addons: [
    '@storybook/addon-viewport/register'
  ],
};
```

#### `.storybook/preview.js` の更新

viewport定義を追加します。

```javascript
import { addParameters } from '@storybook/react';
import { INITIAL_VIEWPORTS } from '@storybook/addon-viewport';
import '!style-loader!css-loader!sass-loader!../src/scss/main.scss';

addParameters({
  viewport: {
    viewports: INITIAL_VIEWPORTS,
  },
});
```

これでOKですが、ここで storybookを起動するとエラーになります。。これは次のaddonをついかすることで解消できます。
<img width="1792" alt="Screen Shot 2020-03-30 at 11.52.22.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/7804/d8025f25-d740-b3ed-99d8-aed5fe0de92c.png">

### Storybook Docs Addon

https://github.com/storybookjs/storybook/tree/master/addons/docs
mdxファイルの利用を可能にするadonです。試行錯誤の結果これだと期待するアプトプットが出せました。

1. `@storybook/addon-docs` のインストール
2. `.storybook/main.js` の更新
3. `.storybook/preview.js` の更新
4. mdxコンポーネントファイルの作成


#### `@storybook/addon-docs` のインストール

```
yarn add -D @storybook/addon-docs
```

#### `.storybook/main.js` の更新

storiesプロパティでターゲットファイルを `mdx` にします。
今回は `stories` 以下のmdx拡張子ファイルを対象とします。
それから、addon定義も追加します。

```javascript
const path = require('path');

module.exports = {
  stories: ['../stories/**/*.stories.mdx'],
  webpackFinal: async (config, { configType }) => {
    config.module.rules.push({
      test: /\.scss$/,
      use: ['style-loader', 'css-loader', 'sass-loader'],
      include: path.resolve(__dirname, '../'),
    });

    return config;
  },
  addons: [
    '@storybook/addon-viewport/register',
    '@storybook/addon-docs',
  ],
};
```

#### mdxコンポーネントファイルの作成

`stories/` 配下にmdxファイルを作成します。今回は目次として `index.stories.mdx` , ボタンコンポーネントとして `button.stories.mdx` を作成します。

- `stories/index.stories.mdx`

```md
import { Meta } from '@storybook/addon-docs/blocks';

<Meta title="01_Getting started" />

# Getting started

Welcome to Our Design System!!
```

- `stories/button.stories.mdx`

Storyタグ内のソースを元にコンポーネント表示、コピー用のソースが生成表示されます。

```md
import { Meta, Story, Preview } from '@storybook/addon-docs/blocks';

<Meta title="02_Components/Button" />

# Button

Buttonコンポーネント

<Preview withSource="open">
    <Story name="Button">
        <button class="button button--primary">Sample Button</button>
    </Story>
</Preview>
```

#### `.storybook/preview.js` の更新

特に設定は不要ですが、左ペインのナビゲーションに連番でコンポーネントを並べたいので、`options.storySort` にソート設定を追加しました。

```javascript
import { addParameters } from '@storybook/react';
import { INITIAL_VIEWPORTS } from '@storybook/addon-viewport';
import '!style-loader!css-loader!sass-loader!../src/scss/main.scss';

addParameters({
  viewport: {
    viewports: INITIAL_VIEWPORTS,
  },
  options: {
    storySort: (a, b) => {
      return a[1].kind === b[1].kind ? 0 : a[1].id.localeCompare(b[1].id, undefined, { numeric: true })
    }
  },
});
```

これで以下のように表示ができます。

<img width="1792" alt="Screen Shot 2020-03-30 at 13.12.26.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/7804/df286044-08a3-adf0-69cf-2fd9a268c277.png">