Building Composable JavaScript Software: Tips and Tricks

How to create composable systems using standardized, independent, and well-documented components of different granularity levels

Eden Ella
Bits and Pieces

--

Composable software is a design approach that aims to simplify the development and maintenance of software systems. It does so by breaking down the system into independent components that can be combined and reused in different ways.

We all build composable systems to a certain extent when we integrate microservices, micro frontends, libraries, or even component-based UI frameworks like React or Vue.

However, not all composable systems are created equal. Some are more ‘composable’ than others. The level of composability depends on a number of factors.

In this article, we’ll look at some of these factors, how they affect the composability of a system, and how we can use Bit to optimize these factors and build more composable systems.

Bit is a build system for composable software, but it’s important to note that Bit itself is also composable. The tips and tricks we’ll cover here apply to Bit itself.

Component Granularity

What degree of granularity is appropriate for components?

On the one hand, fine granularity allows for more flexibility and reusability, as smaller components can be combined in different ways to create different functionalities.

On the other hand, coarse granularity can make it easier to manage and maintain the system, as there are fewer pieces to deal with. Too many fine-grained components can make the system more complex and difficult to understand.

The ideal granularity level depends on the specific requirements of the project and the context in which the components are used.

Software composed of Bit components will have all levels of granularity abstracted away as components

Components are created from the most granular units of code but are assembled into larger components to provide the whole gamut of granularity levels.

The dependency graph of bit.dev reveals components of different levels of granularity

In practice, when composing new systems with Bit, we start by looking for the most abstract components that provide the functionality we need, and then we can drill down to the more fine-grained components if necessary.

Example: a contact-form component. Developers can choose whether to install this component or one of its dependencies.

Component Decoupling

Another important factor that affects a system’s composability is the degree of decoupling between components.

A system composed of loosely coupled components or modules is easier to maintain and scale.

Loose coupling is affected by a number of factors, which can be grouped into two categories: how components are developed and how components are implemented and composed together.

In terms of component implementation and composition, well-defined interfaces and adhering to the SOLID principles can help reduce coupling between components.

Dependency injection, in particular, is a pattern used throughout Bit, using Harmony.

Harmony is a DI framework by the Bit team. It simplifies dependency injection of JS components. It also supports multiple runtimes to enable full-stack components that provide backend services as well as UI for the browser.

An example of an “aspect” — a component that provides a service to other components by “injection”

Dependency injection is one of many patterns that can reduce coupling between components. It’s not a silver bullet, and it’s not always the best solution. Other solutions include event-driven architectures, message queues, and more.

Independent component development and isolated builds

As mentioned above, ‘loose coupling’ also applies to the components' development process.

This is where Bit shines the most. Bit allows us to develop components as independent units — independent of a git repository and a project-level build setup.

Bit components are completely autonomous. They can be developed, built, tested, and deployed independently of the rest of the system.

Even when developed in the context of a project, Bit components are tested and built in isolation to ensure that they are truly independent.

Components built in isolation with Bit’s Ripple CI

As mentioned above, Bit components are of different levels of granularity. Components are composed of other components, which are composed of other components, and so on.

That requires a way to manage the dependencies between components. Bit automates this process by analyzing the code of the components and generating a dependency graph.

To learn more see:

In addition, Bit provides a build system that propagates changes to dependent components so that they can be tested and built.

Component Versioning

An important aspect of composability is the ability to evolve the system over time. This is where versioning comes into play.

Versioning allows us to make changes to the system without breaking existing functionality.

The version history of a lambda component

Bit components are individually versioned using semantic versioning. Each component has a version number incremented to reflect the changes made to the component (patch for bug fixes, minor for new features, and major for breaking changes).

Independent versioning of components allows us to evolve the system over time without breaking existing functionality. Different systems can use different versions of the same component to allow for the gradual adoption of new component releases.

Component documentation and examples

Another important factor affecting a system's composability is the availability of documentation and examples for the components. It allows developers to understand better the component functionality and how to use it. Bit provides a number of tools to help with this.

Component API references are autogenerated by Bit:

Components include visual previews of the components in different states and contexts:

Non-ui components can use the preview to display mock data or visual examples of their outputs:

Component documentation can be customized with manual docs and live playgrounds:

Component Discoverability

Component documentation is not only a guide for developers looking to use a component but also a way to discover and identify the component as the right component when composing new systems.

Unlike the former sections, which described different component attributes, component discoverability is not only a matter of the components themselves but also an attribute of the platform that hosts them.

Bit components are stored in remote scopes, often hosted by bit.cloud. Having components organized by their business concern, in different scopes, and easily searchable maximizes component reuse and streamlines new compositions.

scopes maintained by the Bit team

Conclusion

When building composable software, we have to decide how components will be implemented and assembled. Implementing components as Bit components is a great starting point.

Bit components are completely autonomous. They can be developed and maintained as independent codebases and integrated into systems in any way we choose. A library? A microservice? It is up to you to decide.

--

--