Recently I published “How do we really use reusable components”, where I shared common pains learned by over 30 teams about shared components.
In this post, based on this knowledge, I will try to define some practices that will help your team successfully develop and manage shared components.
We will briefly look into architecture, tooling, design, discoverability and collaboration to overcome the common pitfalls most teams are bound to hit. The goal isn’t just to develop shared components; it’s to develop shared components that different teams will actually use in their different projects.
By following these ideas you will speed development times, leverage modularity to simplify your maintenance, keep your UI consistent and make it much easier for new developers to jump in and start working with the system.
So let’s go ahead and jump right in. Due disclosure, this post refers heavily to Bit. This is because I think Bit is often the best tool and philosophy for the job. I’m actively working on Bit, which might make me bias. That’s ok. There are many great tools like Lerna, Storybook and more which are useful for this too.
Through working on Bit I became so familiar with this use case, and with that understanding, it was built exactly for that purpose. If you don’t want to use Bit, just ignore it. You can still learn a lot from looking into how the different use cases can be described, and better understand how to achieve your goals.
Please feel free to comment, ask or add anything, and share your experience.
Before doing anything else, you must truly understand your needs. This means understanding both human and code architecture for components.
Most teams get to the point where they already have a set of somewhat reusable components and are trying to figure out how to share them.
90% Of the use cases I’ve seen fall into one of four categories:
a) One team with one application- Where you try to create a reusable component design system and increase modularity within the app.
b) One team with more than one application- Where you want to share components between two or more applications.
c) Two teams with two or more applications- Where you want to share components between more people and more applications.
d) Infrastructure team with many consuming teams and apps- Where you need a standardized scalable architecture for sharing components, as one team publishes components and many teams/apps consume them.
Placed yourself in one of the above categories? great. If not, please feel free to comment below and I’ll do my best to help. Let’s dive into each category.
So your team is building an app. Why the hell do you need shared components for? where will you share them? and with whom? Internally, this has been our use case for a while. Finally, it hit us: we really need modular components.
Why? Because it makes it much easier to add new features. It makes it easier to refactor and maintain. It reduces the learning curve of new developers. It turns our components into the design-system of our application. It enables gradual refactoring and makes it easier to build a new next app when needed. It also makes it easier to share code between the front and back ends of the app.
How it looks:
So for this simpler workflow, which is usually found within smaller startups and independent teams, there’s less meaning for the control and syncing of components across projects- and more in the workflow around the app.
Your components are usually internal to the application and are developed within the project. Usually, they won’t even be published as packages.
You are working with Figma/Sketch/other design tools to design components, maybe using Zeplin in between, then just developing them in the application.
There’s not much point in creating a library- where will you use it? Instead, you want to turn your components into an actual design system, so you can easily learn which components are available, see their design, and use them.
I’d recommend keeping the components within the app, while using Bit to isolate and share them into a reusable collection.
Example application repo:
Example component collection:
What you’ll get:
- Building new features or a new app is just Lego-work. You will gradually refactor your app into better modularity, ending app with much faster development and a better, more maintainable codebase.
- Zero refactoring for sharing components - Bit will seamlessly isolate the components so you can export and reuse them. No code changes.
- Visual design system made of actual components- You can see how the components look, play with them in a live playground and more.
- Gradual refactoring- Want to start using TS in your JS app? No problem- you can gradually refactor components in the same repo. Yes.
- Automatic dependency management for your components- Bit automatically defines and updates (when told to) all dependencies for components in the repo, for much easier and faster development. Changed the styling of a component and updated its version? Run
bit statusand Bit will let you update all dependent components with the new version.
- Reduced learning curve- A new developer can easily jump in, learn which components are available and how to use them.
- Better test coverage- Bit will run the unit tests for each component, so you have a very visual and blunt status check on your TDD or lack of it.
So, in this scenario, you have one team working on two or more projects. Classic use cases are web and mobile app, backEnd and frontEnd, or just building multiple applications for different purposes and costumes.
How it looks:
This scenario is usually when real questions begin. On one hand, building a shared library can be a serious overkill at this point. On the other hand, alternatives might not lead to the real amount of code-sharing needed.
Usually, you’ll start seeing duplicated code between the projects. First utility functions, maybe some middleware functions, and finally entire components.
Some people will try to push for a library, but it’s an overkill. Some might even publish one or two small packages they separated from the repos. You’ll start talking about a monorepo, or a multi-package repo with Lerna, etc.
A library is a long-term commitment, takes serious resources to build and maintain, and creates conflicts when the 2 projects have different needs.
At this point, you’d want to keep the source code of the shared components within the projects, but make it easy to reuse and sync the components. Otherwise, you’re bound to grow copy-pastings and your debt will grow deep.
You can use Lerna to refactor the projects into multi-package projects and publish some core packages to NPM. However, the overhead around this workflow will not work well for many smaller components. The project consuming the packages will have a hard time modifying them, as their source code is located in the other project, and serves that purpose first.
Using Bit you can isolate existing components from one project and reuse them in the other one. Then, you can use it to develop the same components from the two different repos at the same time, as the changes versions can be either updated in both projects or shared as separate components.
What you’ll get:
- Instantly share common code between the projects- Levering Bit’s component isolation to quickly share existing code and keep it managed.
- Develop the same code from two projects- Bit lets you bring a shared component into a different project and continue to develop it there.
- Sync and merge changes- Bit will let you update the changes between the project if needed, and even extend Git to merge the changes between them. If not, that’s ok too — just share the changes as a new component.
- All the benefits of section A above.
So here comes in a brand new dimension. We don’t need to just share code between our applications, but also between different people.
This means that in addition to the code-sharing workflow, we need to create a workflow where people can practically discover your shared components and collaborate on top of them.
How it looks:
At this point, you have 2 teams working on a few different projects. One team is in NY and the other is in London. Each team has been building their own components, and now you came to realize that they should standardize.
Then, you start thinking- why not share and reuse?
So, you start building a library for both of them. One of the teams is building the library, ideally while collaborating with the other team. Usually not.
Then problems start to emerge. Both teams use React, but one also uses TS and tests components with a different framework. You start discussing the standardization. Design is determined by the design team, but still the different apps have slightly different needs- from margins to anything else.
Then you realize you need to individually publish each component, and start thinking of Bit and Lerna. You also understand that you must create a discoverability portal for the components, maybe StoryBook or a docs site.
So here what we basically want to have is a universal hub where both teams can easily share, discover, use and modify components as needed.
You can create one Bit collection for the two teams, or two collections- one for each team. It’s up to you. If you do have a library (you probably have one), just share the components from the library to a collection.
Once you have the collection, it works as a single hub where both teams can easily share components too, discover shared components, install components as needed, suggest updates and changes from both projects and stay in sync.
Here’s an example from the popular semantic-ui-react library:
What you’ll get:
- A universal hub where both teams can easily share components
- Discoverability for the shared components- Search the collections, view the components visually, play with them online, view auto-generated API reference docs and more.
- Each team can modify components shared by the other team right from their own project- The other team shared a great button component but the margins are not right? just
bit importthe code and modify it. Like the changes enough to reuse them? share the update back out.
- Standardization for design and technology.
As your organization scales, shared-components becomes a strategical issue that goes beyond time-saving or building more modular software and becomes a critical demand in the standardization of technology and UI/UX.
How it looks:
The organization usually appoints one team to create, maintain and distribute the components. This team is often called the “frontEnd Infra team”.
This team receives a design system from the designers and implements a shared UI library with a bunch of components. Examples are Gestalt by Pinterest, Grommet by HP, Polaris by Shopify and many others.
Then, begins the struggle to achieve true reusability. This basically means that the infra team needs to somehow get all other teams to use these components.
This isn’t a simple task, as other teams fear to couple their apps to the development of a 3rd part library with out-of-the-box components. What happens when the consuming team needs to make a change to a component? maybe adjust some margins? They’d have to PR the library, and wait…
Another problem is the discoverability for the components, which often leads to over-complicated docs sites and wikis that people need to read and browse when they really just need a button and some API examples.
As a result, such libraries tend to get less adoption than desired, and the process for getting them adopted becomes long and tiring for the infra team.
Here are the things we need to achieve to create true adoption for shared components between the “publishing” team and the “consuming” teams:
- Discoverability for the shared components
- Ability to quickly understand and consume components
- Ability to make changes when required from the consumer’s end
Usually, you can choose one of two options:
a) UI Library + Bit
b) UI Library + Lerna + NPM + StoryBook
UI Library + Bit
In this workflow, you will use Bit to manage and share the components from your library. What you’ll get and why:
- Each component will automatically be presented with an example playground, auto-extracted API reference and build+test results. Designers can collaborate on this visualized portal too.
- Individual consumption of components directly from the shared portal without refactoring the library (using Lerna/other tools).
- 2-way collaboration for components, when every consuming developer can install the components using NPM/Yarn but also use Bit to import and change the components right from their projects and share a new version. This helps to dramatically grow adoption for shared components.
- Simpler development of components in the library as Bit automatically handles and lets you update both source-code changes and dependencies.
- Fewer tools in your toolchain.
- New possibilities: For example, you can separate logic from styling. You can share 30 React UI components and 10 theming components, which developers can compose to add styling to the components as needed.
- Gradual refactoring and modularity for components across your codebase.
UI Library + Lerna + NPM + StoryBook
In this workflow, you will use Lerna to refactor your library into a multi-package repo, and publish each to NPM. On top, you will build a docs site for the library, and maybe add StoryBook on top for rapid prototyping.
What you’ll get:
- A refactored multi-package repo with Lerna, from which individual components can be published to NPM.
- A Wiki portal for the components with examples/API reference as you choose, with added features of choice.
- Rapid prototyping for components with StoryBook and friends.
Ultimately, you can combine all of these tools with Bit too- as long as you can create, share and get adoption for shared components. It’s up to you.
It’s important to remember that modular code-sharing doesn’t just keep standardization (which should be achieved, not enforced) but rather to build better software faster through modularity, code reuse and separation of concerns. It helps grow TDD, makes maintenance simpler and helps on-board new developers and new teams to the development of your apps.
When trying to share UI components, you need to address the gap between design, development, usage and collaboration over the components. This process begins by truly understanding your needs, defining your goals and limitations (both human and technological), choosing the best-fit tools for the job, and working to achieve true reusability for the shared components.
If done right, you don’t just share components. You create a design system built of components, speed dev-velocity, build a consistent experience and look for your users across apps and build better modular software.