Cooking up a component library

Amber Williams' avatar

Amber Williams

Last edited - August 30, 2024


Repo link for those that wish to dive straight into the code.

As software engineers, we often find ourselves reinventing the wheel with each new project, particularly when it comes to UI components. This repetitive setup can be time-consuming and often leads to inconsistencies across projects. It's a challenge I've faced repeatedly, both in my personal projects and in professional settings.

To address this, I embarked on creating a custom component library. This approach has proven beneficial not only for solo developers like myself, but also for companies of various sizes.

Here's a handful of benefits off the top of my head:

Solo Developers
• Significantly reduces project setup time
• Ensures consistency across personal projects
• Allows focus on core functionality rather than UI basics
Companies
• Promotes a unified design language across products
• Improves team efficiency by reducing redundant work
• Simplifies onboarding for new team members

In this post, I'll walk you through my process of building a custom component library. While my specific choices may not fit everyone's needs, the overall approach can be adapted to suit different requirements and preferences.

1. Picking a base

Selecting the right component library was crucial. I evaluated several options based on key factors important for my needs.

Libraries considered:

To guide my decision, I prioritized these factors:

• React integration
• Open-source
• Actively maintained
• Extendable
• Robust component collection
• Built-in design system
• Documentation
• Form handling
• Native Support

After careful consideration, I chose MUI's Material-UI as my base library. It stood out in several key areas, particularly in its extensive set of prebuilt components, robust documentation, and built-in design system based on Google's Material Design. As a bonus there's a Figma base MUI provides for free here that can be used prototype your projects ahead of build.

However, it's important to note that each base has its strengths, and what worked best for my needs might not be the optimal choice for everyone. I encourage you to evaluate these options based on your own criteria and project needs.

2. Making it my own

Next up, I tweaked the design system. Went for a kind of robotic text vibe, cooked up a color scheme that didn't make my eyes bleed, and made sure the dark and light modes looked related.

Here are examples of my customized themes:

It's worth noting that while these customizations worked well for my projects, your needs may differ. The beauty of using a flexible design system like is that it allows for extensive customization generally from single config file.

Worth checking if your base component library has a theme generator online to speed this step up.

3. Compile time

Selecting the right Javascript module bundler was a crucial decision in the development of my component library. The most common JavaScript compilers I considered were Rollup, Webpack and Parcel.

If you've ever had to maintain an ejected Create React App you know how complex Webpack can get. Given my Webpack battle scars I wanted to explore my options before committing to a long term relationship with a compiler. This exploration led me to Rollup, which stood out for several reasons. The popularity trend against it's peers included.

4. Package optimizations

Initial package size was an eye-watering 32.7 MB unpacked. I reduced this to 4.8 MB by removing sourcemaps and including only one javascript module system (I went with CommonJS).

Note that @mui/material alone is 10.8 MB unpacked.

It's crucial to consider your package size and optimize for the smallest size that meets your needs. While 4.8 MB is a significant improvement, there's certainly room for further optimization. The key is balancing functionality with performance.

5. Component playground

To streamline development and quickly iterate on components, I set up a local testing environment:

  • Created a playground from a boilerplate React app

  • Pointed the package.json file to the locally built component library: "@mb3r/component-library": "file:.."

  • Added a Makefile with build and test-build commands for quick use

This step is particularly useful for verifying component behavior before publishing a package.

6. Publishing the package

For distributing the component library, I chose to publish it on NPM, the most widely used registry for open-source Node.js packages. Here's how I approached it:

  • Created a company-type namespace: @mb3r

  • Named the component library: component-library

  • Resulting package name: @mb3r/component-library

This approach allows for hosting multiple related packages under a single namespace, providing a clean and organized structure in users' package.json files.

While NPM is ideal for open-source projects, the choice of registry depends on your specific requirements around privacy, integration with existing tools, and deployment processes. You may consider other package repository solutions such as AWS CodeArtifact, GitHub Packages or Verdaccio over NPM.

I update the versioning manually by running npm version 1.0.0 then publish to NPM by running npm publish.

In future releases I plan to automate package versioning with Commitizen. Commitizen essentially is a wrapper around your repo's Git history and creates semantic versioning based on Git commits along with a host of other useful features like automating your changelog.

Final thoughts

The ultimate goal is to find the right balance between consistency, efficiency, and flexibility in your workflow for a component library.

Personally creating a custom component library has significantly streamlined my development process. For example, I was able to build a chrome extension in under a day here's a quick screen grab of it.



If you're interested in this approach, consider giving the component library repo a star.