Creating, and deploying, a component design-system for React with Storybook

André Luiz Batista Aureliano
7 min readJan 10, 2023

--

Hi there, I invite you to follow me while I’m documenting my process of creating a component design system for React using Storybook, orchestrating the CI with GitHub Actions, publishing the lib on NPM and deploying the Storybook on AWS S3 with a domain using Cloudflare to manage the DNS. And then use the lib in another React + Typescript project built with Vite.

List of techs/techniques/platforms:

  • React with Typescript: the components are written with TS in mind.
  • Storybook: for the documentation and visualization of the components.
  • GitHub: host the repository and use the GitHub Actions to manage the CI.
  • NPM: Publish the lib.
  • AWS S3: Host the built Storybook.
  • Cloudflare: To manage the DNS to point for a domain that is linked with the S3 bucket.

I won’t explain how to create a repository in detail because I assume you already know it at this point.

For this article I created my repo:

https://github.com/andreluizweb/andre-design-system

In your machine, clone the empty repo and initialize your project:

# Clone the repo
git clone https://github.com/andreluizweb/andre-design-system

# Go to the dir
cd andre-design-system

# Initialize a new NodeJS project
npm init -y

# Install these dependencies
npm i react react-dom @storybook/react del-cli

Set up the Storybook:

# Run the Storybook setup command
npx -p @storybook/cli sb init

This will create the necessary configuration files for Storybook and add scripts to the package.json file for starting and building Storybook.

Start to develop your components

The Storybook will create a lot of stories. You can delete all of them.

Now, let’s create a default Button:

# Create an src folder to add our components
mkdir src
cd src

And create a Button.tsx and index.ts files:

Button.tsx

import * as React from "react";

export const Button = (props: { children: any }) => {
return <button className="btn btn-primary">{props.children}</button>;
};

index.ts

export { Button } from "./Button";

Ok, now we need to create the story for this Button. But first, we need to organize the folders of the stories.

#Go to the stories folder and create the structure
cd ../stories
mkdir Intro
mkdir Components

cd Components

Inside “./stories/Components” create a Button.stories.tsx file:

import React from "react";
import { Meta, Story } from "@storybook/react";

import { Button } from "../../src";

export default {
title: "Components/Button",
component: Button,
} as Meta;

export const Default: Story = ({ ...args }) => {
return <Button {...args}>Button</Button>;
};

Now you can test your storybook:

npm run storybook

Nice!

We need to build our lib and our Storybook. For that change the package.json to create some scripts and configurations.

package.json

{
[rest of your package.json]...
"scripts": {
"build": "npm run clean && npm run build:cjs && npm run build:esm",
"build:cjs": "tsc",
"build:esm": "tsc -m es6 --outDir esm",
"clean": "del lib/* && del esm/*",
"storybook": "start-storybook -p 6006",
"storybook:build": "build-storybook -o design-system"
},
"main": "lib/index.js",
"types": "./lib",
"module": "esm/index.js",
"files": [
"esm",
"lib",
"src"
],
[rest of your package.json]...
}

And create the tsconfig.json on the root of your project:

{
"compilerOptions": {
"declaration": true,
"forceConsistentCasingInFileNames": true,
"jsx": "react",
"module": "commonjs",
"moduleResolution": "node",
"newLine": "lf",
"noEmitOnError": true,
"noImplicitAny": true,
"noFallthroughCasesInSwitch": true,
"noImplicitReturns": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"pretty": true,
"resolveJsonModule": true,
"sourceMap": true,
"strict": true,
"target": "esnext",
"outDir": "./lib",
"rootDirs": ["src", "stories"],
"skipLibCheck": true,
},
"include": ["src/**/*", "*.d.ts"]
}

Run the command to build it:

npm run build

Time to create the CI

In the root of your project create a file named “.github/workflows/publish.yml”:

name: Publish to npm

on:
push:
tags:
- "v*"
pull_request:
branches: ["*"]

jobs:
publish:
runs-on: ubuntu-latest

steps:
- name: Setup repo
uses: actions/checkout@v3

- name: Setup Node
uses: actions/setup-node@v3
with:
node-version: "16.x"
registry-url: "https://registry.npmjs.org"

- name: NPM CI
run: npm ci

- name: Install Packages
run: npm install

- name: Build
run: npm run build

- name: Build Storybook
run: npm run storybook:build

- name: Publish Package
run: npm publish
env:
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}

- name: Send Storybook to S3
if: startsWith(github.ref, 'refs/tags/')
uses: jakejarvis/s3-sync-action@master
with:
args: --follow-symlinks
env:
AWS_S3_BUCKET: "design-system.andreluizbatista.dev"
AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
SOURCE_DIR: "design-system"

This workflow will trigger only when a tag is created.

As you can see, there are some variables:

  • ${{ secrets.NPM_TOKEN }}
  • ${{ secrets.AWS_ACCESS_KEY_ID }}
  • ${{ secrets.AWS_SECRET_ACCESS_KEY }}

To define them you need to go to your GitHub repo on Settings > Secrets and Variables:

Here is how you can get each one of them:

NPM_TOKEN:

In your terminal type:

npm login
npm token create

The token will be generated and shown to you in the terminal.

AWS_SECRET_ACCESS_KEY and AWS_ACCESS_KEY_ID

  1. Log in to the AWS Management Console
  2. Go to the “IAM” service.
  3. In the IAM dashboard click on Users
  4. Create a new User, in my case, the name was “GitHub”
  5. Mark this user access type as “Access key — Programmatic access”
  6. Create a new Group and add the policies of “AmazonS3FullAccess”
  7. In the “Security Credentials” tab, click on the “Create access key” button.
  8. A dialog box will appear with your new access key and secret key. Click the “Download .csv” button to download the keys and save them to a secure location.
  9. Once you have downloaded the keys, you can use them to authenticate with the AWS API.

Creating the AWS S3 Bucket

Go to the AWS Console in the S3 dashboard and create a new bucket:

!!! It is very important to check if the name of the bucket is the same as the domain(or subdomain) that you want to deploy the Storybook. !!!

In my case is “design-system.andreluizbatista.dev”.

Uncheck the “Block all public access” and mark the above checkbox.

Create the bucket!

Now, we need to edit the permissions to work as a static hosting. click on the bucket and go to Properties:

Down on the page, you’ll see this box:

See this Bucket website endpoint? Save it for later.
Click on Edit.

Fill it like this and save it.

On the permissions Tab of the Bucket:

Fill the Bucket Policy with this:

{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "Statement1",
"Effect": "Allow",
"Principal": "*",
"Action": "s3:GetObject",
"Resource": "arn:aws:s3:::design-system.andreluizbatista.dev/*"
}
]
}

Pointing the DNS to the S3 Bucket

Here I am using Cloudflare to manage my DNS.

You need to create a CNAME record pointing to the Bucket Website Endpoint that you copied before. Like this:

Remember you named the bucket with the subdomain? That’s why.

We are almost ready to publish and deploy our lib. We did a lot of things and didn’t commit and push to the repo.

#Commit and Push the code
git add .
git commit -m "Initizaling the Design System"
git push

# Now, create a version
npm version patch
# You can use patch, minor or major. Depending on the changes of your code.
# this will create a tag like v0.0.1

# Push the tags
git push --tags

# This will trigger the CI we just uploaded.


# If the CI fails to publish the package, try to publish it one
# first time from your machine.

And the magic happens:

We made it!

PUBLISHED!
Storybook deployed!

WAIT!

There is a post-credits scene!

We need to use the lib in another project, right?

I used Vite to start a React + Typescript project:

cd ../
mkdir test-design-system
cd test-design-system

# Create the Vite project
npm create vite@latest . --template react-ts

# Install our brand new package andre-design-system
npm i andre-design-system

Now we can use the <Button /> component:

and on the screen:

THE END

If you see something I did wrong, or could do better, don’t hesitate to reach me, comment, yell… Thank you!

--

--

André Luiz Batista Aureliano
André Luiz Batista Aureliano

Written by André Luiz Batista Aureliano

Tech Lead Engineer @ InHire . Entrepeneur, Web/App Software Developer. “Hitting the head on the keyboard, until something goods happens”

Responses (1)