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-libraryhas 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-libraryit usescreate-react-appversion 3.4,reactversion 16, the packages are all outdated. I remember updating all libraries for my library here. - 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.
 - Please read the best practices for packaging libraries guide here.
 - Read more about publishing library here.
 - You can check my tutorial series here.
 - All the code for this tutorial can be found here.
 - All the code for my 
chakra-ui-clonecan be found here. - You can check the deployed 
storybookfor mychakra-ui-clonehere. 
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 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.tsfile. - 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 -
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 -
{
  "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 -
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 amasterbranch and adevbranch. - 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 
mastertodev, check the github docs here. - We will merge all our feature and fix branches in the 
devbranch, test all our features on thedev deployment. - When we want to ship these features to our users; we will raise a pull request with 
masteras our base branch and finally merge all our code in the mainmasterbranch. This is what many might call arelease pull request. - In this process we can also cherry pick commits, say you merged a feature into 
devand want to test it further, we can skip this commit when we raise therelease 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 
masterbranch. - Second our 
devbranch 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 :).
 
git checkout -b dev
git push origin dev
Step Two: Installing all the necessary dependencies
From your terminal run the following -
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 -
yarn add -D vite typescript @rollup/plugin-typescript tslib @types/node 
- 
viteis our build tool. - 
@rollup/plugin-typescriptis used for generating typescript.d.tsfiles 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-systemunderpeerDependenciesin 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.jsonfile - 
"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 
srcunder it create a new fileindex.ts. - Now from the root of our project create a file 
tsconfig.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 -
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 
distfolder. - Under the 
rollupOptionswe first pass an array to theexternalkey which will not bundle our peer dependencies in our build. - We finally use the 
@rollup/plugin-typescriptthat will generate our typescript types. - In the 
formatsarray we specify our targets,cjs - common js,es - esm.umdtarget is used when our library will also be used in thescripttag. For theumdformat we have to mention theglobalsfor theexternal dependencies. I don't think we needumdfor our library here. But just wanted to highlight its usage . 
In our package.json file add a scripts section -
"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 -
"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 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 -
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 -
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 -
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.npmrcand test them. - First, I would recommend you to please read this awesome tutorial on publishing here. We have already pushed our code to Github, next we need to add the 
publishConfigkey to thepackage.json. 
 "publishConfig": {
    "registry": "https://npm.pkg.github.com/github-username"
  }
- Next step we now have to create a 
.npmrcfile. I had to create this file locally in my project and add it to.gitignoredue 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 - 
- Click on generate token and copy the token and save it somewhere. Now Paste the following to your 
.npmrcfile - 
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 -
yarn install --frozen-lockfile
npm version minor
yarn build
npm publish
- First we install our dependencies.
 - Then we run 
npm versionto version our package. There are 3 options that we can givemajor | minor | prerelease. When we givemajorit bumps up our version say from0.1.0 -> 1.0.0. If we giveminorit bumps our version from0.1.0 -> 0.2.0. - With the 
prereleaseflag we can releasealpha & betaversions of our package like so - 
# 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 publishif we tag our project usingprereleaseflag we should usenpm publish --tag alphaornpm publish --tag beta.Also, I would like to draw your attention to this library called changeset. 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.
Step Seven: Test our published package
- Let us now create a new vite project and import our package, if you are using a local 
.npmrcmake sure you copy it in this new project only then you can install our package - 
npm create vite@latest react-demo-vite
cd react-demo-vite && npm install
npm install @yaldram/react-vite-lib
- In the 
main.tsxfile paste the following - 
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 devand test ourBoxcomponent.Similarly, lets create another react project this time with create-react-app
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 -
 
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 -
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 -
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 -
npm install @yaldram/react-vite-lib@latest
Under the index.tsx/main.tsx paste the following code -
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 -
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 -
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 -
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 -
yarn storybook 
Step Ten: Setup eslint
From your terminal run and complete the questionnaire -
yarn add -D eslint eslint-config-prettier eslint-plugin-prettier prettier
npx eslint --init

In your root create a file .prettierrc -
{
  "trailingComma": "es5",
  "tabWidth": 2,
  "semi": true,
  "singleQuote": true
}
Similarly create a file eslintrc.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
 "lint": "eslint . --color",
 "lint:fix": "eslint . --fix",
 "test:lint": "eslint .",
 "pretty": "prettier . --write",
Step Eleven: Setup Husky hooks
From your terminal run the following -
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 -
npx husky add .husky/pre-commit "npx nano-staged"
Add the following to the package.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 -
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 -
"config": {
   "commitizen": {
      "path": "git-cz"
   }
 }
Add the following under the scripts section in package.json -
 "commit": "git-cz"
Now under the root of our project add a file called changelog.config.js and paste the following -
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 -
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. 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.
              
    
Top comments (0)