Build a Custom React Component Library with Storybook 7 Beta and Vite 4 in 2023
Copied Text
What is a component library?
Copied Text
React component libraries are collections of reusable components that can be used to quickly build user interfaces. They are often distributed as NPM packages and can include a variety of different types of components, such as buttons, form elements, and layout components. Using a React component library can help to speed up development and ensure that the user interface is consistent and follows established design patterns.
Copied Text
Advantages to using a component library
Copied Text
Reusable components: A component library provides a set of pre-built, reusable components that can be easily incorporated into many applications, saving time and effort.
Copied Text
Consistency: By using a component library, it's easier to ensure that the user interface is consistent across different parts of the application. This can improve the overall user experience and make it easier for users to navigate the application.
Copied Text
Improved performance: Well-designed component libraries can improve the performance of an application by providing components that are optimized for performance.
Copied Text
Community support: Many component libraries have a large community of users and contributors, which means that there is often a wealth of resources and support available for working with the library.
Copied Text
Improved maintainability: Using a component library can help to improve the maintainability of an application by providing a set of stable, well-tested components that can be easily updated and maintained over time.
Copied Text
Why you may not need to make your own component library
Copied Text
Whether you’re a company or an individual, creating a component library takes time, so it's important to consider the time investment and whether it's worth it for your project or organization. Building from scratch takes significant effort, but leveraging existing libraries or frameworks can reduce the effort. Consider whether the benefits of developing a component library outweigh the time investment. For larger, long-term projects with multiple developers, a component library can save time and improve consistency. For smaller projects with shorter lifespans, it may not be worth the effort.
Copied Text
Project Setup Overview
Copied Text
Setup the Vite React project with TypeScript
Copied Text
Setup Storybook with React and TypeScript
Copied Text
Setup styling with Tailwind and import the generated files
Copied Text
Setup the library build script and Storybook builds
Copied Text
Setup package publishing
Copied Text
Setup Vite React project with TypeScript make sure to rename
if you’re on NPM 6 or below then you may not need the extra set of dashes (
Copied Text
--
Copied Text
)
Copied Text
Once you generate the Vite app cd into the directory,
Copied Text
Initialize Storybook beta:
Copied Text
npx sb@next init
Copied Text
Setting Up Tailwind
Copied Text
In case you’ve never used Tailwind, here is my elevator pitch:
Copied Text
Tailwind is a utility-first CSS framework that offers many advantages compared to traditional CSS solutions. Unlike traditional CSS frameworks which provide a set of predefined components and styles, Tailwind uses a "utility-first" approach which provides low-level utility classes such as
Copied Text
text-red-600
Copied Text
or
Copied Text
p-4
Copied Text
which can be combined to build complex components. This approach allows for greater flexibility and customization, allowing developers to quickly create custom components without the need to write custom CSS. It also makes it easier to keep track of styles, as all the style rules are defined in a single file. Tailwind's bundling system is designed to only include the classes that are used in the project. When the Tailwind config file is generated, it creates a list of all available classes, and when the Tailwind CSS bundle is generated, only the classes that are referenced in the project are included in the bundle, reducing the size of the bundle and making it more efficient. This allows developers to use Tailwind without having to worry about including unused classes or bloating their bundle size. Additionally, Tailwind is fully customizable and supports theming, so developers can easily create their own custom themes for their applications.
"scripts": {
"build": "concurrently \"npm run build:css\" \"tsc --emitDeclarationOnly && vite build\"",
"build:css": "tailwindcss -m -i ./src/tailwind-entry.css -o ./dist/index.css",
"storybook": "concurrently \"npm run storybook:css\" \"storybook dev -p 6006\"",
"storybook:css": "tailwindcss -w -i ./src/tailwind-entry.css -o ./src/index.css",
"build-storybook": "concurrently \"npm run build-storybook:css\" \"storybook build\"",
"build-storybook:css": "tailwindcss -m -i ./src/tailwind-entry.css -o ./src/index.css"
},
Let’s review what is going on here:
Copied Text
Since we are building a component library you’ll notice we removed the
Copied Text
dev
Copied Text
and
Copied Text
preview
Copied Text
scripts, this would be to run the Vite app, this is replaced with Storybook - which in Storybook 7 runs Vite.
Copied Text
You’ll notice the
Copied Text
:css
Copied Text
scripts, in the case of running Storybook it will start a watcher that will generate a new CSS file when new Tailwind classes are added. The build scripts will create the css bundles for the builds. In development, Tailwind takes in the
Copied Text
./src/tailwind-entry.css
Copied Text
file and outputs
Copied Text
./src/index.css
Copied Text
normally in the
Copied Text
./src/tailwind-entry.css
Copied Text
file you’ll see
Copied Text
@tailwind base;
Copied Text
which is Tailwind’s normalizer. A CSS normalizer is a set of rules used to ensure that all HTML elements will appear consistently across different browsers. It works by resetting all of the default styles that are applied to HTML elements, such as margins, padding, and font sizes, to a consistent baseline. This helps to ensure that the user interface looks the same no matter which browser it is being viewed in. I am adding it to the project but you may not necessarily want to have that and I just want to make sure you’re aware it's being added.
Copied Text
Now that we are generating the Tailwind CSS file, we need that file to be imported to the Storybook stories, in order to do that we need to update the
file is used to configure various aspects of Storybook, such as the locations of source files, the build process, and the add-ons that should be used. Here is what our
this is what enables Vite to run when Storybook is started.
Copied Text
Package.json setup
Copied Text
In the
Copied Text
package.json
Copied Text
we want to add a new field called
Copied Text
peerDependencies
Copied Text
and move
Copied Text
react
Copied Text
and
Copied Text
react-dom
Copied Text
from
Copied Text
dependencies
Copied Text
to
Copied Text
peerDependencies
Copied Text
. NPM peer dependencies are packages that are required by a package but are not automatically installed when the package is installed. Instead, they must be manually installed by the user. This allows packages to depend on other packages without needing to include them in the package's actual code. For example, if a package uses React, it can list React as a peer dependency, so the user of the package must install React separately in order for the package to work correctly. Remove
file are used to specify which files should be included in the package when it is published to NPM. The
Copied Text
type
Copied Text
field specifies the type of package, such as a library or an application. The
Copied Text
main
Copied Text
field specifies the entry point or main file for the package. The
Copied Text
module
Copied Text
field specifies the file that should be used for the ES module version of the package. The
Copied Text
types
Copied Text
field specifies the TypeScript declaration files for the package. Finally, the
Copied Text
files
Copied Text
field specifies which files and directories should be included in the package when it is published. The
Copied Text
name
Copied Text
field in the
Copied Text
package.json
Copied Text
file is used to specify the name of the package. This is the name that will be used when the package is published to NPM and when it is installed using the
Copied Text
npm install
Copied Text
command. It should be a unique, lowercase, and dash-separated string, and should not contain any spaces or special characters.
Now that everything is set up and ready to go, let's take it for a spin! To run the app, simply execute the following command:
Copied Text
npm run storybook
Copied Text
. There should be a few default stories that Storybook generates with the project. I’ve removed the default stories, but you can set up the folder structure however you wish, but I set up the project to have
Copied Text
components
Copied Text
folder and a
Copied Text
stories
Copied Text
folder under the
Copied Text
src
Copied Text
folder. First let's create a card component. Create a file
Since we are building a component library, I have a
Copied Text
src/index.ts
Copied Text
file that exports any of the components I plan on exporting with the component library. You can think of that file as the entry point to the component library. In that file, we need to import/export the
Copied Text
Card
Copied Text
component
Copied Text
After that, it's time to create a story. A story is like a miniature version of your app, and it's used to create isolated examples of your component. A Storybook can be used to create, view, and organize these stories. To create a story, simply create a new file in the
Copied Text
stories
Copied Text
directory. Let's create a story for the
Copied Text
Card
Copied Text
component, and create a file called
Copied Text
card.stories.js
Copied Text
.
Copied Text
Copied Text
import type { Meta, StoryObj } from "@storybook/react";
import { Card } from "../";
const meta = {
title: "Example/Card",
component: Card,
tags: ["docsPage"],
argTypes: {
title: {
control: { type: "text" },
},
description: {
control: { type: "text" },
},
},
} satisfies Meta<typeof Card>;
export default meta;
type Story = StoryObj<typeof meta>;
export const Primary: Story = {
args: {
title: "Card Title",
description: "This is a card",
},
};
The
Copied Text
argTypes
Copied Text
field allows us to specify which props we want to allow for the Storybook controls. This means that when viewing the story, we can see the controls for the props, and can adjust them as needed when viewing the story. There is a lot that you can do with this functionality and if you haven’t already I highly encourage you to read the storybook docs.
Copied Text
The
Copied Text
Primary
Copied Text
export allows us to set up an example of the story, including passing in default prop values. This allows us to see the story in action and can be used to debug and check that the component is working as expected. Hopefully, if you’ve been following along you should be able to go to
Copied Text
localhost:6006
Copied Text
and view our new
Copied Text
Card
Copied Text
story, you can update the title and description props to test things out.
Copied Text
So great, we can build and view the components, but now you’re probably wondering “how do I build and distribute my components?”
Copied Text
Setting Up the Build Process
Copied Text
All the following files are at the root of the project.
Copied Text
Vite config Setup
Copied Text
vite.config.ts
Copied Text
Copied Text
import react from "@vitejs/plugin-react";
import { resolve } from "path";
import { defineConfig } from "vite";
import dts from "vite-plugin-dts";
import tsConfigPaths from "vite-tsconfig-paths";
import * as packageJson from "./package.json";
export default defineConfig((configEnv) => ({
plugins: [
react(),
tsConfigPaths(),
dts({
include: ["src"],
}),
],
build: {
lib: {
entry: resolve("src", "index.ts"),
name: "react-component-library",
formats: ["es", "umd"],
fileName: (format) => `react-component-library.${format}.js`,
},
rollupOptions: {
external: [...Object.keys(packageJson.peerDependencies)],
},
},
}));
The file starts by importing a number of modules that are used in the configuration. The
Copied Text
react
Copied Text
module is a Vite plugin for building React applications. The
Copied Text
resolve
Copied Text
function from the
Copied Text
path
Copied Text
module is used to resolve file paths. The
Copied Text
defineConfig
Copied Text
function is a part of the Vite API and is used to define the configuration for the build. The
Copied Text
dts
Copied Text
module is a Vite plugin for generating TypeScript declarations files, and the
Copied Text
tsConfigPaths
Copied Text
module is a Vite plugin for using TypeScript paths in the configuration.
Copied Text
The file then exports a default configuration object that is generated by calling the
Copied Text
defineConfig
Copied Text
function and passing in a function that receives a
Copied Text
configEnv
Copied Text
object. The configuration object has two properties:
Copied Text
plugins
Copied Text
and
Copied Text
build
Copied Text
.
Copied Text
The
Copied Text
plugins
Copied Text
property is an array of Vite plugins that should be loaded. In this case, the configuration includes the
Copied Text
react
Copied Text
plugin, the
Copied Text
tsConfigPaths
Copied Text
plugin, and the
Copied Text
dts
Copied Text
plugin.
Copied Text
The
Copied Text
build
Copied Text
property has a
Copied Text
lib
Copied Text
sub-property, which specifies configuration options for building a library. The
Copied Text
entry
Copied Text
property is the entry point for the library, and the
Copied Text
name
Copied Text
property is the name of the library. The
Copied Text
formats
Copied Text
property specifies the output formats that should be generated, and the
Copied Text
fileName
Copied Text
property is a function that generates the file names for the output files.
Copied Text
The
Copied Text
build
Copied Text
property also has a
Copied Text
rollupOptions
Copied Text
sub-property, which specifies options for the Rollup bundler that Vite uses. The
Copied Text
external
Copied Text
property is an array of dependencies that should be treated as external to the bundle.
If you're curious what each property does I break it down below:
Copied Text
"compilerOptions"
Copied Text
: An object that specifies various options for the TypeScript compiler.
Copied Text
"target"
Copied Text
: Specifies the ECMAScript target version for the compiled code. In this case, the value is
Copied Text
"ESNext"
Copied Text
, which means the code will be compiled to the latest version of ECMAScript that is supported by the TypeScript compiler.
Copied Text
"useDefineForClassFields"
Copied Text
: Controls the emit of the
Copied Text
defineProperty
Copied Text
calls for class fields.
Copied Text
"lib"
Copied Text
: An array of library files that the compiler should include in the compiled output. In this case, the libraries
Copied Text
"DOM"
Copied Text
,
Copied Text
"DOM.Iterable"
Copied Text
, and
Copied Text
"ESNext"
Copied Text
are included.
Copied Text
"allowJs"
Copied Text
: Controls whether or not the compiler should allow the compilation of JavaScript files. In this case, the value is
Copied Text
false
Copied Text
, meaning that the compiler will not allow the compilation of JavaScript files.
Copied Text
"allowSyntheticDefaultImports"
Copied Text
: Controls whether synthetic default imports are allowed in the input files.
Copied Text
"strict"
Copied Text
: Enables all strict type-checking options.
Copied Text
"forceConsistentCasingInFileNames"
Copied Text
: Disallows inconsistently-cased references to the same file.
Copied Text
"module"
Copied Text
: Specifies the module type for the compiled code. In this case, the value is
Copied Text
"ESNext"
Copied Text
, which means the code will be compiled as an ECMAScript module.
Copied Text
"moduleResolution"
Copied Text
: Specifies the module resolution strategy for the compiler. In this case, the value is
Copied Text
"Node"
Copied Text
, which means the compiler will use the Node.js module resolution strategy.
Copied Text
"resolveJsonModule"
Copied Text
: Controls whether the TypeScript compiler should resolve
Copied Text
.json
Copied Text
files as modules. In this case, the value is
Copied Text
true
Copied Text
, meaning that the compiler will resolve
Copied Text
.json
Copied Text
files as modules.
Copied Text
"isolatedModules"
Copied Text
: Controls whether input files are treated as a separate module in their own right.
Copied Text
"noEmit"
Copied Text
: Tells the compiler not to emit output.
Copied Text
"jsx"
Copied Text
: Specifies the JSX factory function to use when compiling JSX code. In this case, the value is
Copied Text
"react-jsx"
Copied Text
, which means the compiler will use the
Copied Text
React.createElement
Copied Text
function as the JSX factory function.
Copied Text
"declaration"
Copied Text
: Tells the compiler to generate corresponding
Copied Text
.d.ts
Copied Text
files for each input file.
Copied Text
"skipLibCheck"
Copied Text
: Tells the compiler to skip type checking of declaration files.
Copied Text
"esModuleInterop"
Copied Text
: Controls whether the compiler should add namespaces to the top-level import/export statements in the generated code.
Copied Text
"declarationMap"
Copied Text
: Controls whether the compiler should generate a source map for each corresponding declaration file.
Copied Text
"baseUrl"
Copied Text
: Specifies the base URL for the compiler to use when resolving non-relative module names. In this case, the value is
Copied Text
"."
Copied Text
, which means the compiler will use the current directory as the base URL.
Copied Text
"paths"
Copied Text
: Used to specify aliases for imports that should be resolved by the TypeScript compiler. These aliases can be used to simplify imports in the code, and can also be used to make it easier to move code around without needing to change the imports. For example, in this configuration, the
Copied Text
react-component-library
Copied Text
alias is used to point to the
Copied Text
src/index.ts
Copied Text
file, so any imports using this alias will be resolved to the
Copied Text
src/index.ts
Copied Text
file.
Copied Text
“typeroots”
Copied Text
: An array of paths that the TypeScript compiler will use to search for type declarations when resolving module imports. These paths can be used to specify where the compiler should look for type declarations for third-party modules, as well as for type declarations for custom modules. By adding the
Copied Text
src/index.d.ts
Copied Text
path to the
Copied Text
typeRoots
Copied Text
array, we can make sure that the TypeScript compiler will be able to find the type declarations for our custom modules.
Copied Text
"include"
Copied Text
: Used to specify which files and folders should be included in the compilation process. By default, the TypeScript compiler will only compile files that have a
Copied Text
.ts
Copied Text
or
Copied Text
.tsx
Copied Text
extension. The
Copied Text
"include"
Copied Text
property can be used to specify additional files and folders that should be included in the compilation process. In this case, the
Copied Text
"include"
Copied Text
property is set to
Copied Text
"src"
Copied Text
, which means the compiler will include all files and folders in the
Copied Text
src
Copied Text
folder in the compilation process.
Copied Text
“references”
Copied Text
: Used to specify other
Copied Text
tsconfig
Copied Text
files that should be referenced when compiling the project. This can be used to include configuration from multiple files, which can make it easier to maintain and share configuration across multiple projects. For example, in this configuration, the
files are used to configure the TypeScript compiler for the project. The
Copied Text
tsconfig.json
Copied Text
file is used to specify the general configuration for the project, while the
Copied Text
tsconfig.node.json
Copied Text
file is used to specify configurations specific to Node.js. Having separate files for the general and Node.js specific configuration helps to keep the configuration organized and makes it easier to maintain and share configuration across multiple projects.
Copied Text
composite
Copied Text
: A boolean value that tells the compiler to enable composite mode. In composite mode, the TypeScript compiler will combine all the projects specified in the
Copied Text
tsconfig.json
Copied Text
file into a single composite project. This can be useful if you want to build multiple projects together, or if you want to avoid building projects multiple times.
Copied Text
Testing The Build
Copied Text
Once you’ve set up the
Copied Text
tsconfig.json
Copied Text
,
Copied Text
tsconfig.node.json
Copied Text
,
Copied Text
package.json
Copied Text
, and
Copied Text
vite.config.ts
Copied Text
. Let's test to make sure the build actually works by running
Copied Text
npm run build
Copied Text
The library should successfully build and you should now see a
Copied Text
dist
Copied Text
folder in your project. This is the final built version of your library. But before you host the package on NPM it would be wise to test it on a local project.
Copied Text
Linking the Library to a Local App
Copied Text
NPM link is a command-line utility that is part of the Node package manager (NPM). It allows developers to create a symbolic link between a local package and a project so that changes to the local package can be tested in the project without having to publish the package.
Copied Text
To use NPM link, run
Copied Text
npm link
Copied Text
within the project. This will create a symlink between the package and the global NPM installation directory. Now go to a separate project where you want to test this library. Then, in the project directory, run
Copied Text
npm link react-component-library
Copied Text
to create a symlink between the package and the project. Finally, run
Copied Text
npm install
Copied Text
in the project directory to install the linked package. You should now be able to make updates to the component library code, and those changes will be reflected in the project it is linked to.
Copied Text
Publishing the Library
Copied Text
When publishing an NPM package to NPM, you'll first need to create an account on NPM. Once you have an account, you can use the
Copied Text
npm publish
Copied Text
command to publish your package to the NPM registry. Before running the command, make sure that you have updated the version number in the
Copied Text
package.json
Copied Text
file and that your code is properly tested and documented. Once the package is published, you'll be able to install it using the
Copied Text
npm install
Copied Text
command.
Copied Text
I wanted to focus on creating a component library with the technologies mentioned, so I didn’t include things like ESLint or Prettier, if those are desired I can always amend the post. But I can definitely include them in the GitHub Project.
Copied Text
Thanks for reading this blog post! I hope it was helpful and that you feel confident in building a custom React component library with Storybook 7 and Vite 4. Working with these tools can be intimidating, but I hope this post has been useful. If you have questions or need help, don't hesitate to reach out. If something's confusing, please let me know in the comments so I can update the post. I'm continuously striving to make this post helpful and comprehensive, so any feedback is appreciated.