Intro
Building component libraries is a great side project for any front-end developer who is looking to get more practice with react, typescript, deployment, or enterprise tools (chromatic, storybook, rollup). On top of the skills you learn, you also come out of the project with plug-and-play components that you can leverage in future projects.
This guide will take you step by step through the process of creating a typescript library, publishing it to NPM, and then deploying the storybook to Chromatic so you can share your component library with others.
Step 1: Create a Git repository for your project
Before we even get into our text editor, make sure to create a remote repo for your library to live in.
- Log in to your Github account
- Click the green button that says “New”
- Name the repository
I named mine y2k-component-library
- Set the repository to Public
- Initialize the local directory where your Library lives with
git remote add origin https://github.com/username/REPO_NAME
Step 2: Configuring the workspace
Now that we have source control configured, it’s time to start defining the settings that dictate how our project will behave.
- Run
npm initto initialize your directory as a package - Create a .gitignore file and add
node_modules - Add dev dependencies:
npm i -D react react-dom typescript
- Create a
tsconfig.jsonfile to define typescript behavior in our project
{
"compilerOptions": {
"target": "ES2016",
"esModuleInterop": true,
"forceConsistentCasingInFileNames": true,
"strict": true,
"skipLibCheck": true,
"jsx": "react",
"module": "ESNext",
"sourceMap": true,
"outDir": "dist",
"moduleResolution": "node",
"allowSyntheticDefaultImports": true,
"types": [
"aria-query"
]
}
}
This is what my
tsconfiglooked like for my project. If you wish to customize any of the settings, feel free to tweak as needed.
- Define file structure for components
└── src/
├── components/
│ ├── component-1/
│ │ ├── component-1.css
│ │ ├── component-1.tsx
│ │ └── index.ts
│ ├── component-2/
│ │ ├── component-2.css
│ │ ├── component-2.tsx
│ │ └── index.ts
│ └── index.ts
└── index.ts
There are a lot of different ways to do this, so take my template as a suggestion and not a hard rule. We include numerous
index.tsfiles within our project to allow users of our library to have an easier time importing the components they need.
Each component’s
index.tsneeds to includeexport {default} from ‘’./component-1
The
index.tsin the components directory should include an export for each of the components you create. Mine looks like this:
export { default as Button } from "./Button";
export { default as Image } from "./Image";
export { default as List } from "./List";
export { default as Nav } from "./Nav";
export { default as Section } from "./Section";
export { default as Snippet } from "./Snippet";
export {default as NavItem} from './NavItem'
And FINALLY, the
index.tsin the src directory needs just 1 line:
export * from './components'
- Now that we have our folders organized, we initialize Storybook with
npx storybook init
7. Once Storybook has initialized, it should have created a .storybook directory, open the main.js file in there and make sure it looks like this:
"stories": [
"../src/**/*.stories.mdx",
"../src/**/*.stories.@(js|jsx|ts|tsx)"
],
"addons": [
"@storybook/addon-links",
"@storybook/addon-essentials",
"@storybook/addon-interactions"
],
"framework": "@storybook/react"
}
By defining these paths, we can now add a
.storiesto each of our component folders and Storybook will automatically find them and compile them with the commandnpm run storybook
- Run
npm run storybookfrom your terminal to see the example stories from initialization.
NOTE: you may run into an error that says “0308010C:digital envelope routines::unsupported”
To solve this, create a
.npmrcfile and paste this in there: legacy-peer-deps=trueThen, update the
storybookscript in package.json to look like this
"storybook": "export NODE_OPTIONS=--openssl-legacy-provider; start-storybook -p 6006",
Step 2: Creating your first Story
We now have the foundation of our project created, now we can move to creating stories for the components you created.
NOTE: This guide assumes you made components yourself between Step 2 and Step 3.
- Within one of your component directories, create a stories file based on the name of your component. I have a component called “Image” so I named the associated story file
Image.stories.tsx - Make the necessary imports
import React from "react";
import { ComponentStory, ComponentMeta } from "@storybook/react";
import Image from "./Image";
- Now we get to define how our component will behave in Storybook
export default {
title: 'Image', //What to name Story
component: Image, //Component we imported
argTypes: {
shape: {control: 'radio', options: ['DEFAULT', 'LEFT_CHEVRON', 'RIGHT_CHEVRON', 'PARALLELOGRAM']}
},
} as ComponentMeta<typeof Image>;
The argTypes object is what we use to customize the fields in Storybook that change the props! It is a super powerful tool from Storybook with a ton of different configurations. Visit the documentation to see all the different options. Right now, I am using a radio button to switch between the options for my
shapeprop.
- Define template for your component
const Template: ComponentStory<typeof Image> = (args) => <Image {...args}/>
- Now you can define examples based on the template, and show the different options in the Storybook!
export const RightChevron = Template.bind({});
RightChevron.args = {
src:'https://www.slashfilm.com/img/gallery/astro-boy-reboot-everything-we-know-so-far/l-intro-1657292510.jpg',
alt: 'astroboy',
shape: 'RIGHT_CHEVRON'
}
You can include any number of these exports in your Stories file and they will all appear under the Title we passed in at #3
Step 4: Getting ready for deployment
In order to make our components as light as possible for users, we are going to leverage rollup
- Install Rollup and necessary plugins
npm i -D rollup rollup-plugin-typescript2 rollup-plugin-peer-deps-external rollup-plugin-cleaner @rollup/plugin-commonjs @rollup/plugin-node-resolve
- Create a
rollup.config.mjsfile - Fill the file with configuration preferences. Assuming you have the same setup as mine, it should look something like this:
import typescript from 'rollup-plugin-typescript2';
import peerDepsExternal from 'rollup-plugin-peer-deps-external';
import cleaner from 'rollup-plugin-cleaner';
import commonjs from '@rollup/plugin-commonjs';
import resolve from '@rollup/plugin-node-resolve';
import packageJson from "./package.json" assert { type: "json" };
import css from "rollup-plugin-import-css";
export default {
input: 'src/index.ts',
output: [
{
file: packageJson.main,
format: 'cjs',
sourcemap: true,
},
{
file: packageJson.module,
format: 'esm',
sourcemap: true,
},
],
plugins: [
cleaner({
targets: ['./lib'],
}),
peerDepsExternal(),
resolve(),
commonjs(),
typescript({
exclude: ['**/*.stories.tsx', '**/*.test.tsx'],
}),
css(),
],
};
- Update package.json for deployment: we need to add
main,module, andfilesfields, as well as a script to run the Rollup process.
"main": "lib/index.js",
"module": "lib/index.esm.js",
"scripts": {
...
"rollup": "rollup -c",
}
"files": [
"lib"
],
- Now run
rpm run rollupand it should create a file calledlibfilled with some javascript and CSS files. NPM knows to use these generated files for our library because we declared “lib” in our files inpackage.json
Step 5: Deployment
We’re at the final leg of our journey: sharing our hard work with the world. First we will walk through publishing our package to NPM. Then set up our Storybook to be hosted on Chromatic
- Sign in (or register) @ npmjs.com
- Sign in to NPM from your terminal within your project with
npm login - Run
npm publish —access=public
This is all you need to publish your library! Now you should be able to see your package in your account on npmjs.com
Now lets get this project hosted on Chromatic
- Create static storybook build of your project with
npm run storybook-build
There may be a few issues with this command, similar to when we first tried to use
npm run storybookfor the first time. Change thestorybook-buildcommand to:
"build-storybook": "export NODE_OPTIONS=--openssl-legacy-provider; build-storybook",
In your package.json
- Install chromatic with
npm i -D chromatic - Log into Chromatic
- Select “Choose GitHub repo”
- Select the repo that houses your library
- Copy the unique project token
- Run
npx chromatic —project-token=YOUR_PROJECT_TOKEN - Once that has run it will generate a link for you in the terminal
- Click the link and follow the rest of the configuration steps
- You’re done!