How to create a react component library using Storybook, TypeScript, SCSS, and Rollup?
Tags: react, typescript, uilibrary, storybook
Published: 2022-02-11 21:44:00

## Introduction

The beautiful thing about react is that it lets you break down UI into smaller components and reuse them at multiple places in your project, but that's not it, you can standardize your components and publish them to npm or yarn and let other people use your components in their projects.

In this post, I'll show you how you can create a component library for reactJS using Typescript, Storybook, SCSS, and Rollup. 

It's not necessary to create a big library, you can create a library for a single component such as a *Date Picker* or you can create a full-fledged library like material UI.

A UI component library is very essential for organizations to maintain a consistent UI across different projects. However, you do not need to create a component library just for a single project as all components can be maintained in that particular project but if you work on multiple side projects and have similar components that you copy-paste from one project to another then it is better to create a library for those components.

So let's get started with our library

## Setting up the library

### Getting Started

Before starting this project make sure you have node js and npm installed globally in your system. If not, you can download node from [here](https://nodejs.org/en/)

Create a folder and name it whatever you want, but for the sake of this article let's name this folder react-lib, and if you are planning to publish this library to npm then check if the package name is available on [*npm*](https://npmjs.org) registry

Open Terminal and navigate to your project directory and run 

```bash
npm init -y
```

This initializes a node project in the current directory. The `-y` flag when passed to the NPM command tells the generator to use the defaults instead of asking questions, and a file `package.json` is created at the root of your directory

![package.json](https://cdn.hashnode.com/res/hashnode/image/upload/v1643534270391/HSBlwZPbD.png)

### React & React DOM

Let's install `react` and `react-dom` as dev dependencies

```bash
 npm i -D react react-dom
```

We are creating this library for reactJS so it is required that a project that uses this library must have `react` and `react-dom` as dependencies so let's add `react` and `react-dom` as peer dependencies in our `package.json` file.

![add-peerdeps](https://cdn.hashnode.com/res/hashnode/image/upload/v1643535785984/3I0kcbmmDx.png)

### Basic Folder Structure

Before adding storybook and typescript and moving further let's create a basic folder structure for our project. You can have any folder structure that you prefer.

![folder-structure.png](https://cdn.hashnode.com/res/hashnode/image/upload/v1643537525915/7UJOTuzaI.png)

### Installing Typescript

First, install typescript globally in your system with the following command

```bash
npm i typescript -g
```

Then install `typescript` and `@types/react` as a dev dependency in your project.

```bash
npm i -D typescript @types/react
```

Then create a `tsconfig.json` file using the following command. 

```bash
tsc --init
``` 

This file will allow you to configure further and customize how Typescript and `tsc` compiler interact.

Open `tsconfig.json` and change the following configuration.


```json
...
"compilerOptions" : {
    ...
    "jsx": "react",
    ...
    "module": "es6",
    ...
    "moduleResolution": "node",
    ....
    "outDir": "./dist",
    ....
  }
....
```

### Getting Storybook

[*Storybook*](https://storybook.js.org/) is an open-source tool for building UI components and pages in isolation. It streamlines UI development, testing, and documentation. It works with a javascript library such as React, Vue, Angular, etc.


To install the storybook in our library run this command:

```bash
npx sb init
```
You should now be able to run Storybook locally by running `npm run storybook` or if you prefer `yarn storybook`.

Here is a preview of the Storybook application:

![Storybook-preview](https://cdn.hashnode.com/res/hashnode/image/upload/v1644612086127/W16wg95bD.png)



#### Making sense of the folder structure

`npx sb init` generates some files and folders let's take a look at them

![folder-storybook.png](https://cdn.hashnode.com/res/hashnode/image/upload/v1643541132428/J7Z_VWKxr.png)

##### The `stories` folder

Storybook is smart enough and detects that the project uses Typescript and React so it creates some example components and documentation pages inside the `stories` folder in `src`. You can go ahead and remove everything from this folder but I recommend taking a peek inside to explore a little.

- `Introduction.stories.mdx` contains the documentation used to generate the Introduction page in the storybook preview above. Files with `.mdx` extension are written using MDX which is an amalgamation of JSX and Markdown. It helps to write component stories alongside their documentation at the same place.

- All files like `<ComponentName>.tsx` are the react components created with typescript and files like `<ComponentName>.stories.tsx` is used to preview the stories in the storybook and helps us to develop component in isolation

##### The `.storybook` folder

Contains files for customizing Storybook:

- `main.js` defines the file pattern used by Storybook to determine what to include in the showcase application. By default, Storybook uses files containing .stories in their name.

```js
"stories": [
   "../src/**/*.stories.mdx",
   "../src/**/*.stories.@(js|jsx|ts|tsx)"
 ]
```

 [***Addons***](https://storybook.js.org/addons/) for the Storybook application are also defined in `main.js.`

```js
"addons": [
    "@storybook/addon-links",
    "@storybook/addon-essentials"
  ]
```


- `preview.js` configures how actions and controls will show up depending on the prop's name. By default, props such as onClick,  onSubmit that start with `on` are automatically interpreted by Storybook as actions, so when triggered, they get logged inside Storybook's Actions addon. Besides, props suffixed with background and color will show a color picker control, whereas props suffixed with Date display a date picker control.

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

```


##### The `package.json` file

`npx sb init` command adds all dev dependencies needed by the storybook and it also adds the following scripts in the `package.json` file

```json

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

```
- `npm run storybook` starts a development server locally on your machine
- `npm build-storybook` builds the static storybook application ready to be deployed

### Adding SASS support 

Storybook uses webpack to bundle all the code when we run `storybook` or `build-storybook` script.
By default, Storybook does not come with sass support. In order to add sass to our library, we need to add some packages and extend the webpack config in `.stoybook/main.js`

Add the following dev dependencies for adding sass support

```bash
npm i -D sass style-loader css-loader sass-loader@10.2.0
```

Let's understand these dependencies

- `sass` is a pure JavaScript implementation of Sass.
- `style-loader` inject CSS into the DOM.
- `css-loader` interprets @import and url() like import/require() and will resolve them.
- `sass-loader` loads a Sass/SCSS file and compiles it to CSS. We are using a specific version because the latest version of sass-loader has a conflicting peer dependency issue with webpack that is used by the storybook internally.

To extend the webpack configuration lets add the following code in `.storybook/main.js`

```js
const path = require("path");

```

```js
.....
addons: ["@storybook/addon-links", "@storybook/addon-essentials"],
framework: "@storybook/react",
webpackFinal: async (config, { configType }) => {
    // Make whatever fine-grained changes you need
    config.module.rules.push({
      test: /\.scss$/,
      use: ["style-loader", "css-loader", "sass-loader"],
      include: path.resolve(__dirname, "../"),
    });

    // Return the altered config
    return config;
  }
....
```

With Sass support added, we are ready to build our components.

## Creating our first component

### Creating a button component

You can create any sort of component that you need to, but for the sake of this post let's create a Button component that can be reused.

Inside the components folder, create a file named `Button.tsx` . 

First, we'll define the interface of the props that are required by the button.
We start by importing react inside our file and our button props extends the HTML button element as we might pass different props like `onClick` or `type` that are native to the HTML button element.


```js
import React from "react";

export interface ButtonProps extends React.HTMLAttributes<HTMLButtonElement> {
  children: React.ReactNode;
  variant: "primary" | "danger"; 
  shape?: "rounded";
}
```

We are getting the following props 
- `children` that will be rendered inside the button element.
- `variant` : the variant of button i.e. `primary` or `danger` in this case.
- we also have an optional `shape` prop.

Now Let's add our component

```js
export const Button: React.FC<ButtonProps> = ({
  children,
  variant,
  shape,
  ...props
}) => {
  const classNames = `btn btn-${variant} btn-${shape}`;
  return (
    <button className={classNames} {...props}>
      {children}
    </button>
  );
};

```

Here we are returning an HTML button element from our function and we'll use the `variant` and `shape` prop to create different classNames and add them to our `<button>` element and we can target those particular classes from our SCSS to give different styles to our component.
For example, `btn` is the base class and when we pass a `variant` then we'll have a class either `btn-primary`  or `btn-danger` depending on the variant passed, and we can add styling to different variants.

### Adding styles for Button component

Create a `global.scss` file inside the `scss` folder. You can choose a folder structure of your choice and put SCSS code in different files and import them in this `global.scss` file, but for the sake of simplicity let's add all our styles in the `global.scss` file.

```scss
/* base styling for our button */
.btn {
  padding: 0.6rem 1rem;
  background: transparent;
  border: 1px solid #1e1e1e;
  cursor: pointer;
}

/* styling for our variants */
.btn-primary {
  border: none;
  background-color: blue;
  color: white;
  &:hover {
    background-color: blue;
  }
}
.btn-danger {
  border: none;
  background-color: red;
  color: white;
  &:hover {
    background-color: red;
  }
}

/* styling for different shape*/
.btn-rounded {
  border-radius: 0.4rem;
}
```

Either the button will have a `btn-primary` class or a `btn-danger` along with other classes and the respective styles will take effect.

Now we have created our button and added some styles let's use this button and see how it looks in our storybook.


### Creating the Button story in storybook

Before creating a story for our button let's import our SCSS so that it can be used. Open the `preview.js` file inside the `.storybook` folder and import the `global.scss` file inside it.

![global.scss-import-in-preview.js](https://cdn.hashnode.com/res/hashnode/image/upload/v1644611592660/A7PC5VRUg.png)

With our styles imported into the storybook let's create the Button Story.
Open the `stories` folder and delete all the files that were generated automatically by the storybook for us inside this folder  and create a `Button.stories.tsx` file  and write the following code

```ts
import React from "react";
import { Story } from "@storybook/react";
import { Button, ButtonProps } from "../components/Button";


export default {
  title: "Button",
  component: Button,
};

const Template: Story<ButtonProps> = args => <Button {...args} />;

export const Primary = Template.bind({});
Primary.args = {
  children: "Primary",
  variant: "primary",
};

export const Danger = Template.bind({});
Danger.args = {
  children: "Danger",
  variant: "danger",
  shape: "rounded",
};

```

Let's understand this code

We import our `Button` and `ButtonProps` from the `Button.stories.tsx` file and we start by exporting a default object that contains some metadata about this story like `title` and `component`. The `title` is the Actual Title of this story and inside the component, we have our `Button` component.

Then we create a Template function that takes some arguments and returns our button component. 

```ts
const Template: Story<ButtonProps> = args => <Button {...args} />;
```

Now call `Template.bind({})` for sort of creating a clone of our button template. This `Template.bind({})` will return a function that we can store in a variable and export it. You can read more about `.bind()` [*here*](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function/bind)


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

Now set some *args* for our Primary button

```ts
Primary.args = {
  children: "Primary",
  variant: "primary",
};
```

Now run the `npm run storybook` command to start a development server and you'll see the button components


![BTN preview.png](https://cdn.hashnode.com/res/hashnode/image/upload/v1644611993460/hC6VEXxZ4.png)

In the storybook's dev server, we have a control section that can be used to change props and see changes immediately or we can write more versions or variants of our button using `Template.bind({})`.

In this way, we can develop and test multiple components in isolation using the storybook.

## Bundling using Rollup

Rollup is a good bundling tool, if we want to package the React component library and reuse it in other projects.

Rollup needs an entry point to generate the bundle. We have already created an `index.ts` file in the `src` folder which will serve as our entry point for Rollup.

Add the exports of the components in this `index.ts` file which will be used by others and also import the `global.scss` file here so we can create CSS bundle.


![scss-and-button-import-in-index.ts](https://cdn.hashnode.com/res/hashnode/image/upload/v1644613759405/ytVaIvy-1.png)

In order to build our library lets add the following dev dependencies.

```bash
npm i -D rollup @rollup/plugin-babel rollup-plugin-peer-deps-external rollup-plugin-scss rollup-plugin-terser @babel/preset-react @rollup/plugin-node-resolve @rollup/plugin-typescript 

```


Let's understand these dependencies:

- `rollup` gives the command-line interface (CLI) to bundle the library.
- `@rollup/plugin-babel` allows us seamless integration between Rollup and existing Babel.
- `rollup-plugin-peer-deps-external` prevents adding peer dependencies to the bundle because the consumer of the library is expected to have them. So we also get a smaller bundle size.
- `rollup-plugin-scss` bundles scss files.
- `rollup-plugin-terser` minify generated es bundle.
- `@babel/preset-react` adds support for JSX.
- `@rollup/plugin-node-resolve` helps resolve third-party modules in case you are using any third-party dependencies. If you use any third-party dependency it is going to resolve them and add them to the source code.
- `@rollup/plugin-typescript` transpiles TypeScript files to JavaScript.

Next, we create a `rollup.config.js` file and add the following contents.

```js
import { babel } from "@rollup/plugin-babel";
import external from "rollup-plugin-peer-deps-external";
import resolve from "@rollup/plugin-node-resolve";
import scss from "rollup-plugin-scss";
import typescript from "@rollup/plugin-typescript";
import { terser } from "rollup-plugin-terser";

export default [
  {
    input: "./src/index.ts",
    output: [
      {
        file: "dist/index.js",
        format: "cjs",
      },
      {
        file: "dist/index.es.js",
        format: "es",
        exports: "named",
      },
    ],
    plugins: [
      scss({
        output: true,
        failOnError: true,
        outputStyle: "compressed",
      }),
      babel({
        exclude: "node_modules/**",
        presets: ["@babel/preset-react"],
      }),
      external(),
      resolve(),
      typescript(),
      terser(),
    ],
  },
];


```



Next, we need to update package.json. Libraries should be distributed using CommonJS and ES6. We specify the output file paths using `main` and `module` properties. We also use these properties in the Rollup configuration file.

Then we add a `build` script that uses rollup CLI with `-c` flag. This means that Rollup will look for a configuration file named rollup.config.js to bundle the component library.


```json
...
 "main": "dist/index.js",
 "module": "dist/index.es.js",
...
"scripts": {
    ...
    "build": "rollup -c", 
  }
...
```

Now if you run the `npm run build` command it will bundle our library and create a dist folder in the root of your project directory that could be deployed to npm.

## Final Words

We have created a react component library from scratch using typescript. We installed the storybook for developing UI components in isolation and configured it to add SASS support and finally bundled everything together with Rollup.  

I decided to write this article after I build a UI library for myself as a side project, if you are interested you can check it out [***here***](https://artemisui.netlify.app/).

I hope you found this interesting and learned something. Thank you.

> Per Aspera Ad Astra