StencilJS with Storybook
Tags: storybook, stenciljs, webcomponents, designsystem
Published: 2022-02-04 18:33:00

StencilJS is such a great tool for creating web components, and Storybook is great for creating design systems, but integrating these two tools doesn't feel very natural because there's no single right way to do it.
After a lot of research, I will show you a simple way to carry out this integration and not die trying 😁

## Create a Stencil Project
```sh
npm init stencil
```

This will show you some questions. Answer this way
```sh
✔ Pick a starter > component
✔ Project name > storybook-wc-stencil
```
After that, you will have a stencil project with a basic example component inside.

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/gmb0w65718bk7mb1dj52.png)

## Install dependencies
```sh
cd storybook-wc-stencil
yarn install
```

## Ignore node_modules code on check
Add _skipLibCheck_ property to exclude node_modules code

_**tsconfig.json**_
```json
{
  "compilerOptions": {
    ...
    "skipLibCheck": true,
  },
  ...
```

## Create a typing file for tsx imports
Our linter could give us problems when trying to import md files, we can also use this file for another type of extensions.

_**src/typings.d.ts**_
```ts
declare module '*.jpg';

declare module '*.md' {
  const value: string; // markdown is just a string
  export default value;
}

declare module '*.css' {
  const content: { [className: string]: string };
  export default content;
}
```

## Add storybook

```sh
npx -p @storybook/cli sb init --type html 
```
This will generate the storybook project

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/6wg8fjux26it11iuj4oc.png)

## Add notes addon

```sh
yarn add -D @storybook/addon-notes
```

_**.storybook/main.js**_
```js
module.exports = {
  stories: ['../src/**/*.stories.mdx', '../src/**/*.stories.@(js|jsx|ts|tsx)'],
  addons: [
    '@storybook/addon-links',
    '@storybook/addon-essentials',
    '@storybook/addon-notes',
  ],
  framework: '@storybook/html',
};

```

## Configuration to load Stencil components on Storybook

_**.storybook/preview.js**_
```js
import { defineCustomElements } from '../dist/esm/loader';

defineCustomElements();

export const parameters = {
  actions: { argTypesRegex: '^on[A-Z].*' },
  controls: {
    matchers: {
      color: /(background|color)$/i,
      date: /Date$/,
    },
  },
};

```

## Clean stories

Let's remove all content from stories directory

## Project structure

Create a new file called _**my-component.stories.tsx**_ inside _**src/stories**_ directory

Your project structure should look like this

```
./storybook-wc-stencil/
|
|---- .storybook/
|     |---- main.js
|     |---- preview.js
|---- src/
|     |---- components/
|     |     |---- my-component/
|     |           |---- my-component.css
|     |           |---- my-component.e2e.ts
|     |           |---- my-component.spec.ts
|     |           |---- my-component.tsx
|     |           |---- readme.md
|     |---- stories/
|     |     |---- components/
|     |           |---- my-component.stories.tsx
|     |---- typings.d.ts
|---- .editorconfig
|---- .gitignore
|---- .prettierrc.json
|---- LICENSE
|---- package.json
|---- readme.md
|---- stencil.config.ts
|---- tsconfig.json
|---- yarn.lock
```

## Generate components

We can use the next command to automatically generate our components on our _components_ directory

```sh
yarn generate component-name
```

## Run Project

For having hot reload, we must execute this two commands in parallel, so we can use two terminals or create a new script

```sh
yarn build -- --watch
```

```sh
yarn storybook
```

The first command will generate the build of our components, we use the `--watch` flag to be always generating this build on any change

## Story code structure

We are going to face some disadvantages when working storybook with stencil
- We need to define properties that we want to use in controls
  - We need to define default props for controls
- We need to add description and prop types for Docs pages
- defaultValue property is not working for Doc pages
- We need to pass args values on the template

```ts
// This md file is generated by stencil, and we are going to use it as a note page
import notes from '../../components/my-component/readme.md';

export default {
  title: 'UI/My Component',
  args: {
    // Here we define default values that we want to show on controls
    // Also, only props defined here are going to be shown
    first: 'Juan Fernando',
    middle: 'Gómez',
    last: 'Maldonado',
  },
  argTypes: {
    // Here we can add description and prop value type
    first: {
      description: 'First name',
      // First way to define type
      table: {
        type: {
          summary: 'string',
        },
      },
    },
    middle: {
      // Second and shorter way to define type
      type: {
        summary: 'string',
      },
    },
    last: {
      // We can disable the property
      // This will hide it in controls and Doc page
      table: {
        disable: true,
      },
    },
  },
  parameters: {
    // This will create a note page for our story component
    notes,
  },
};

const Template = args =>
  `<my-component first="${args.first}" middle="${args.middle}" last="${args.last}"></my-component>`;

export const Basic = Template.bind({});

export const Another = Template.bind({});
Another.args = {
  first: 'John',
};
```

### Cleaner way for adding values and description on stories

To avoid boilerplate code, I created a simple library that returns args, argTypes and a custom template for our component. This library is [story-wc-generator](https://www.npmjs.com/package/story-wc-generator).

```sh
yarn add story-wc-generator
```

```ts
import notes from '../../components/cool-button/readme.md';
import storyGenerator from 'story-wc-generator';

const { args, argTypes, Template } = storyGenerator('cool-button', {
  text: { value: 'Click me!', description: 'Text label', type: 'string' },
  color: {
    value: 'primary',
    description: 'Color of button',
    control: 'select',
    options: ['primary', 'secondary', 'dark'],
    type: 'primary | secondary | dark',
  },
});

export default {
  title: 'UI/Cool Button',
  args,
  argTypes,
  parameters: {
    notes,
  },
};

export const Primary = Template.bind({});

export const Secondary = Template.bind({});
Secondary.args = {
  color: 'secondary',
};

...
```

This example includes all properties that can be used, but you can go to the
[documentation](https://www.npmjs.com/package/story-wc-generator) for more about the library

After creating our components and stories, we must have a result like this


![Controls](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ddpyhdeysiis2dcw45mp.png)

![Docs](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/5lxagsk0yrxiqvrb0599.png)

![Notes](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/dec620p8aao1q7722dev.png)

You can see the repository [here](https://github.com/jf-gm-dev/storybook-wc-stencil) and the live project [here](https://jf-gm-dev.github.io/storybook-wc-stencil/?path=/story/introduction--page)