Como criar uma biblioteca de componentes com Angular e Storybook
Tags: angular, storybook
Published: 2020-01-03 12:10:42

Se você já tentou [criar uma biblioteca](https://angular.io/guide/creating-libraries) de componentes pro seu sistema, você com toda certeza já alterou um componente que quebrou várias páginas e quando te perguntaram quem foi...

![Um fantoche de macaco olha de canto de olho pra tela, e depois olha pra frente como se não tivesse visto nada](https://i.kym-cdn.com/photos/images/original/001/471/227/dd0.png)

Pra eliminar esse e outros problemas, como não saber se um componente existe, como funciona ou em que página você pode ver um exemplo dele funcionando; existe o Storybook.

[![Logo do Storybook](https://user-images.githubusercontent.com/321738/63501763-88dbf600-c4cc-11e9-96cd-94adadc2fd72.png)](https://storybook.js.org/)

O Storybook se denomina como "*Um explorador de componentes UI para desenvolvedores front-end*". Isso quer dizer que ele serve para gerar várias histórias dos nossos componentes, assim como a documentação deles, para facilitar a exploração visual dos nossos componentes.

![Vídeo de exemplo do Storybook](https://miro.medium.com/proxy/1*593C9tIr2pYJRbjmhA2GJA.gif)

Então, vamos criar passo-a-passo essa biblioteca de componentes.

----

## Preparação

Na época que esse guia foi preparado, foram usadas as seguintes tecnologias:

| tecnologia | versão | instalação |
|-|-|-|
| Node.js | 11.6.0 | [como instalar](https://nodejs.org/en/) |
| NPM | 6.13.4 | **Já vem com o Node.js** |
| Angular CLI | 8.3.21 | [como instalar](https://angular.io/guide/setup-local#step-1-install-the-angular-cli) |

Todo o código desse projeto está no GitHub, então, caso você se perca em qualquer parte, pode fazer uma comparação direta:
{% github klauskpm/angular-storybook no-readme %}

#### :warning: Caso você já saiba gerar uma biblioteca, você pode pular para a sessão [Instalando o Storybook](#instalando-o-storybook) :warning:

## Gerando o projeto

Usando o Angular CLI, vamos criar um projeto chamado `angular-storybook` e uma biblioteca chamada `storybook-ui`:

```sh
ng new angular-storybook

# dentro do projeto angular-storybook
ng g lib storybook-ui --prefix ui # versão curta de ng generate library
```

Já temos o projeto para usar nossos componentes, e temos a biblioteca onde vamos desenvolver eles. A biblioteca já vem com um componente pronto, mas não vamos usar ele. Então, vamos até a pasta `/projects/storybook-ui/src/lib/` e deletamos tudo de lá. Nessa mesma pasta vamos criar o módulo e componente `simple-button`.

```sh
# dentro da pasta /projects/storybook/src/lib

ng g m simple-button # versão curta de ng generate module

ng g c simple-button --export # versão curta de ng generate componente
```

E vamos dar uma cara de botão para o nosso componente:
```ts
import { Component, Input } from '@angular/core';

@Component({
  selector: 'ui-simple-button',
  template: `
    <button>{{text}}</button>
  `,
  styles: [``]
})
export class SimpleButtonComponent {

  @Input() text = 'clique aqui';
}
```

Agora, vamos no arquivo `/projects/storybook/src/public_api.ts` e substituir todo o conteúdo desse arquivo por:

```ts
export * from './lib/simple-button/simple-button.component';
export * from './lib/simple-button/simple-button.module';
```

Para usar esse componente precisamos gerar o arquivo final da biblioteca:
```sh
ng b storybook-ui # versão curta de ng build
```

Reescrever o `/src/app/app.component.html`:
```html
Ola Mundo!
<ui-simple-button></ui-simple-button>
```

E adicionar o `SimpleButtonModule` ao `/src/app/app.module.ts`:
```ts
import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';
import { SimpleButtonModule } from 'storybook-ui';

import { AppComponent } from './app.component';

@NgModule({
  declarations: [
    AppComponent
  ],
  imports: [
    BrowserModule,
    SimpleButtonModule // modificado
  ],
  providers: [],
  bootstrap: [AppComponent]
})
export class AppModule { }
```

Por último, vamos servir nossa aplicação e ver como ela está no http://localhost:4200 :
```sh
ng s # versão curta de ng serve angular-storybook
```

![Projeto de Angular rodando na porta 4200, com o texto "Ola Mundo!" e o botão "Clique aqui".](https://thepracticaldev.s3.amazonaws.com/i/01j0cq7vjg2klkbx9qf9.png)

> Pode comparar o seu progresso com a [tag v1.2 do repositório](https://github.com/klauskpm/angular-storybook/tree/v1.2)

----

## Instalando o Storybook

O Storybook tem um [guia de como fazer a instalação em um projeto Angular](https://storybook.js.org/docs/guides/guide-angular/) e o jeito mais fácil é só usando uma única linha:

```sh
npx -p @storybook/cli sb init --type angular
```

Executando essa linha no seu projeto, ele:
- instala todas as dependências necessárias
- adiciona os scripts `storybook` e `build-storybook` no seu package.json
- cria os arquivos de configuração pro Storybook
- cria as histórias de exemplo no seu projeto principal

Para ver o Storybook em ação, é só rodar o comando:

```sh
npm run storybook
```
![Página do Storybook rodando em localhost:6006, exibindo a história ˜with some emoji and action" do componente Button ](https://thepracticaldev.s3.amazonaws.com/i/tup293mgc2eu5ksz10qc.png)

Tudo bonito, tudo rodando. Mas, a configuração do Storybook está apenas pro seu projeto principal, e não para a sua biblioteca. Além disso, você sabe o que foi adicionado e para que? Então vamos voltar no tempo e seguir de forma manual.

### Fazendo a instalação manualmente

#### 1) Instale as dependências

```sh
npm install @storybook/angular babel-loader @babel/core --save-dev
```

#### 2) Adicione os scripts de npm

```json
{
  "scripts": {
    "storybook": "start-storybook -p 6006",
    "build-storybook": "build-storybook"
  }
}
```

O script `storybook` vai executar o [start-storybook](https://storybook.js.org/docs/configurations/cli-options/#for-start-storybook) para exibir nossas histórias no http://localhost:6006. O `build-storybook` vai executar o [build-storybook](https://storybook.js.org/docs/configurations/cli-options/#for-build-storybook) que vai gerar os arquivos estáticos do Storybook na pasta `storybook-static`.

> Você pode definir o diretório final mudando o comando `build-storybook` por `build-storybook -o /dist/storybook`, colocando os arquivos estáticos no mesmo diretório que os builds gerados pelo seu projeto.

#### 3) Crie o arquivo de configuração

É esse o arquivo que o Storybook vai usar para saber onde ele pode procurar as nossas histórias e como identificar os arquivos. No caso, vamos procurar arquivos com `.stories.ts` neles, dentro das pastas `src` e `projects`.

Crie o arquivo `.storybook/config.js` com o seguinte conteúdo:

```js
import { configure } from '@storybook/angular';

configure([
  require.context('../src', true, /\.stories\.ts$/),
  require.context('../projects', true, /\.stories\.ts$/)
], module);
```

#### 4) Configure o Storybook para funcionar com TypeScript

Como o `@storybook/angular` usa o [ForkTsCheckerWebpackPlugin](https://github.com/TypeStrong/fork-ts-checker-webpack-plugin), precisamos criar o arquivo `.storybook/tsconfig.json`:

```json
{
  "extends": "../tsconfig.json",
  "exclude": [
    "../src/test.ts",
    "../src/**/*.spec.ts",
    "../projects/**/*.spec.ts"
  ],
  "include": [
    "../src/**/*",
    "../projects/**/*"
  ]
}
```

#### 5) Escreva sua história :tada:

Finalmente! Agora vamos criar nossa própria história e ver como ela se comporta.

Dentro da pasta do nosso componente `simple-button`, vamos criar o arquivo `index.stories.ts` com o conteúdo a seguir:

```ts
import { SimpleButtonComponent } from "./simple-button.component";

export default {
  title: 'UI | Button'
};

export const withDefaultValues = () => ({
  component: SimpleButtonComponent
});

export const withText = () => {

  return {
    component: SimpleButtonComponent,
    props: {
      text: 'Ola'
    }
  }
};
```

> Essa é uma das formas que podemos escrever as histórias. Ela é chamada de [CSF (Component Story Format)](https://storybook.js.org/docs/formats/component-story-format/). Também temos a [StoriesOf API](https://storybook.js.org/docs/formats/storiesof-api/) e o [MDX Syntax, que está por vir](https://storybook.js.org/docs/formats/mdx-syntax/). Como o CSF é um adição nova no Storybook, boa parte dos exemplos da internet vão estar usando o StoriesOf API.

Dessa forma estamos falando que temos o componente Button, dentro do agrupador UI, com as histórias "With Default Values" e "With Text".

```
UI
└── Button
    ├── With Default Values
    ├── With Text

```

#### 6) Rode o projeto

Rode o projeto com o `npm run storybook` e veja o resultado final:

![Alt Text](https://thepracticaldev.s3.amazonaws.com/i/f9cdv0ktd2vxdqvzh1yr.png)

> Pode comparar o seu progresso com a [tag v1.3 do repositório](https://github.com/klauskpm/angular-storybook/tree/v1.3)

## Próximos passos - addons

Agora que você aprendeu como configurar o Storybook e escrever uma história, você pode melhorar a qualidade das suas histórias com simples pluggins, chamados de [addons](https://storybook.js.org/addons/).

Por exemplo, com o addon de [actions](https://github.com/storybookjs/storybook/blob/master/addons/actions/README.md) você pode adicionar logs para ações, como clicks, e ouvir o que eles estão enviando. Com o addon de [knobs](https://github.com/storybookjs/storybook/tree/master/addons/knobs/README.md) você pode expor `@Input`s do seu componente e altera-los na hora.

Pra não ficar só na explicação e sonhos, vamos montar ele agora. É muito fácil.

#### 1) Instale as dependências

Além dos pacotes óbvios do knobs e do actions, precisamos também instalar o pacote do addons, que vai ser responsável por carregar os addons no Storybook.

```sh
npm install --save-dev @storybook/addons @storybook/addon-knobs @storybook/addon-actions
```

#### 2) Crie o arquivo de configuração dos addons

Crie o arquivo `.storybook/addons.js` com a seguinte configuração:

```
import '@storybook/addon-knobs/register';
import '@storybook/addon-actions/register';
```

> A ordem que você *importa* eles é importante. Ela vai dizer a ordem em que esses addons vão aparecer nos paineis

#### 3) Aplique os addons na sua história

No nosso caso, vamos usar o knobs para expor o `@Input` text e o actions para exibir uma mensagem ao clicar no botão. Como vamos lidar com um evento e não uma propriedade, não podemos mais usar o `component` na história `withText`, pois o `props` só define propriedades e não eventos. Precisamos usar o `template`, mas para usarmos o template, precisamos definir o módulo com o componente `SimpleButtonComponent`.

No final das contas, o nosso `index.stories.ts` do `SimpleButtonComponent` vai ficar assim:


```ts
import { SimpleButtonComponent } from "./simple-button.component";
import { text, withKnobs } from '@storybook/addon-knobs';
import { moduleMetadata } from '@storybook/angular';
import { action } from '@storybook/addon-actions';

export default {
  title: 'UI | Button',
  decorators: [
    withKnobs,
    moduleMetadata({declarations: [SimpleButtonComponent]})
  ],
};

export const withDefaultValues = () => ({
  component: SimpleButtonComponent
});

export const withText = () => {
  return {
    template: `
        <ui-simple-button
          [text]="text"
          (click)="click($event)"
        ></ui-simple-button>
    `,
    props: {
      text: text('Text', 'Ola'),
      click: action('Clicou')
    }
  }
};
```

E quando rodarmos o Storybook:
![História "With Text" está com o texto alterado para "isso ai", via knob](https://thepracticaldev.s3.amazonaws.com/i/s4izuk7rgt6ohjnbhdxk.png)
![História "With Text" está exibindo o log "Clicou", depois de ter clicado no botão e acionado o actions](https://thepracticaldev.s3.amazonaws.com/i/rsn2w4c4pto3zr59en57.png)

> Pode comparar o seu progresso com a [tag v1.4 do repositório](https://github.com/klauskpm/angular-storybook/tree/v1.4)

----

# Conclusão

Agora, você sabe como...
:white_check_mark: Instalar e configurar o Storybook
:white_check_mark: Criar histórias para componentes
:white_check_mark: Instalar e configurar addons

Você não vai ter mais problemas em relação ao que sua aplicação tem ou não tem, e o desenvolvimento vai seguir com mais confiança e agilidade, por poder testar as mudanças em diversos cenários.

Se você se interessou pelo Storybook e quer saber o que mais ele pode fazer, você pode começar a ver como...
:heavy_exclamation_mark: [Aprender o que compõe uma história](https://storybook.js.org/docs/basics/writing-stories/)
:heavy_exclamation_mark: [Exportar e deployar arquivos estáticos das histórias](https://storybook.js.org/docs/basics/exporting-storybook/)
:heavy_exclamation_mark: [Tematizar o Storybook](https://storybook.js.org/docs/configurations/theming/)
:tada: [E muito mais](https://storybookjs-next.now.sh/angular-cli/?path=/story/addon-background--background-component)

----

Se você :heart: esse artigo, não deixe de me seguir e compartilhar com seus colegas. E se achar que tem algo para mudar no artigo, não deixe de comentar!

Te vejo na próxima! :grimacing: