Angular + Storybook でコンポーネントガイドを作ろう

Storybook v3.3.0で[Angular対応](https://github.com/storybooks/storybook/issues/269)がリリースされたので、さっそく試してみました[^ngadv]。

[^ngadv]: Angularアドカレのネタにしようかな、とも思ってたのですが、若干タイミングがズレてたのでCDK優先にしました

## Angular CLIプロジェクトへの導入方法
Angular CLIで作成したプロジェクトに導入するのであれば、コマンド一発です。

```sh
ng new my-ng-storybook-prj
cd my-ng-storybook-prj
yarn add @storybook/cli --dev
yarn run getstorybook
```

これで完了です。
package.jsonに `storybook` と `build-storybook` scriptが定義されているので、こいつを叩くだけです。

```sh
yarn run storybook
```

`localhost:6006` でstorybookが起動します。

ちゃんとHMRにも対応しています。

![Q645xzK9WL.gif](https://qiita-image-store.s3.amazonaws.com/0/8808/0c4ac8d3-345d-cd41-3d7f-c992645d2431.gif)


もちろんReactやVue版と同じく、`yarn run build-storybook` を実行することで、storybookを静的なサイトとして出力することも可能です。

~~現状、 `getstorybook` で作成される設定では、storyを `.js` で記述するようになっていますが、TypeScriptに変えたい！という場合はWebpackのRequire Contextを修正しておくとよいです。~~
どう考えても.tsの方が良いに決まっているので、[PR出して修正済み](https://github.com/storybooks/storybook/pull/2594)。

```javascript:.storybook/config.js
// automatically import all files ending in *.stories.js
const req = require.context('../stories', true, /.stories.ts$/); //ここね
function loadStories() {
  req.keys().forEach((filename) => req(filename));
}

configure(loadStories, module);
```

## Angular版Storyの記述方法
Storyを書く際は、`@storybook/angular` の `storiesOf` を使います。

```ts
storiesOf('Awesome Button', module).add('to Storybook', () => ({
  component: AwesomeButtonComponent,
  props: {
    name: 'Button',
    myClick: action('Clicked!'),
  },
}));
```

対象のComponentと、propsを指定するのが基本です。
propsは、対象が `@Input` であれば入力として、 `@Output` であればEventEmitterが発火したタイミングで呼び出されるコールバック関数を指定します。

単純なComponentであればこれでよいのですが、そのComponentを動作させるためのモジュール設定が必要な場合は、さらに `moduleMetadata` を指定できます。

```ts
storiesOf('Awesome Button', module).add('to Storybook', () => ({
    component: AwesomeButtonComponent,
    props: { /*...*/ },
    moduleMetadata: {
      imports: [],
      schemas: [],
      declarations: [],
      providers: []
    }
  }))
```

Angularの単体テストを書いたことがある人であれば、下記のようなテスト用モジュール設定には馴染みがあると思います。Storybookの `moduleMetadata` も役割は同じです。

```ts
TestBed.configureTestingModule({
  declarations: [ BannerComponent ],
  providers: [
    { provide: ComponentFixtureAutoDetect, useValue: true }
  ]
})
```

Storyの書き方の詳細については、 https://github.com/storybooks/storybook/tree/master/examples/angular-cli/src/stories に色々とサンプルがあるので、こちらも参考にするとよいでしょう。

## css関係の諸注意

現状のStorybookはAngular CLIには特に依存しない作りになっています。このため、Componentのstyle周りについては、多少のworkaroundを施す必要があります[^css_tips]。

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

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

  // Overwrite .css rule
  const cssRule = config.module.rules.find(rule => rule.test && rule.test.toString() === '/\\.css$/');
  if (cssRule) {
    cssRule.exclude = /\.component\.css$/;
  }

  // Add .scss rule
  config.module.rules.unshift({
    test: /\.scss$/,
    loaders: ['raw-loader', 'sass-loader'],
  });

  return config;
};
```

[^css_tips]: https://storybook.js.org/basics/guide-angular/#configure-style-rules 

本当はAngular CLIのmodelを参照して、同じruleを適用するようにすべきなのでしょうが、ちょっと複雑になりそうなので今後の宿題にします。

## おわりに
さくっとStorybook + Angularの使い方を見てきましたが如何でしょうか。導入するのは本当に簡単なので、ぜひAngularでComponentガイドを作ってみてほしいです。

~~あと、[tsuyoshiwada/storybook-chrome-screenshot](https://github.com/tsuyoshiwada/storybook-chrome-screenshot) のAngular版があれば、CIでStorybookについてキャプチャをとって、[reg-suit](https://github.com/reg-viz/reg-suit) で画像スナップショットテストを回す、みたいなこともできそうなので、色々と夢が広がりますね。~~
折角なので、[自分でPR作ったので、mergeしてもらいました](https://github.com/tsuyoshiwada/storybook-chrome-screenshot/pull/21)。これで Angular + Storybook + Screenshot + Snapshot test という構成のプロジェクトがAngular CLIベース作れるようになったので、 [Quramy/angular-sss-demo](https://github.com/Quramy/angular-sss-demo) にデモレポジトリを作っています。

それでは、また。