How to create a react component library with TypeScript, rollup.js and Storybook
Why would I want to set up a component library on my own? Well, we all have been to this point ... once our application grows, things tend to get complex and overwhelming. Despite the best intentions components start to have inconsistencies. Things get hard to maintain, and development speed decreases...
"Why would I want to set up a component library on my own?". Well, we all have been to this point: Once our application grows, things tend to get complex and overwhelming. Despite the best intentions components start to have inconsistencies. Things get hard to maintain, and development speed decreases… wouldn't it be nice to have a component friendly development environment that supports you to write consistent code and enables you to share the same components over and over in multiple projects?
This short guide will show you how to create your own bulletproof (aka decoupled) component library and tackle these challenges.
You will end up with …
-
… less redundant/duplicate code.
-
… consistent and robust components.
-
… a speed up in development time.
-
… a documentation/Styleguide.
-
… a better collaboration between front-end and design teams.
That's a lot of benefits. Let's start!
Prerequisites
Before reading, you should have some fundamental knowledge of the following technologies:
-
node.js and npm
-
Typescript
-
React
-
Storybook
-
rollup.js
At first initialize a new project:
npm init
Add Storybook
Once we created the project and initialized our repository we add Storybook. Storybook provides the possibility to browse and preview components and associated assets. It allows you to develop components in isolation.
npx -p @storybook/cli sb init --type react
This will install storybook and its dependencies and also creates the basic folder structure and example stories.
After installation the only thing we need to do is to run:
npm run storybook
If everything worked correctly you should be able to access storybook at: http://localhost:6006/
Add React and Typescript
Since we want to develop our components in Typescript we need to install React and Typescript:
npm i -D react @types/react typescript
We also need a configuration for our TypeScript compiler.
touch tsconfig.json
{
"compilerOptions": {
"target": "es5",
"module": "esnext",
"lib": ["dom", "dom.iterable", "es6"],
"allowJs": true,
"skipLibCheck": true,
"esModuleInterop": true,
"allowSyntheticDefaultImports": true,
"strict": true,
"forceConsistentCasingInFileNames": true,
"moduleResolution": "node",
"resolveJsonModule": true,
"isolatedModules": true,
"noEmit": true,
"jsx": "react-jsx",
"declaration": true,
"outDir": "./dist"
},
"include": ["src"]
}
Create first component
Let's create our first component. Create a folder and place the component into it:
mkdir src/Button touch src/Button/Button.tsx
import React from "react";
export interface ButtonProps {
/**
* Is this the principal call to action on the page?
*/
primary?: boolean;
/**
* What background color to use
*/
backgroundColor?: string;
/**
* How large should the button be?
*/
size?: "small" | "medium" | "large";
/**
* Button contents
*/
label: string;
/**
* Optional click handler
*/
onClick?: () => void;
}
/**
* Primary UI component for user interaction
*/
export const Button: React.FC<ButtonProps> = ({
primary = false,
size = "medium",
backgroundColor,
label,
...props
}) => {
const mode = primary
? "storybook-button--primary"
: "storybook-button--secondary";
return (
<button
type="button"
className={["storybook-button", `storybook-button--${size}`, mode].join(
" "
)}
style={{ backgroundColor }}
{...props}
>
{label}
</button>
);
};
Create a Storybook story for the component:
touch src/Button/Button.stories.tsx
import React from "react";
import { Story, Meta } from "@storybook/react/types-6-0";
import { Button, ButtonProps } from "./Button";
export default {
title: "Example/Button",
component: Button,
argTypes: {
backgroundColor: { control: "color" },
},
} as Meta;
const Template: Story<ButtonProps> = (args) => <Button {...args} />;
export const Primary = Template.bind({});
Primary.args = {
primary: true,
label: "Button",
};
export const Secondary = Template.bind({});
Secondary.args = {
label: "Button",
};
export const Large = Template.bind({});
Large.args = {
size: "large",
label: "Button",
};
export const Small = Template.bind({});
Small.args = {
size: "small",
label: "Button",
};
Create a stylesheet for the component:
touch src/Button/Button.css
.storybook-button {
font-family: "Nunito Sans", "Helvetica Neue", Helvetica, Arial, sans-serif;
font-weight: 700;
border: 0;
border-radius: 3em;
cursor: pointer;
display: inline-block;
line-height: 1;
}
.storybook-button--primary {
color: white;
background-color: #1ea7fd;
}
.storybook-button--secondary {
color: #333;
background-color: transparent;
box-shadow: rgba(0, 0, 0, 0.15) 0px 0px 0px 1px inset;
}
.storybook-button--small {
font-size: 12px;
padding: 10px 16px;
}
.storybook-button--medium {
font-size: 14px;
padding: 11px 20px;
}
.storybook-button--large {
font-size: 16px;
padding: 12px 24px;
}
Import the stylesheet in the component:
import React from "react";
import "./Button.css";
// ... rest of the component
Now you can run storybook again and you should see the new button component.
npm run storybook
Add rollup.js
Now we need to add rollup.js to bundle our components for distribution. Let's install the necessary dependencies:
npm i -D rollup @rollup/plugin-node-resolve @rollup/plugin-commonjs @rollup/plugin-typescript rollup-plugin-postcss rollup-plugin-peer-deps-external rollup-plugin-terser
Create a rollup configuration:
touch rollup.config.js
import resolve from "@rollup/plugin-node-resolve";
import commonjs from "@rollup/plugin-commonjs";
import typescript from "@rollup/plugin-typescript";
import postcss from "rollup-plugin-postcss";
import peerDepsExternal from "rollup-plugin-peer-deps-external";
import { terser } from "rollup-plugin-terser";
const packageJson = require("./package.json");
export default {
input: "src/index.ts",
output: [
{
file: packageJson.main,
format: "cjs",
sourcemap: true,
},
{
file: packageJson.module,
format: "esm",
sourcemap: true,
},
],
plugins: [
peerDepsExternal(),
resolve(),
commonjs(),
typescript({ tsconfig: "./tsconfig.json" }),
postcss({
extract: true,
minimize: true,
}),
terser(),
],
};
Create the main entry point:
touch src/index.ts
export { Button } from "./Button/Button";
Update your package.json to include the build information:
{
"name": "your-component-library",
"version": "1.0.0",
"description": "A React component library",
"main": "dist/index.js",
"module": "dist/index.esm.js",
"types": "dist/index.d.ts",
"files": ["dist"],
"peerDependencies": {
"react": ">=16.8.0",
"react-dom": ">=16.8.0"
},
"scripts": {
"build": "rollup -c",
"storybook": "start-storybook -p 6006",
"build-storybook": "build-storybook"
}
}
Now you can build your library:
npm run build
Publishing
You can now publish your component library to npm:
npm publish
Or use it in other projects by installing it from npm or linking it locally during development:
npm link
Conclusion
You now have a fully functional component library with TypeScript, Storybook for documentation and development, and rollup.js for bundling. This setup provides:
- Type safety with TypeScript
- Component documentation and testing with Storybook
- Optimized bundles with rollup.js
- Easy publishing to npm
This foundation allows you to build consistent, reusable components that can be shared across multiple projects, improving development speed and maintaining design consistency.