React component library setup with vite, typescript, styled-components, styled-system and storybook
Tags: react, typescript, vite, tutorial
Published: 2022-09-23 14:11:45

### Introduction
Last year in September, I shared a series of blog posts, creating a react component library, using `typescript`, `styled-components`, `styled-system`. I created a `chakra-ui-clone`. I took a lot of inspiration from `chakra-ui` on the component design and functionality and went through the code on github and in the process learned a lot. I used `create-react-library` to scaffold my project. But if I were to create a new component library today, I would use other build tools like `vite` or `rollup`. I have updated my repository, it uses `vite` as the build tool and latest versions of `react`, `react-dom`, `typescript` and `storybook with integration testing`. In this tutorial let us bootstrap a react component library using `vite` as the build tool.

### Why use another build tool
- `create-react-library` has not been updated for 2 years and is just a bootstrapping tool not a build tool.
- When we create a new project using `create-react-library` it uses `create-react-app` version 3.4, `react` version 16, the packages are all outdated. I remember updating all libraries for my library [here](https://dev.to/yaldram/setup-a-component-library-using-react-typescript-and-create-react-library-5e48).
- Vite is a promising build tool and has been stable for almost a year now, it has a smaller footprint when it comes to installing dependencies and I saw a lot of improvements in the build time.

### Links
- Using Vite in Library Mode [here](https://vitejs.dev/guide/build.html#library-mode).
- Please read the best practices for packaging libraries guide [here](https://github.com/frehner/modern-guide-to-packaging-js-library).
- Read more about publishing library [here](https://dev.to/alexeagleson/how-to-create-and-publish-a-react-component-library-2oe#publishing-your-library).
- You can check my tutorial series [here](https://dev.to/yaldram/working-with-styled-system-using-typescript-4mkj).
- All the code for this tutorial can be found [here](https://github.com/yaldram/react-vite-lib).
- All the code for my `chakra-ui-clone` can be found [here](https://github.com/yaldram/chakra-ui-clone).
- You can check the deployed `storybook` for my `chakra-ui-clone` [here](http://yaldram-chakra-ui-clone.s3-website.ap-south-1.amazonaws.com/).

### Prerequisite
This tutorial is not recommended for a beginner, a good amount of familiarity with `react`, `styled-components`, `styled-system`, and `typescript` is expected. You can check my [introductory post](https://dev.to/yaldram/working-with-styled-system-using-typescript-4mkj) if you want to get familiar with the mentioned libraries. In this tutorial we will: -

- Initialize our project, create a GitHub repository. 
- Install all the necessary dependencies.
- Add a `vite.config.ts` file.
- Create our first component and build the project.
- Publish the project to private github package registery using npm.
- Setup Storybook.
- Setup eslint.
- Setup husky git hooks and commitizen.

### Step One: Bootstrap Project.
First create a github repository. Then we will create a new folder and initialize git. From your terminal run - 
```bash
mkdir react-vite-lib
cd react-vite-lib
git init
git remote add origin https://github.com/username/repo.git
```
Now we will run npm init and fill in the questionnaire, my `package.json` is as follows - 
```json
{
  "name": "@yaldram/react-vite-lib",
  "version": "0.0.1",
  "description": "A react library starter with styled components and vite as build tool.",
  "main": "index.js",
  "repository": {
    "type": "git",
    "url": "git+https://github.com/yaldram/react-vite-lib.git"
  },
  "keywords": [
    "react",
    "typescript",
    "vite"
  ],
  "author": "Arsalan Yaldram",
  "license": "ISC",
  "bugs": {
    "url": "https://github.com/yaldram/react-vite-lib/issues"
  },
  "homepage": "https://github.com/yaldram/react-vite-lib#readme"
}
```
Notice the name of the library; I have added my github username as prefix to the name this will be useful when we publish our library. Also notice the version is `0.0.1` as of now.

Let us now commit our code and push it to github - 
```bash
git add .
git commit -m "feat: ran npm init, package.json setup."
git push origin master
```

- Now let use create a new branch called `dev`, the reason for that is simple. I like to have a `master` branch and a `dev` branch. 
- Assume we have many developers working on various features they will create pull requests from the dev branch. For that make sure you have changed the default branch from `master` to `dev`, check the github docs [here](https://docs.github.com/en/repositories/configuring-branches-and-merges-in-your-repository/managing-branches-in-your-repository/changing-the-default-branch#changing-the-default-branch).
- We will merge all our feature and fix branches in the `dev` branch, test all our features on the `dev deployment`. 
- When we want to ship these features to our users; we will raise a pull request with `master` as our base branch and finally merge all our code in the main `master` branch. This is what many might call a `release pull request`.
- In this process we can also cherry pick commits, say you merged a feature into `dev` and want to test it further, we can skip this commit when we raise the `release pull request`. 
- This is how I used to do my backend projects on AWS ElasticBeanstalk and CodePipeline having 2 separate deployments. 
- One our main public facing deployment triggered when we merge code to `master` branch.
- Second our `dev` branch used by the Frontend and Q/A teams. We can test our new features here before shipping them.
- Please feel free to share your workflow, or any imporvements that I should make, would love to hear your thoughts :).
```bash
git checkout -b dev
git push origin dev
```

### Step Two: Installing all the necessary dependencies
From your terminal run the following - 
```bash
yarn add -D react react-dom styled-components styled-system @types/react @types/react-dom @types/styled-components @types/styled-system
```
Now let us install vite and build related dependencies -
```bash
yarn add -D vite typescript @rollup/plugin-typescript tslib @types/node 
```
- `vite` is our build tool.
- `@rollup/plugin-typescript` is used for generating typescript `.d.ts` files when we build the project.
**Also note we have installed all dependencies as dev dependencies.** 
- Now we will add `react, react-dom, styled-components & styled-system` under `peerDependencies` in our package.json. 
- The consumer of our library will need to install these dependencies in order to user our library, when we build our project, we won't be bundling/including these 4 dependencies in our build. Here is my `package.json` file -
```json
"devDependencies": {
    "@rollup/plugin-typescript": "^8.5.0",
    "@types/node": "^18.7.18",
    "@types/react": "^18.0.20",
    "@types/react-dom": "^18.0.6",
    "@types/styled-components": "^5.1.26",
    "@types/styled-system": "^5.1.15",
    "react": "^18.2.0",
    "react-dom": "^18.2.0",
    "styled-components": "^5.3.5",
    "styled-system": "^5.1.5",
    "tslib": "^2.4.0",
    "typescript": "^4.8.3",
    "vite": "^3.1.2"
  },
  "peerDependencies": {
    "react": "^18.2.0",
    "react-dom": "^18.2.0",
    "styled-components": "^5.3.5",
    "styled-system": "^5.1.5"
  }
```

In the root of our project create a new file `.gitignore` add -

```
node_modules

build
dist
storybook-static

.rpt2_cache
.DS_Store
.env
.env.local
.env.development.local
.env.test.local
.env.production.local
.npmrc

npm-debug.log*
yarn-debug.log*
yarn-error.log*
```

### Step Three: Setup vite
- Create a new folder in our project called `src` under it create a new file `index.ts`.
- Now from the root of our project create a file `tsconfig.json`-
```json
{
  "compilerOptions": {
    "outDir": "dist",
    "module": "esnext",
    "target": "ESNext",
    "lib": ["dom", "esnext"],
    "moduleResolution": "node",
    "jsx": "react",
    "sourceMap": true,
    "declaration": true,
    "esModuleInterop": true,
    "noImplicitReturns": true,
    "noImplicitThis": true,
    "noImplicitAny": true,
    "strictNullChecks": true,
    "suppressImplicitAnyIndexErrors": true,
    "noUnusedLocals": true,
    "noUnusedParameters": true,
    "allowSyntheticDefaultImports": true,
    "allowJs": true,
    "skipLibCheck": true,
    "strict": true,
    "forceConsistentCasingInFileNames": true,
    "resolveJsonModule": true,
    "isolatedModules": true,
    "noEmit": true
  },
  "include": ["src"],
  "exclude": ["node_modules", "dist", "**/*.stories.tsx", "**/*.test.tsx"]
}
```
In the root of the project create a file `vite.config.ts` - 
```typescript
import { defineConfig } from 'vite';
import typescript from '@rollup/plugin-typescript';
import { resolve } from 'path';

import { peerDependencies } from './package.json';

export default defineConfig({
  build: {
    outDir: 'dist',
    sourcemap: true,
    lib: {
      name: 'ReactViteLib',
      entry: resolve(__dirname, 'src/index.ts'),
      formats: ['es', 'cjs', 'umd'],
      fileName: 'index',
    },
  
    rollupOptions: {
      external: [...Object.keys(peerDependencies)],
      plugins: [typescript({ tsconfig: './tsconfig.json' })],
      output: {
        globals: {
          react: 'React',
          'react-dom': 'ReactDom',
          'styled-components': 'styled'
        }
      }
    },
  },
});

})
```
- The output of the build will be in the `dist` folder.
- Under the `rollupOptions` we first pass an array to the `external` key which will not bundle our peer dependencies in our build.
- We finally use the `@rollup/plugin-typescript` that will generate our typescript types.
- In the `formats` array we specify our targets, `cjs - common js`, `es - esm`. `umd` target is used when our library will also be used in the `script` tag. For the `umd` format we have to mention the `globals` for the `external dependencies`. I don't think we need `umd` for our library here. But just wanted to highlight its usage .

In our package.json file add a scripts section - 
```json
"scripts": {
   "build": "tsc && vite build"
},
```

### Step Four: Add package entries in the package.json file

Now in our package.json file we need to add the following - 
```json
"main": "./dist/index.js",
"module": "./dist/index.mjs",
"types": "./dist/index.d.ts",
"source": "src/index.ts",
"files": [
    "dist"
 ],
 "exports": {
   ".": {
     "import": "./dist/index.mjs",
     "require": "./dist/index.js"
   }
 },
```

In order to understand each and every entry above I would highly recommend you read this guide - https://github.com/frehner/modern-guide-to-packaging-js-library#packagejson-settings.

### Step Five: Create the Box Component
I would recommend you read my tutorials [here](https://dev.to/yaldram/working-with-styled-system-using-typescript-4mkj) where I build multiple components, following the atomic design methodology. For this tutorial we will add a simple component to our library.

Under `src` folder create a new file `provider.tsx` - 
```typescript
import * as React from "react";
import { ThemeProvider } from "styled-components";

export const ViteLibProvider: React.FC<{ children?: React.ReactNode }> = ({
  children,
}) => {
  return <ThemeProvider theme={{}}>{children}</ThemeProvider>;
};
```
Again under `src` folder create a new file `box.tsx` - 
```typescript
import styled from "styled-components";
import {
  compose,
  space,
  layout,
  typography,
  color,
  SpaceProps,
  LayoutProps,
  TypographyProps,
  ColorProps,
} from "styled-system";

export type BoxProps = SpaceProps &
  LayoutProps &
  TypographyProps &
  ColorProps &
  React.ComponentPropsWithoutRef<"div"> & {
    as?: React.ElementType;
  };

export const Box = styled.div<BoxProps>`
  box-sizing: border-box;
  ${compose(space, layout, typography, color)}
`;
```
Finally under `src/index.ts` paste the following - 
```typescript
export * from './provider'

export * from './box'
```
From your terminal now run `yarn build` and check the `dist` folder. You should see 3 files namely `index.js`(cjs), `index.mjs`(esm) and `index.umd.js`(umd).

### Step Six: Publishing our Library.
- Previously while working with `create-react-library`, I used to test my packages locally. Now I publish my packages to a private GitHub registry using a `.npmrc` and test them. 
- First, I would recommend you to please read this awesome tutorial on publishing [here](https://dev.to/alexeagleson/how-to-create-and-publish-a-react-component-library-2oe#publishing-your-library). We have already pushed our code to Github, next we need to add the `publishConfig` key to the `package.json`.
```json
 "publishConfig": {
    "registry": "https://npm.pkg.github.com/github-username"
  }
```
- Next step we now have to create a `.npmrc` file. I had to create this file locally in my project and add it to `.gitignore` due to some issue with my wsl setup. 
- On your github create a new Access token by navigating to `Settings -> Developer Settings -> Personal access tokens`, while creating the Access token make sure you check the following permission - 

![github-access-token](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/cpu7jzxfju5arn3ypqn4.png)
- Click on generate token and copy the token and save it somewhere. Now Paste the following to your `.npmrc` file - 
```
registry=https://registry.npmjs.org/
@GITHUB_USERNAME:registry=https://npm.pkg.github.com/
//npm.pkg.github.com/:_authToken=AUTH_TOKEN
```

To publish our package we run the following commands - 
```bash
yarn install --frozen-lockfile
npm version minor
yarn build
npm publish
```
- First we install our dependencies.
- Then we run `npm version` to version our package. There are 3 options that we can give `major | minor | prerelease`. When we give `major` it bumps up our version say from `0.1.0 -> 1.0.0`. If we give `minor` it bumps our version from `0.1.0 -> 0.2.0`.
- With the `prerelease` flag we can release `alpha & beta` versions of our package like so - 
```bash
# for an alpha version like 0.0.2-alpha.0
npm version prerelease -preid alpha
# for an alpha version like 0.0.2-beta.0
npm version prerelease -preid beta
```
- In the next command we build our project.
- Finally, we publish our project, using `npm publish` if we tag our project using `prerelease` flag we should use `npm publish --tag alpha` or `npm publish --tag beta`.

- Also, I would like to draw your attention to this library called [changeset](https://github.com/changesets/changesets). It's very easyfor  publishing and the best part is that you can use it in a GitHub action and automate the publishing tasks, release notes, version upgrades etc.

Special thanks to my colleague **Albin** for helping me understand the npm version command and its working, also suggesting the `changeset` package. Please check the npm versioning docs [here](https://docs.npmjs.com/cli/v8/commands/npm-version).

### Step Seven: Test our published package
- Let us now create a new vite project and import our package, if you are using a local `.npmrc` make sure you copy it in this new project only then you can install our package -
```bash
npm create vite@latest react-demo-vite
cd react-demo-vite && npm install
npm install @yaldram/react-vite-lib
```
- In the `main.tsx` file paste the following - 
```typescript
import React from "react";
import ReactDOM from "react-dom/client";

import { ViteLibProvider, Box } from "@yaldram/react-vite-lib";

ReactDOM.createRoot(document.getElementById("root") as HTMLElement).render(
  <React.StrictMode>
    <ViteLibProvider>
      <Box color="white" p="1rem" m="2rem" bg="green">
        Hello my awesome package
      </Box>
    </ViteLibProvider>
  </React.StrictMode>
);
```
- From the terminal run `npm run dev` and test our `Box` component.

- Similarly, lets create another react project this time with create-react-app 
```bash
npx create-react-app react-demo-cra --template typescript
cd react-demo-cra
yarn add @yaldram/react-vite-lib
```
- In the index.tsx file paste the following - 
```typescript
import React from "react";
import ReactDOM from "react-dom/client";

import { ViteLibProvider, Box } from "@yaldram/react-vite-lib";

ReactDOM.createRoot(document.getElementById("root") as HTMLElement).render(
  <React.StrictMode>
    <ViteLibProvider>
      <Box color="white" p="1rem" m="2rem" bg="green">
        Hello my awesome package
      </Box>
    </ViteLibProvider>
  </React.StrictMode>
);
```
From the terminal run `npm run start` and test our `Box` component.

### Step Eight: Add Flex component
Let us now add a new component to our package, and then we will publish it. Under `src` create a new file `flex.tsx` and paste - 
```typescript
import * as React from 'react';
import styled from 'styled-components';
import { system, FlexboxProps } from 'styled-system';

import { Box, BoxProps } from './box';

type FlexOmitted = 'display';

type FlexOptions = {
  direction?: FlexboxProps['flexDirection'];
  align?: FlexboxProps['alignItems'];
  justify?: FlexboxProps['justifyContent'];
  wrap?: FlexboxProps['flexWrap'];
};

type BaseFlexProps = FlexOptions & BoxProps;

const BaseFlex = styled(Box)<BaseFlexProps>`
  display: flex;
  ${system({
    direction: {
      property: 'flexDirection',
    },
    align: {
      property: 'alignItems',
    },
    justify: {
      property: 'justifyContent',
    },
    wrap: {
      property: 'flexWrap',
    },
  })}
`;

export type FlexProps = Omit<BaseFlexProps, FlexOmitted>;

export const Flex = React.forwardRef<HTMLDivElement, FlexProps>(
  (props, ref) => {
    const { direction = 'row', children, ...delegated } = props;

    return (
      <BaseFlex ref={ref} direction={direction} {...delegated}>
        {children}
      </BaseFlex>
    );
  }
);

Flex.displayName = 'Flex';
```
Let us first commit these changes, after that lets publish a new version of our package, from your terminal - 
```bash
yarn install --frozen-lockfile
npm version minor
yarn build
npm publish
```
Check whether the package is published, by visiting you github pages under the `package` tab.

Now from the vite or cra demo apps, run - 
```bash
npm install @yaldram/react-vite-lib@latest
```
Under the `index.tsx/main.tsx` paste the following code - 
```typescript
import React from "react";
import ReactDOM from "react-dom/client";

import { ViteLibProvider, Box, Flex } from "@yaldram/react-vite-lib";

ReactDOM.createRoot(document.getElementById("root") as HTMLElement).render(
  <React.StrictMode>
    <ViteLibProvider>
      <Flex h="80vh" color="white">
        <Box size="100px" p="md" bg="red">
          Box 1
        </Box>

        <Box size="100px" p="md" bg="green">
          Box 2
        </Box>
      </Flex>
    </ViteLibProvider>
  </React.StrictMode>
);
```
Run `npm run start / npm run dev` from the terminal to check if the Flex component works as expected.

### Step Nine: Setup Storybook
From your terminal run - 
```bash
npx storybook init
```
Storybook will automatically detect we are using vite and will use the vite bundler. Under the `.storybook` folder now rename `main.js` to `main.tsx` and `preview.js` to `preview.tsx` and under `preview.tsx` we will add our ThemeProvider as a decorator so that all our stories are wrapped by this Provider like so - 
```typescript
import React from "react";
import { Parameters } from "@storybook/react";

import { ViteLibProvider } from "../src/provider";

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

const withThemeProvider = (Story) => (
  <ViteLibProvider>
    <Story />
  </ViteLibProvider>
);

/**
 * This decorator is a global decorator will
 * be applied to each and every story
 */
export const decorators = [withThemeProvider];
```
Under `src` folder create a new story `box.stories.tsx` and paste the following - 
```typescript
import * as React from "react";
import { StoryObj } from "@storybook/react";

import { Box, BoxProps } from "./box";

export default {
  title: "Box",
};

export const Playground: StoryObj<BoxProps> = {
  parameters: {
    backgrounds: {
      default: "grey",
    },
  },
  args: {
    bg: "green",
    color: "white",
    p: "2rem",
  },
  argTypes: {
    bg: {
      name: "bg",
      type: { name: "string", required: false },
      description: "Background Color CSS Prop for the component",
      table: {
        type: { summary: "string" },
        defaultValue: { summary: "transparent" },
      },
    },
    color: {
      name: "color",
      type: { name: "string", required: false },
      description: "Color CSS Prop for the component",
      table: {
        type: { summary: "string" },
        defaultValue: { summary: "black" },
      },
    },
    p: {
      name: "p",
      type: { name: "string", required: false },
      description: `Padding CSS prop for the Component shothand for padding.
        We also have pt, pb, pl, pr.`,
      table: {
        type: { summary: "string" },
        defaultValue: { summary: "-" },
      },
    },
  },
  render: (args) => <Box {...args}>Hello</Box>,
};
```
Now from the terminal run -
```bash
yarn storybook 
```

### Step Ten: Setup eslint
From your terminal run and complete the questionnaire - 
```bash
yarn add -D eslint eslint-config-prettier eslint-plugin-prettier prettier
npx eslint --init
```
![eslint-questionnarie](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ilr8f2g89w2cqerwplfn.png)
In your root create a file `.prettierrc` - 
```json
{
  "trailingComma": "es5",
  "tabWidth": 2,
  "semi": true,
  "singleQuote": true
}
```
Similarly create a file `eslintrc.json` - 
```json
{
  "env": {
    "browser": true,
    "node": true,
    "es2021": true
  },
  "extends": [
    "eslint:recommended",
    "plugin:react/recommended",
    "plugin:@typescript-eslint/recommended"
  ],
  "overrides": [],
  "parser": "@typescript-eslint/parser",
  "parserOptions": {
    "ecmaVersion": "latest",
    "sourceType": "module"
  },
  "plugins": ["react", "@typescript-eslint", "prettier"],
  "rules": {},
  "settings": {
    "react": {
      "version": "detect"
    }
  }
}
```
Finally create 2 new files `.eslintignore` & `.prettierignore` - 
```
build/
dist/
node_modules/
storybook-static
.snapshots/
*.min.js
*.css
*.svg
```
Under the scripts section of the package.json file paste the following scripts
```json
 "lint": "eslint . --color",
 "lint:fix": "eslint . --fix",
 "test:lint": "eslint .",
 "pretty": "prettier . --write",
```

### Step Eleven: Setup Husky hooks
From your terminal run the following - 
```bash
yarn add -D husky nano-staged
npx husky install
```
`nano-staged` is lighter and more performant than `lint-staged`. Now from the terminal add a husky `pre-commit` hook - 
```bash
npx husky add .husky/pre-commit "npx nano-staged"
```
Add the following to the package.json - 
```json
 "nano-staged": {
    "*.{js,jsx,ts,tsx}": "prettier --write"
  }
```

### Step Twelve: Setup git-cz, commitizen (Optional)
I like to write git commits using git-cz and commitizen this step is optional you can skip it. Let us first install the necessary packages, run the following commands from your terminal -
```bash
yarn add -D git-cz commitizen
npx commitizen init cz-conventional-changelog --save-dev --save-exact
```
In our package.json replace the default commitizen configuration with the one below -
```json
"config": {
   "commitizen": {
      "path": "git-cz"
   }
 }
```
Add the following under the scripts section in package.json -
```json
 "commit": "git-cz"
```
Now under the root of our project add a file called `changelog.config.js` and paste the following -
```javascript
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',
    },
  },
};
```
Let us now test our husky hooks and commitizen setup - 
```bash
git add .
yarn commit
```
Now we will push our changes - 
```
git push origin dev
git checkout master
git merge dev
git push origin master
```

### Summary
There you go we have finally setup our library. All the code can be found [here](https://github.com/yaldram/react-vite-lib). In the next tutorial I would like to share my GitHub workflows for deploying my storybook / react spa to AWS S3.

Feel free to ask me any queries. Also, please leave your suggestion, let me know where I can improve and share your workflows. Your constructive feedback will be highly appreciated. Until next time PEACE.