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.
- Initial setup.
- Creating a Card component.
- Documenting the component with Storybook.
- 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:
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.
- 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.
- 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.
- plugins: Defines 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.
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.
Create a file called card.module.css in the /src/components/Card/ directory:
Component-Level index.ts
Library-Level index.ts
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.
- Initialize Storybook: npx storybook init
Create a Story for the Card Component
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.
Compiling Your Component Library with Rollup
Why Rollup?
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?
- 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.