Build a Web Component library with Stencil and Storybook
Tags: stencil, storybook, webcomponents, javascript
Published: 2020-02-18 16:48:41

In this little tutorial we learn how to add [Web Components](https://www.webcomponents.org/) created with Stencil to a Storybook setup. [Stencil](https://stenciljs.com/) is a framework which let us write Web Components in a JSX-style syntax similar to [React's class-components](https://reactjs.org/docs/react-component.html). It then compiles it to native Web Components code to make it usable in the browser.

# 1. Create a new Stencil project

First we initialize a new Stencil project from scratch with [Stencil's handy CLI tool](https://www.npmjs.com/package/create-stencil). It generates an initial project structure for us where we can simply add new components later on.

_**Note:** If you already have a Stencil project you can skip this step and go straight to [Step 2](#add-storybook)._

```sh
npm init stencil
```

In the following dialog Stencil asks us to pick a project type. Since we want to generate a library of Web Components choose the `component` option here and continue.

![Screenshot of Stencils project wizard where the user can choose the type of the project](https://dev-to-uploads.s3.amazonaws.com/i/ue4zzfv0emqvlhnlwgqa.png)

In the last step of the project generator we choose a name for the project, for simplicity we name it `storybook-stencil-example`.

![Screenshot of Stencils project wizard where the user can choose the name of the project](https://dev-to-uploads.s3.amazonaws.com/i/kmioucv3ylnqgm9mge4b.png)

Now we navigate into our newly created stencil project and install the dependencies:

```sh
cd storybook-stencil-example  # Navigate into project dir
npm i                         # Install dependencies
```

# 2. Add Storybook to the project <a name="add-storybook"></a>

Because Stencil components are compiled to Web Components we simply can use [Storybook for HTML](https://storybook.js.org/docs/guides/guide-html/) project type here:

```sh
# Bootstrap storybook
npx -p @storybook/cli sb init --type html

# Install additional dependencies for our setup
npm i --save-dev \
  write-file-webpack-plugin \
  copy-webpack-plugin \
  @storybook/addon-notes
```

Now we need to make some changes to the default configuration from Storybook. We

```js
// .storybook/main.js
const fs = require('fs');
const path = require('path');
const CopyPlugin = require('copy-webpack-plugin');
const WriteFilePlugin = require('write-file-webpack-plugin');

const OUTPUT_DIR = '../dist';
// Stencil names the project entry the same as the project
// Look for the file `dist/<your-project-name>.js` to find out what to insert here
const PROJECT_NAME = 'storybook-stencil-example';

module.exports = {
  stories: ['../src/**/*.stories.js'],
  addons: ['@storybook/addon-notes/register'],
  // Custom webpack config to tell Storybook where to find the compiled files from Stencil
  async webpackFinal(config) {
    config.entry.push(path.join(__dirname, OUTPUT_DIR, `${PROJECT_NAME}.js`));
    fs.readdirSync(path.join(__dirname, OUTPUT_DIR, 'collection/components')).map(file => {
      jsFilePath = path.join(__dirname, OUTPUT_DIR, `collection/components/${file}/${file}.js`);
      try {
        if (fs.existsSync(jsFilePath)) {
          config.entry.push(jsFilePath);
        }
      } catch (err) {
        console.error(err);
      }

      // Add CSS
      let cssFilePath = path.join(
        __dirname,
        OUTPUT_DIR,
        `collection/components/${file}/${file}.css`
      );
      try {
        if (fs.existsSync(cssFilePath)) {
          config.entry.push(cssFilePath);
        }
      } catch (err) {
        console.error(err);
      }
    });

    // Add all static files to Storybook
    config.plugins.push(
      new CopyPlugin([
        {
          from: '**/*',
          to: './',
          context: 'dist',
        },
      ])
    );

    // Write the files to disk and not to memory
    config.plugins.push(new WriteFilePlugin());

    return config;
  },
};
```

# 3. Create our first story

The Stencil project setup has already added a sample component for us named `my-component`. So let's create our first story for this component. Inside the component folder add a new file `my-component.stories.js`:

```sh
src/
└── components/
    └── my-component/
        ├── my-component.css
        ├── my-component.e2e.ts
        ├── my-component.stories.js  # <-- Add the file here
        ├── my-component.tsx
        └── readme.md
...
```

_Of course we could also write the stories in TypeScript because stencil already relies on it but to keep things simple we use plain JavaScript for now._

```js
// my-component.stories.js
import readme from './readme.md';

export default {
  title: 'My Component',
  parameters: {
    markdown: readme,
  },
};

export const Default = () => `
  <my-component first="Millie" middle="Bobby" last="Brown"></my-component>
`;
```

Importing the auto-generated `readme.md` from Stencil gives us a free documentation of our component which can also be showed in Storybooks "Notes" tab of the component.

# 4. Development workflow

To make the best use of Stencil and Storybook, both support a live-reload development workflow so that we can see changes we make to our Stencil components directly in Storybook without reloading the browser.
To make this work we start the Stencil dev server and the Storybook dev server parallel in two different terminals:

```sh
# Terminal 1 - Run Stencil compiler in watch mode
npm start

# Terminal 2 - Run Storybook
npm run storybook
```

Now the final result should now look like this in the browser. The canvas gives us a preview of our component while the note tab holds the documentation of the component itself.

[![Screen recording of the final result where a Stencil component is loaded inside storybook](https://dev-to-uploads.s3.amazonaws.com/i/kkj5wjfhxrz5sesj8wsx.gif)](https://ofhouse.github.io/storybook-stencil-example)

You can also see a live-demo of the Storybook here: [https://ofhouse.github.io/storybook-stencil-example](https://ofhouse.github.io/storybook-stencil-example/?path=/story/my-component--default)

_**This is only a basic setup guide for Stencil and Storybook, to make use of Storybook's advanced features like [Knobs](https://github.com/storybookjs/storybook/tree/master/addons/knobs) or [Actions](https://github.com/storybookjs/storybook/tree/master/addons/actions) I will add a second tutorial shortly. So follow me for more content!**_

---

_This guide was made possible by the work of of [Bobby Johnson](https://github.com/popcorn245). He has made a really nice video of the whole process of his Stencil and Storybook setup here: [Using StencilJS with Storybook on YouTube
](https://youtu.be/XwHtPw3izLE)_

---

You can find the whole example project from this tutorial in the following repository:

{% github ofhouse/storybook-stencil-example %}