Setup a component library using react, typescript and create-react-library.
Tags: react, typescript
Published: 2021-09-01 13:48:47

### Introduction

This post is part of the Chakra UI clone series where we are cloning some chakra ui components using `react`, `typeScript`, `styled-components` and `styled-system`.

While building React components, I often look for tutorials, best methods, etc. searching the web learning new things trying out new libraries. I often love to read the code of various libraries, packages on github. I always find myself visiting - [chakra-ui](https://chakra-ui.com/) , [react-rainbow](https://react-rainbow.io/), [welcome-ui](https://www.welcome-ui.com/), [radix-ui](https://www.radix-ui.com/) for inspiration and love reading their code on github (though I have not used these libraries beyond the basic stuff). A while back I cloned **some chakra ui elements**, using `styled-components`, `styled-system` and `typescript` and I would like to share my journey. You can check the [repo](https://github.com/yaldram/chakra-ui-clone), and try it out.

Chakra UI has this utility props for margin, padding, color, etc. that you can pass to components -

```tsx
<Box bg="tomato" w="100%" p="20px" color="white">
  This is the Box
</Box>
```

We will create our components with a similar API using `styled-components` and `styled-system`.

### Update 
I wrote this post back in August of 2021, if I were to start a react component library today, I would use modern build tools like `Vite`. In fact, I wrote another blog post on how to use `Vite` as our build tool you can check it here - https://dev.to/yaldram/react-component-library-setup-with-vite-typescript-styled-components-styled-system-and-storybook-55ad

### Prerequisite

This series of posts is not recommended for a beginner, a good amount of familiarity with `react`, `styled-components`, `styled-system`, and `typescript` is expected. For an introduction to using the above together check my [introductory post](https://dev.to/yaldram/working-with-styled-system-using-typescript-4mkj). In this tutorial we will setup up our repo -

- Bootstrap our project using `create-react-library`, meaning we are creating a component library that can be used in other projects and even can be published to npm.
- Setup eslint, upgrading packages.
- Setup Storybook.
- Setup Husky and git-cz for commits.

This tutorial covers a lot, but no worries all the code is available under the **library-setup** branch, check the [repo](https://github.com/yaldram/chakra-ui-clone).

### Step One: Create a React Project with TypeScript

Let us start by bootstrapping our project using `create-react-library`. In your terminal run

```bash
npx create-react-library chakra-ui-clone
```

After the above command executes, you go through a questionnaire, like package name, etc. Be sure to select typescript for template.
After the project has been bootstrapped you can see it comes pre-configured with eslint, prettier, react-testing-library, etc. Also the react version is not 17. We will change that, we will update eslint, use eslint airbnb style, update our react versions also we will setup storybook.

### Step Two: Setup Eslint

- First let us delete .eslintrc from the project and then in your terminal run -

```bash
npx eslint --init
```

- When running this command, you will need to answer some questions about the configuration:

  - How would you like to use ESLint?
  - Select: To check syntax, find problems, and enforce code style

  - What type of modules does your project use?
  - Select: JavaScript modules (import/export)

  - Which framework does your project use?
  - Select: React

  - Does your project use TypeScript?
  - Select: Yes

  - Where does your code run?
  - Select: Browser

  - How would you like to define a style for your project?
  - Select: Use a popular style guide

  - Which style guide do you want to follow?
  - Select: Airbnb: https://github.com/airbnb/javascript

  - What format do you want your config file to be in?
  - Select: JSON

  - After this, it will check the dependencies that need to be installed and then will ask:

  - Would you like to install them now with npm?
  - Select: Yes

- Install the following addition devDependencies -

```bash
npm install --save-dev eslint-import-resolver-typescript
```

- Now our eslint package has been updated to the latest version and necessary dependencies are installed successfully. Let us remove the eslint config standard packages -

```bash
npm uninstall eslint-config-standard eslint-config-standard-react  eslint-plugin-standard
```

- Now under the root of your project create a new file called .eslintrc.json and paste the following -

```json
{
  "env": {
    "node": true,
    "es6": true
  },
  "extends": [
    "plugin:react/recommended",
    "airbnb",
    "plugin:prettier/recommended",
    "prettier/standard",
    "prettier/react",
    "plugin:@typescript-eslint/eslint-recommended"
  ],
  "globals": {
    "Atomics": "readonly",
    "SharedArrayBuffer": "readonly"
  },
  "parser": "@typescript-eslint/parser",
  "settings": {
    "import/resolver": {
      "typescript": {}
    }
  },
  "parserOptions": {
    "ecmaFeatures": {
      "legacyDecorators": true,
      "jsx": true
    },
    "ecmaVersion": 2018,
    "sourceType": "module"
  },
  "plugins": ["react", "react-hooks", "@typescript-eslint"],
  "rules": {
    "space-before-function-paren": 0,
    "react/prop-types": 0,
    "react/jsx-handler-names": 0,
    "react/jsx-fragments": 0,
    "react/no-unused-prop-types": 0,
    "import/export": 0,
    "no-unused-vars": "off",
    "@typescript-eslint/no-unused-vars": "error",
    "react/jsx-props-no-spreading": "off",
    "import/prefer-default-export": 0,
    "react-hooks/rules-of-hooks": "error",
    "react-hooks/exhaustive-deps": "warn",
    "max-len": ["warn", { "code": 80 }],
    "no-use-before-define": "off",
    "@typescript-eslint/no-use-before-define": ["error"],
    "react/jsx-filename-extension": ["warn", { "extensions": [".tsx"] }],
    "import/extensions": [
      "error",
      "ignorePackages",
      {
        "js": "never",
        "jsx": "never",
        "ts": "never",
        "tsx": "never"
      }
    ],
    "import/no-extraneous-dependencies": [
      "error",
      {
        "devDependencies": ["**/*.test.ts", "**/*.test.tsx", "**/*.stories.tsx"]
      }
    ]
  }
}
```

- Under the .eslintignore file paste the following -

```
build/
dist/
node_modules/
.snapshots/
*.min.js
*.css
*.svg
```

### Step 3 - Some Cleanup

- First let us upgrade react and react dom to latest versions -

```bash
npm install react@latest react-dom@latest
```

- Update the types also -

```bash
  npm install @types/react@latest @types/react-dom@latest --save-dev
```

- Also let us remove some unnecessary files from our project, first we remove styles.module.css and index.test.tsx from the src folder.

- Next under the src/index.tsx file paste the following -

```tsx
import * as React from "react";

export function ExampleComponent() {
  return <div>Example Component</div>;
}
```

- Now run **npm run build**, this will build our library. Whenever we create a new component or update existing ones, we must run **npm run build** so that we can import and run our components in the `example` directory. You can also run **npm run start** which will build your library each time you save a file, basically running build in watch mode.

- From under the `example/src` folder, lets remove App.test.tsx, index.css, setupTests.ts.

- Let us import our ExampleComponent in our example react project to test it and see if it is working as expected. Under the `example/src/App.tsx` paste the following code -

```tsx
import * as React from "react";

import { ExampleComponent } from "chakra-ui-clone";

export function App() {
  return <ExampleComponent />;
}
```

- And under example/src/index.tsx paste the following code -

```tsx
import * as React from "react";
import ReactDOM from "react-dom";

import { App } from "./App";

ReactDOM.render(<App />, document.getElementById("root"));
```

- Now cd into the example folder and run `npm run start`, it will give some wired babel version mismatch error so under the example root folder (not example/src) create a .env file in it paste -

```
SKIP_PREFLIGHT_CHECK=true
```

- Now run `npm run start` again in the browser you should see the `Example Component` text in your browser.

### Step Four - Storybook Setup

- For storybook we will use the latest pre-released `csf format`. In your main directory i.e. chakra-ui-clone directory run -

```bash
npx -p @storybook/cli sb init --story-format=csf-ts
```

- We also need the following additional libraries -

```bash
npm install --save-dev @storybook/addon-a11y @storybook/addon-viewport storybook-readme
```

- Now we have a stories folder under the src folder, delete the stories folder.
- Under the root we also have .storybook folder with `main.js` & `preview.js` files.
- Under the main.js paste the following code -

```js
module.exports = {
  features: {
    previewCsfV3: true,
  },
  stories: ["../src/**/*.stories.mdx", "../src/**/*.stories.@(js|jsx|ts|tsx)"],
  addons: [
    "storybook-readme/register",
    "@storybook/addon-links",
    "@storybook/addon-essentials",
    "@storybook/addon-knobs",
    "@storybook/addon-a11y",
  ],
  typescript: {
    reactDocgen: "react-docgen-typescript",
  },
};
```

- Under the preview.js file paste the following code -

```js
import React from "react";
import { create } from "@storybook/theming";
import { addReadme } from "storybook-readme";

const basicTheme = create({
  base: "light",
  brandTitle: "Chakra UI Clone",
});

export const parameters = {
  options: {
    showPanel: true,
    panelPosition: "bottom",
    theme: basicTheme,
    storySort: {
      order: ["Atoms", "Molecules", "Organisms"],
      method: "alphabetical",
    },
  },
  readMe: {
    codeTheme: "duotone-sea",

    StoryPreview: ({ children }) => <div>{children}</div>,

    HeaderPreview: ({ children }) => <div>{children}</div>,

    FooterPreview: ({ children }) => <div>{children}</div>,

    DocPreview: ({ children }) => (
      <div style={{ padding: "1.6rem" }}>{children}</div>
    ),
  },
  actions: { argTypesRegex: "^on[A-Z].*" },
  knobs: {
    escapeHTML: false,
  },
  backgrounds: {
    default: "white",
    values: [
      { name: "white", value: "#fff" },
      { name: "dark", value: "#000" },
    ],
  },
};

export const decorators = [addReadme, (Story) => <Story />];
```

- Create one more file under the .storybook folder by the name `preview-body.html` for loading our fonts and add -

```html
<link rel="preconnect" href="https://fonts.gstatic.com" />
<link
  href="https://fonts.googleapis.com/css2?family=Montserrat:wght@300;400;500;600;900&display=swap"
  rel="stylesheet"
/>

<style>
  * {
    padding: 0;
    margin: 0;
  }

  body {
    font-family: Montserrat, sans-serif;
  }

  /*
    Styling issue for Docs -> Show Code
    resolved with the following styles
  */
  code[class*="language-"],
  pre[class*="language-"] {
    color: white;
    text-shadow: none;
  }
</style>
```

- Let us create a story, under the src folder, create a new file `index.stories.tsx` and add -

```tsx
import * as React from "react";

import { ExampleComponent } from ".";

export default {
  title: "ExampleComponent",
};

export const Default = {
  render: () => <ExampleComponent />,
};
```

- Let us check our component story in storybook. In your terminal run -

```bash
npm run storybook
```

### Step Six: Commitizen and Husky Setup

- In your terminal run -

```bash
npm install --save-dev git-cz commitizen
```

- Now run the following command -

```bash
 npx commitizen init cz-conventional-changelog --save-dev --save-exact
```

- Under the root of the project create a file by the name `changelog.config.js` in it paste the following code -

```js
/* eslint-disable prettier/prettier */
module.exports = {
  disableEmoji: false,
  list: [
    "test",
    "feat",
    "fix",
    "chore",
    "docs",
    "refactor",
    "style",
    "ci",
    "perf",
  ],
  maxMessageLength: 64,
  minMessageLength: 3,
  questions: [
    "type",
    "scope",
    "subject",
    "body",
    "breaking",
    "issues",
    "lerna",
  ],
  scopes: [],
  types: {
    chore: {
      description: "Build process or auxiliary tool changes",
      emoji: "🤖",
      value: "chore",
    },
    ci: {
      description: "CI related changes",
      emoji: "🎡",
      value: "ci",
    },
    docs: {
      description: "Documentation only changes",
      emoji: "✏️",
      value: "docs",
    },
    feat: {
      description: "A new feature",
      emoji: "🎸",
      value: "feat",
    },
    fix: {
      description: "A bug fix",
      emoji: "🐛",
      value: "fix",
    },
    perf: {
      description: "A code change that improves performance",
      emoji: "⚡️",
      value: "perf",
    },
    refactor: {
      description: "A code change that neither fixes a bug or adds a feature",
      emoji: "💡",
      value: "refactor",
    },
    release: {
      description: "Create a release commit",
      emoji: "🏹",
      value: "release",
    },
    style: {
      description: "Markup, white-space, formatting, missing semi-colons...",
      emoji: "💄",
      value: "style",
    },
    test: {
      description: "Adding missing tests",
      emoji: "💍",
      value: "test",
    },
  },
};
```

- Now install husky by running -

```bash
npm install husky --save-dev
```

- Activate husky hooks by running -

```bash
npx husky install
```

- Adding hooks to husky we do have a command but it did not work for me so what I did was to create a file called `pre-commit` under the `.husky` folder.

- Under `pre-commit` paste the following code -

```
#!/bin/sh
. "$(dirname "$0")/_/husky.sh"

npx lint-staged
```

- Now add the `lint-staged` entry in package.json file -

```json
"lint-staged": {
    "*.+(ts|tsx)": [
      "eslint --fix"
    ],
    "*.+(json|css|md)": [
      "prettier --write"
    ]
  }
```

- Add the following scripts under the scripts section in package.json -

```json
"commit": "git-cz",
"lint": "eslint . --color",
"lint:fix": "eslint . --fix",
"pretty": "prettier . --write",
```

- Let us test husky and commitlint first run the following in your terminal -

```bash
  git add .
  npm run commit
```

- The above `npm run commit` should open the git-cz questionnaire with the options and emojis mentioned under the `changelog.config.js`. Fill in the questions, after that is done husky should kick in and run the lint and pretty scripts mentioned under lint-staged in package.json

### Summary

There you go our component library setup is done. Please check the **library-setup** for all the code that we covered here [repo](https://github.com/yaldram/chakra-ui-clone). In the next tutorial we will setup the theme values, until next time PEACE.