Mastering React: How to Build a Powerful Component Library

You are currently viewing Mastering React: How to Build a Powerful Component Library

Building a reusable component library in React can drastically improve your development workflow, enabling you to maintain consistency across projects and speed up development. Whether you’re working solo or with a team, having a well-structured component library allows you to focus on the unique features of each project while reusing battle-tested UI components.

In this blog post, we’ll walk you through the steps to create a powerful React component library from scratch. You’ll learn how to set up your environment, build and document your components with Storybook, and prepare your library for distribution using Rollup. 

  1. Initial setup.
  2. Creating  a Card component.
  3. Documenting the component with Storybook.
  4. Compiling Your Component Library with Rollup

Whether you’re a React enthusiast or a seasoned developer, this tutorial will help you get started with a robust setup for building reusable components.

 

Initial Setup

Before diving into the code, ensure you have Node.js and npm or yarn installed. Then, follow these steps:

 Create a new directory and initialize a project:

 

Install required dependencies:

npm install react react-dom prop-types 
npm install –save-dev typescript rollup @rollup/plugin-node-resolve @rollup/plugin-commonjs @rollup/plugin-typescript rollup-plugin-peer-deps-external rollup-plugin-terser jest chance storybook @storybook/react @storybook/addon-essentials  typescript-plugin-css-modules
 

This setup includes core dependencies like react and react-dom for building and rendering components, with prop-types for runtime prop validation. Development is powered by typescript for static typing, rollup for bundling into cjs and esm formats, and plugins like node-resolve, commonjs, and peer-deps-external to handle dependencies efficiently.  terser optimizes bundle size, while storybook with @storybook/react and addon-essentials provides an interactive playground for components. Testing is supported by jest for unit tests and chance for mock data generation. Finally, typescript-plugin-css-modules ensures type safety for CSS modules, completing a robust and streamlined library development workflow.

 

Here you will find the package.json
 
Configure TypeScript
 
This configuration is well-suited for creating a React component library:
  • Strict typing ensures reliability.
  • Type declarations enable seamless integration into TypeScript projects.
  • CSS modules plugin supports modern styling practices.
  • Optimized output ensures compatibility with both ES Module and CommonJS consumers.
 
 

Here’s a detailed explanation of each line in the tsconfig.json:

  • compilerOptions:  This section contains all the options that configure how the TypeScript compiler (tsc) behaves.
  • target: “es2016”:  Specifies the JavaScript version to which TypeScript compiles your code.
  • esModuleInterop: true:  Enables compatibility with CommonJS and ES Module imports.
  • forceConsistentCasingInFileNames: true:  Ensures that the compiler treats file paths with case sensitivity.
  • strict: true:  Enables all strict type-checking options in TypeScript, including:
    • strictNullChecks
    • strictFunctionTypes
    • noImplicitAny,  etc.
    • This is crucial for writing robust and type-safe code.
  • plugins: [{“name”: “typescript-plugin-css-modules”}]:  Specifies a TypeScript plugin for handling CSS modules.
  • skipLibCheck: true: Skips type-checking for declaration files (.d.ts).
  •  jsx: “react”:  Specifies how JSX should be processed.
  • module: “ESNext”: Specifies the module system for the compiled output. “ESNext” outputs ES Module syntax (import/export)
  • declaration: true:  Generates .d.ts. type definition files for your TypeScript code.  Helps consumers of your library use TypeScript effectively.
  • declarationDir: “types”:  Specifies the directory where declaration files (.d.ts.) will be output.
  • sourceMap: true: Generates source map files (.j.map) alongside compiled .js files.
  • outDir: “dist”:  Specifies the directory where the compiled JavaScript and related files (e.g., source maps) will be output.
  • moduleResolution: “node”: Specifies the module resolution strategy. “node” mimics Node.js behavior when resolving modules, searching node_modules.
  • allowSyntheticDefaultImports: true: Allows default imports from modules that don’t have a default export.  You can write  import React from ‘react’  even if the module uses module.exports.
  • emitDeclarationOnly: true:  Emits only type declaration files (.d.ts.) without compiling .ts into .js. Useful for libraries focusing on type definitions.
  • rootDir: “src”: Specifies the root directory for TypeScript files. Ensures that only files within the src folder are included in the compilation process.
 

 If you like to find more about other TS configurations, here is a helpful link to read more on the matter: 

https://www.typescriptlang.org/es/tsconfig/#compilerOptions 

 

Configure Rollup

This Rollup configuration does the following: 

  • Builds the JavaScript library: Outputs both CommonJS (cjs) and ES Module (esm) formats. Processes TypeScript, CSS, and dependencies efficiently. 
  • Generates type definitions: Outputs a single .d.ts file for consumers who use TypeScript. 
  • Optimizes for modern bundlers: Handles peer dependencies and minimizes output size with tree-shaking and minification.
This setup ensures your library is performant, type-safe, and compatible with a wide range of environments. Add a rollup.config.mjs file. 
 
 
 Here’s a detailed explanation of each part of the rollup.config.js file:
  • input: “src/index.ts”: Entry point for the library. The file specified here imports and exports all components or utilities in the library.
  • output: Defines the output format and location:
    • file: “dist/cjs/index.js”:  Output for CommonJS (CJS) modules, typically used in Node.js environments.
    • file: “dist/esm/index.js”:  Output for ES Modules (ESM), which are tree-shakable and suitable for modern bundlers like Webpack or Vite.
  • pluginsDefines the transformations and enhancements applied to the bundle:
    • peerDepsExternal(): Ensures peer dependencies like react are marked as external to prevent duplication in the final build.
    • resolve({ browser: true }): Resolves module imports with browser compatibility.
    • commonjs(): Converts CommonJS modules to ES Modules for compatibility.
    • postcss({ modules: true }): Processes imported .css files with CSS Modules enabled.  Converts CSS class names to scoped, unique identifiers.
    • terser():  Minifies the output files to reduce file size.
    • typescript():  Compiles TypeScript files.  Uses the specified tsconfig.json.  Excludes files and directories
  • input: “dist/esm/types/index.d.ts”:  Entry point for TypeScript declaration files, generated by the first build step.
  • output:
    • file: “dist/index.d.ts”: Bundles all .d.ts files into a single declaration file for the library.
    • format: “esm”: Ensures the declaration file is compatible with ES Module consumers.
  • plugins:
    • dts(): Bundles all .d.ts files into a single output file.
    • external: [/\.css$/]: Excludes CSS files from the declaration file bundle.
 
Project Sctructure
 

Organize your files:

In a TypeScript project, working with CSS Modules requires a bit of setup to ensure compatibility. One important step is adding the line declare module “*.module.css”; to a global.d.ts file. This declaration informs TypeScript that any file with a .module.css extension should be treated as a module. It allows you to import styles from CSS Module files without TypeScript throwing errors. For example, you can write import styles from ‘./MyComponent.module.css’; and access scoped class names directly in your components. This setup ensures a smooth integration of CSS Modules in your library, keeping your styles locally scoped and your codebase type-safe.

 
Creating the Card Component
 
The Card component will have a title and subtitle.
 

Create a file called card.module.css in the /src/components/Card/  directory:

 
Add Component Exports
 
To create a scalable and developer-friendly component library, it’s important to manage your exports effectively. Using multiple index.ts files—one at the component level and one at the library root—is a clean and efficient way to handle this. Let’s explore how these two layers of exports work together.
 

Component-Level index.ts

At the component level, the index.ts file serves as a direct re-export point for the component it represents. For example, if you have a component called Card, the index.ts file in src/components/Card/index.ts would look like this:  export { default } from “./Card”; 
 

Library-Level index.ts

At the root of the src folder, the index.ts file acts as the central entry point for your library. It aggregates and re-exports all components by referencing the index.ts files in the components folder. Here’s an example of what it looks like: export * from “./components”.
 
This syntax ensures that every component exported by the index.ts files within src/components is automatically available at the library level. If your components folder has multiple components like Card, and Button, they’ll all be accessible through this single export statement.
 

Why This Structure?

  • Encapsulation: Each component manages its own exports independently, promoting modularity.
  • Scalability: Adding new components only requires exporting them in their folder-level index.ts. The root file automatically picks them up.
  • Ease of Use for Consumers: Consumers can import all components from a single location without navigating complex paths.
 
Documenting with Storybook
 
Storybook is a powerful tool for documenting and showcasing your React components in isolation. It not only helps you visually test your components but also serves as a living documentation site for your library. Here’s how you can use Storybook to document your components effectively.
 
First, you need to add Storybook to your project. Run the following command to install Storybook and its essentials addon:
  • Initialize Storybook: npx storybook init
This will configure Storybook for your project and create a .storybook directory along with example stories.
 

Create a Story for the Card Component

Each component can have its own story file. Let’s create a story for the Card component in this path
 src/components/Card/Card.stories.tsx
 
 
Now we are going to start storybook with:  npm run storybook
This will open the Storybook UI in your browser, where you can see the Card component rendered with the defined stories.
 
 

Why Use Storybook?

  • Isolated Development: Focus on individual components without worrying about the rest of the application.
  • Documentation: Serve as living documentation for your library, making it easier for others to use your components.
  • Testing: Visualize edge cases and ensure your components look great across various use cases.
By integrating Storybook into your workflow, you turn your component library into a polished and professional toolkit that’s easy to use and maintain.
 
 

Compiling Your Component Library with Rollup

 

Once your components are built and documented, the next step is to prepare your library for distribution. This is where Rollup, a powerful module bundler, comes in. Rollup helps you bundle your components into optimized formats ready to be consumed by other projects.
 

Why Rollup?

Rollup is ideal for libraries because it creates smaller, faster bundles by leveraging tree-shaking (removing unused code). It supports modern JavaScript features and provides flexibility for exporting in multiple formats like CommonJS (CJS) and ES Modules (ESM).
 

Update package.json for Distribution

  • main: Points to the CommonJS build. 
  • module: Points to the ES Module build. 
  • types: Specifies the TypeScript definitions.
 

Run the Rollup build script: npm run build

 

You can find the complete project for this guide on GitHub. Feel free to explore the code, try it out in your own projects, and contribute if you’d like! The repository contains all the steps we covered in this blog,
 

Congratulations! 

You’ve successfully built the foundation of a powerful React component library. Through this guide, we walked through setting up the project with TypeScript and Rollup, creating and documenting components with Storybook, and preparing a build-ready library using Rollup. By following these steps, you now have a reusable and scalable component library ready to share and integrate into other projects.

Creating a component library isn’t just about writing reusable code; it’s about ensuring your library is well-documented, easy to use, and efficient. Tools like Storybook, TypeScript, and Rollup play a vital role in achieving these goals by streamlining the development, documentation, and distribution processes.

 

What’s Next?

In Part Two of this series, we’ll focus on:
  • Testing Your Library: Writing unit  tests with tools like Jest and ensuring your components work as intended.
  • Local Testing with Other Projects: Using tools like npm link to integrate your library into other projects during development.
  • Publishing to GitHub Packages: Setting up CI/CD pipelines for automated testing and publishing your library to GitHub Packages for easy distribution.
Stay tuned for the next steps as we take this library to production-ready quality and share it with the world! 🚀

View available Trainings in miCoach