AngularでStorybookを使う

## はじめに
AngularでAtomic Designを実践してみようと思い、Storybookを使ってみました。
いろいろハマったので書き残しておこうと思います。
導入時には公式ドキュメントとこちらの記事が参考になりました。
https://qiita.com/Quramy/items/463ed80f0d6fcd9e3939


## 環境
試した環境です。

- Angular: 6.0.3
- storybook: 3.4.8

## 導入
下記コマンドを実行すればOKです。
プロジェクト内にstorybookで使うファイルが生成されているはずです。

```bash
npm i -g @storybook/cli
cd {対象プロジェクトのディレクトリ}
getstorybook
```

getstorybokで行われてることは下記に書かれています。
https://storybook.js.org/basics/guide-angular/#docs-content

***注意***
src/tsconfig.app.jsonのexcludeにstorybookに関するファイルを追加しないとangularのビルド時にエラーになります。

```json
{
  "extends": "../tsconfig.json",
  "compilerOptions": {
    "outDir": "../out-tsc/app",
    "module": "es2015",
    "types": []
  },
  "exclude": [
    "src/test.ts",
    "**/*.spec.ts",
    "stories",            // 追加
    "**/**.stories.ts"    // 追加
  ]
}
```

## storybookを起動する
下記コマンドのどちらかでstorybookが起動します。ブラウザでlocalhost:6006にアクセスすると確認できます。

```bash
yarn storybook
# or
npm run storybook
```

### Cannot read property 'compilation' of undefined が発生した場合
Angular6だと、StoryBook 4.0-alphaを使わないといけないらしい。

### Module build failed: Error: Cannot find module 'babel-core' が発生した場合
babel-coreをインストールすると解決します。

```
yarn add babel-core --dev
```


## componentの依存関係を解決する
例えば、itemというcomponentでpriceというcomponentを利用していた場合、story上で依存関係を解決しないといけません。
storybookはangularのmoduleファイルをみないので、storyにitem componentの追加だけを行うとpriceというエレメントなんて知らないよと怒られてしまいます。
storyで依存を解決する方法は下記です。moduleファイルと似ているので、すんなり書けると思います。　

```js
const itemStories = storiesOf('{story名}', module);
itemStories.addDecorator(
  moduleMetadata({
    declarations: [
       PriceComponent
    ],
    imports: [],
    providers: []
  })
);

```

## Global styleとして読み込む方法
各ComponentでGlobalに定義しているstyleを参照したくなるケースがあるかと思います。
angularではangular.jsonに追加すればいいのですが、storybookはangular.jsonとは無縁なので、自分でGlobalで読み込めるようにしないといけません。

scssの場合は、storyファイル(src/stories/*.stories.ts)で下記のように対象ファイルをimportします。

```js
import '!style-loader!css-loader!sass-loader!../styles.scss';
```

cssの場合はscssのsass-loaderが不要になるだけです。

```js
import '!style-loader!css-loader!../styles.css';
```


## knobsの利用
knobsはcomponentのInputになる値をstorybook上から動的に変更できるプラグインです。
デフォルトでは入っていないようなので、自分で追加します。

```bash
yarn add @storybook/addon-knobs --dev
```

.storybook/addons.jsに追加
 
```js

import '@storybook/addon-actions/register';
import '@storybook/addon-links/register';
import '@storybook/addon-notes/register';
import '@storybook/addon-knobs/register';  // 追加

```

storyにknobsを追加します。angularの場合はangular用のknobsをimportする必要があるみたいです。

```js

import { withKnobs, text, number, color, object } from '@storybook/addon-knobs/angular';

const itemStories = storiesOf('{story名}', module);
txtContainerStories.addDecorator(withKnobs);

```


## import時にsrcを起点としたパスでimportする
angularでscssやtsファイルで他のファイルをimportする際には、どのファイルからも下記のようにsrcを起点としたパスで書くことができます。(tsconfigの設定にもよる)

```js

// e.g.
import { ButtonComponent } from 'src/app/shared/button/button.component.ts';

```

```scss
// e.g.
@import 'src/variables/colors.scss';
```

しかし、storybookでは、パスを解決してくれないので、エラーになってしまいます。
これも自分でどうにかしないといけないです。
やり方としては、storybookのwebpackの設定を自分で拡張するというやり方です。

1. .storybook配下にwebpack.config.jsファイルを作成します。

    ```js:webpack.config.js
    const genDefaultConfig = require('@storybook/angular/dist/server/config/defaults/webpack.config.js');
    const path = require('path');

    module.exports = (baseConfig, env) => {
      const config = genDefaultConfig(baseConfig, env);

      return config;
    };
    ```
2. aliasを設定して、srcのパスを解決させる設定を追加します。これでimport時にsrcから指定してもstorybookからも怒られません。

    ```js:webpack.config.js
    const genDefaultConfig = require('@storybook/angular/dist/server/config/defaults/webpack.config.js');
    const path = require('path');

    module.exports = (baseConfig, env) => {
      const config = genDefaultConfig(baseConfig, env);

      // 追加
      config.resolve = {
        ...config.resolve,
        alias: {
          src: path.resolve(__dirname, '../src')
        }
      };

      return config;
    };
    ```

今後、他にもwebpackの設定を自分で拡張していくケースがあると思うので、基本的にwebpack.config.jsは最初で用意しておくといいと思います。

## 静的ファイルの参照
assets配下にある画像やアイコンをcomponent等で参照することはよくあると思いますが、storybookでassetsファイルを読み込めるようにするには、起動やビルドじにoptionが必要でした。
package.jsonを開いて、scriptsを修正します。

```js:package.json

{
  "name": "test-project",
  "version": "0.0.0",
  "scripts": {
    // 省略...
    "storybook": "start-storybook -p 6006 -s ./src",  // 修正
    "build-storybook": "build-storybook -s ./src"    // 修正
  },
  // 省略...
}

```

何をしているかというと、static-dirオプションというのを追加しています。
これにより、ビルド時などに吐き出されるディレクトリにassetsディレクトリが追加され、storybook上からも参照することが可能になります。
なぜ指定している値がassetsじゃなくてsrcになっているかというと、指定したディレクトリの中にあるファイルやディレクトリのみが展開され、指定したディレクトリ自体は展開されないためです。
angularで画像などを参照する際には、通常 assets/image/item.pngのようにすると思うのですが、staticーdirオプションにassetsを指定すると、image/item.pngでしか参照できません。
ただ、srcを指定すると無駄なファイルもたくさん吐き出されるので、良い方法をご存知の方は是非教えてください！


## さいごに
いろいろハマりましたがコンポーネントを一覧化できるので、便利ですね！
あとatomic designの考えのもとにつくってみたんですが、再利用性も高くなるし、コードのメンテナンスもしやすくなるしでいいことだらけでした。最初は時間がかかってしまいますが、これからはatomic designを意識して開発していきたいなと思います。