nuxt.js × Atomic Design での component 作成にhygenを使ってみる

nuxt × atomic design でのコンポーネントを量産していく過程で、 
`.vue`ファイルと **storybook**のjsファイルを **hygen** で生成していくフローが楽だったので、やり方をメモ。


[storybookのドキュメント](https://storybook.js.org/docs/guides/guide-vue/)通りに始めると、
出力サンプルが `/stories/` dirができ、そこに書いてくような形で始まる。

前提として、 `.stories.js` と `.vue` ファイルは同じdirに収納していく形にする方が、管理しやすいように思います。（違う？）
手動でやると大変、ミスもあるので、hygenを使い、この部分を自動化します。

## hygenとは

<img width="615" alt="hygen.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/274832/a886c78f-30eb-f34f-a02d-97e610368f7b.png">

Hygenとはコマンドラインからファイルを生成する対話式のコードジェネレーターのパッケージです。

参考 : https://qiita.com/usagi-f/items/ea4ecf7ec6a6b52567e3



## nuxtとstorybookをセットアップ

### まずはnuxtをセットアップ

サクッといれます。

```
$ npx create-nuxt-app <project-name>
$ cd <project-name>
$ npm run dev
```

動いた。

### storybookのインストールとコンポーネントの作成

[公式のドキュメント](https://storybook.js.org/docs/guides/guide-vue/) を参考に、今回は Manual setup(手動方式)でいれてみる。

#### スクリプトを入れる

```
$ npm install @storybook/vue --save-dev

$ npm install vue --save
$ npm install vue-loader vue-template-compiler @babel/core babel-loader babel-preset-vue --save-dev
```

`package.json`にコマンドを追加。今回はこんな感じで追加してみる。

```package.json
{
  "scripts": {
    "storybook": "start-storybook -s ./static -p 3001 --ci",
    "build-storybook": "build-storybook -c .storybook"
  }
}
```
#### configをつくる
`/.storybook/config.js` を作成。

```/.storybook/config.js
import { configure } from '@storybook/vue';

function loadStories() {
  // こちらを消し
  // require('../stories/index.js');

  // /components/ 配下の .stories.js を対象
  const req = require.context('../components', true, /\.stories\.js$/);
  req.keys().forEach(filename => req(filename));

}

configure(loadStories, module);
```

実際にはここで`addons.js`に、storybookのaddonを入れていくと思いますが、割愛。

vue開発で約に立ちそうなaddonの参考 :
https://qiita.com/asdfghjkl1234/items/3c7580718715c275a729


#### Atomic Design のルールに沿って、コンポーネントを作ってみる。

プロジェクト毎に微妙にルールが異なると思いますが、今回は`/components/`配下を以下のような、atom等の各粒度の配下に、「サブカテゴリ」を設けたルールにしてみます。
`.stories.js` も各コンポーネントとセットで定義していく。

```
components
├── atoms
│   ├── buttons
│   │   └── MyButton
│   │       ├── MyButton.stories.js  // ← ここ
│   │       └── MyButton.vue
│   ├── texts
│   │   └── MyText
│   │       ├── MyText.stories.js  // ← ここ
│   │       └── MyText.vue
│   ├── forms
│   ├── ... その他プロジェクトで必要なサブカテゴリ
│   └── others
├── molecules
├── organisms
├── pages
└── templates
```

最小限のコンポーネントを作成。
例として `MyButton.vue` と `MyButton.stories.js`

```MyButton.vue
<template>
  <button class="my-button"><slot></slot></button>
</template>

<script>
export default {
  name: 'MyButton'
}
</script>

<style scoped>
.my-button{}
</style>
```
```MyButton.stories.js

import { storiesOf } from '@storybook/vue'
import MyButton from './MyButton'

storiesOf('atoms/buttons/MyButton', module)
  .add('default', () => ({
    components: { MyButton },
    template: '<MyButton>私はボタンです</MyButton/>'
  }))
```

必要ファイルができたら以下で実行。

```
$ npm run storybook
```

package.jsonで指定した `http://localhost:3001` へアクセスすると無事見れました。

<img width="728" alt="スクリーンショット 2019-07-14 18.53.36.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/274832/7e0b384b-3349-a5a3-b9b6-d7d642f2f224.png">

## hygenをセットアップ

こちらも [公式ドキュメント](https://www.hygen.io/quick-start)通りに

```
$ cd <project-name>
$ npm i -g hygen
$ hygen init self
```
すると以下のような dir が作られる。

```
_templates
└── generator
    ├── help
    │   └── index.ejs.t
    ├── new
    │   └── hello.ejs.t
    └── with-prompt
        ├── hello.ejs.t
        └── prompt.ejs.t
```

`/_templates/generator/` 以下はサンプルファイルの様子。
hygenのジェネレーターは以下のようなルールになってるらしい。

```
hygen <generator> <action> [NAME]
```

サンプルは無視して、 Atomic Designのコンポーネントを作るので、以下のような3ファイルでテンプレートファイルを準備する。

```
_templates
└── component
    └── new
        ├── prompt.js
        ├── story.ejs.t
        └── vue.ejs.t
```

```prompt.js
// 対話式の設問でAtomic Designのコンポーネントを定義します

module.exports = [
  {
    type: 'select',
    name: 'category',
    message: '作成する Atomic Design のカテゴリを選択',
    choices: ['atoms', 'molecules', 'organisms', 'templates', 'pages']
  },
  {
    type: 'select',
    name: 'subcategory',
    message: 'サブカテゴリを選択',
    choices: [
      'no-subcategory', //サブカテゴリに属さない
      'buttons',
      'texts',
      'forms',

      // ... プロジェクトに必要なパーツのサブカテゴリをすべて定義する

      'others'
    ]
  },
  {
    type: 'input',
    name: 'componentname',
    message: 'component名を入力',
    hint: '"FormInputText" ...等（パスカルケース）'
  },

  // ・・・ プロジェクト毎、必要に応じて 設問を追加 ・・・

]
```
```story.ejs.t
---
to: components/<%= category %><% if(subcategory && subcategory!=='no-subcategory'){ %>/<%= subcategory %><% } %>/<%= componentname || 'unnamed'%>/<%= componentname || 'unnamed'%>.stories.js
---
import { storiesOf } from '@storybook/vue'
import <%= componentname %> from './<%= componentname %>'
storiesOf('<%= category %><% if(subcategory && subcategory!=='no-subcategory'){ %>/<%= subcategory %><% } %>/<%= componentname %>', module)
  .add('default', () => ({
    components: { <%= componentname %> },
    template: '<<%= componentname %>/>'
  }))

```
```vue.ejs.t
---
to: components/<%= category %><% if(subcategory && subcategory!=='no-subcategory'){ %>/<%= subcategory %><% } %>/<%= componentname || 'unnamed'%>/<%= componentname || 'unnamed'%>.vue
---
<template>
</template>

<script>
export default {
  name: '<%= componentname %>'
}
</script>

<style scoped>
</style>
```

### hygenを使ってみる

ここまでできたら、hygenコマンドをつかってコンポーネントを作ってみる。

```
$ hygen component new
```

上記を実行すると対話式でコンポーネント作成が始まるので、質問に順に答えると、
nuxtの`/components/`内に新しいファイルが、Atomic Designとして設定したルール通りで新規作成されます。


## アイデア次第ではもっと便利に

実際の開発では、templateにもっと大量に設定が追加されるので、新規作成はより便利に感じます。
storybookだけでなく、 jest 等のテストファイルも、テンプレも作っておくと捗ります。



もっと良いアイデアや使い方をご存知の方、コメントを頂ければ幸いです。