Skip to main content

Build A React UI Library (WIP)

March 1, 2024 | 3 min read

This article is a collection of notes stemming from my Build React UI Youtube Series. These notes aren’t meant to serve as a standalone solution but rather as notes to use while following along with the series – or to refer back to.

⚠️ These notes are very much Work In Progress

Setup UI

For the first video, we setup the React UI library by using vite library mode.

#Steps 1, install Vite:

Vite Build Guide

npm create vite@latest

React + TypeScript

cd [into project]
npm install && npm run dev # runs the project

#Step 2, setup library mode with config:

import { defineConfig } from 'vite';
import { resolve } from 'path';
import react from '@vitejs/plugin-react';

// https://vitejs.dev/config/
export default defineConfig({
	plugins: [react()],
	build: {
		// library entry (src/main.tsx)
		lib: {
			entry: resolve(__dirname, 'lib/main.tsx'),
			name: 'react-ui-library',
			fileName: 'react-ui-library' // this will add extensions automatically
		},
		// import react and react-dom as external
		rollupOptions: {
			external: ['react', 'react-dom', 'react/jsx-runtime'],
			output: {
				globals: {
					react: 'React',
					'react-dom': 'ReactDOM',
					'react/jsx-runtime': 'react/jsx-runtime'
				}
			}
		}
	}
});

Follow rest of guide https://vitejs.dev/guide/build:

  1. create lib/main
  2. create lib/components/Button
  3. update package.json and make sure to use the correct lib name
  4. test build

Package.json:

{
	"name": "react-ui-library",
	"private": true,
	"version": "0.0.0",
	"type": "module",
	"files": ["dist"],
	"main": "dist/react-ui-library.cjs",
	"module": "dist/react-ui-library.js",
	"exports": {
		".": {
			"import": "./dist/react-ui-library.js",
			"require": "./dist/react-ui-library.umd.cjs"
		}
	},
	"scripts": {
		"dev": "vite",
		"prebuild": "rm -rf build",
		"build": "tsc && vite build",
		"lint": "eslint . --ext ts,tsx --report-unused-disable-directives --max-warnings 0",
		"preview": "vite preview",
		"prepublish": "npm run build"
	},
	"dependencies": {
		"react": "^18.2.0",
		"react-dom": "^18.2.0"
	},
	"devDependencies": {
		"@types/react": "^18.2.55",
		"@types/react-dom": "^18.2.19",
		"@typescript-eslint/eslint-plugin": "^6.21.0",
		"@typescript-eslint/parser": "^6.21.0",
		"@vitejs/plugin-react": "^4.2.1",
		"eslint": "^8.56.0",
		"eslint-plugin-react-hooks": "^4.6.0",
		"eslint-plugin-react-refresh": "^0.4.5",
		"typescript": "^5.2.2",
		"vite": "^5.1.0"
	}
}

#Step 3, ts config paths:

https://www.npmjs.com/package/vite-tsconfig-paths

install:

npm install vite-tsconfig-paths

Update Vite and TS configs:

TSConfig should reference the paths – directly path to main.tsx

https://www.typescriptlang.org/tsconfig#paths

#Step 4, test it!

  • import a component
  • render it!
  • preview it using npm run dev

Tailwind and Storybook

#Step 1: install tailwind

official guide

npm install -D tailwindcss postcss autoprefixer
npx tailwindcss init -p
/** @type {import('tailwindcss').Config} */
export default {
	content: [
		// reference the library _only_
		'./lib/**/*.{js,ts,jsx,tsx}'
	],
	theme: {
		extend: {}
	},
	plugins: []
};

#Step 2: create ./lib/index.css

@tailwind base;
@tailwind components;
@tailwind utilities;

Import it in main.tsx

#Step 3: test Tailwind!

  • add a color to the button
  • publish it
  • install it and test it in a separate project

#Step 4: Storybook

https://storybook.js.org/blog/storybook-for-vite/

Initialize storybook for vite

npx sb init --builder @storybook/builder-vite

Run it for the first time:

npm run storybook

#Step 5: Setup storybook for our usecase

Delete ./src/stories

Adjust the storybook/main.ts file

import type { StorybookConfig } from '@storybook/react-vite';

const config: StorybookConfig = {
	// our lib paths
	stories: ['../lib/**/*.mdx', '../lib/**/*.stories.@(js|jsx|mjs|ts|tsx)'],
	addons: [
		'@storybook/addon-links',
		'@storybook/addon-essentials',
		'@storybook/addon-onboarding',
		'@storybook/addon-interactions'
	],
	framework: {
		name: '@storybook/react-vite',
		options: {}
	},
	docs: {
		autodocs: 'tag'
	}
};
export default config;

Start storybook

npm run storybook

#Step 6: Add First Story

import type { Meta, StoryObj } from '@storybook/react'

import { Button } from './Button'

const meta: Meta<typeof Button> = {
  component: Button,
}

export default meta

type Story = StoryObj<typeof Button>

export const Default: Story = () => <Button>Click me</Button>

It should work! :D

#Step 7: Add Tailwind to Storybook

I think this is where the complexity starts to get ridiculous and it’ll get even more ridiculous. We’re going to add Tailwind to Storybook.

This steps feels like crossing wires but I don’t know a better solution yet. Import tailwind.css in .storybook/preview.js

import '../lib/tailwind.css'

Export Styles And Types

In this video, we will export CSS and Types for our React UI Library project for target applications to use when importing Cottage UI.

Resources:

Styles and types aren’t getting exported yet which hampers our ability to use this UI library. Let’s go ahead and add that.

#Reinstall command

Add this command to your package.json for easy reinstall:

"reinstall": "rm -rf node_modules && npm install"

#Styles

Add the style export and mark side effects as false in package.json.

"exports": {
	"./dist/style.css": "./dist/style.css"
},
"sideEffects": false

Target app needs to import it:

import 'cottage-ui/dist/style.css'

#Why?

Could not get things to work correctly with other solutions and this one seemed to make the most sense – it imports TailwindCSS. This really depends on how you want to structure your CSS.

Since this is supposed to be an internal library, I prefer the simplicity of the solution over having to import an additional file

#Types

https://www.npmjs.com/package/vite-plugin-dts

npm install --save-dev vite-plugin-dts

add plugin:

import dts from 'vite-plugin-dts';
plugins: [dts({ rollupTypes: true })];

add include into typescript:

includes: ['lib']

Run it!

#How about tree-shaking?

We’ll handle that in the future.

Share this article

Enjoyed this? Get new posts delivered.

Join the newsletter for more articles on AI, development, and side projects.