Bit Components: The Web’s New Building Blocks

Why everything you know about microservices, micro frontends, monorepos, and even plain old component libraries is about to change

Eden Ella
Bits and Pieces

--

Bit components revolutionize the way we build web projects. They replace or transform much of today’s software development strategies and architectural styles and offer more effective ways to collaborate on code. Even so, they seem to have kept themselves behind a veil of mist — misunderstood and unexplained.

This article is an attempt to provide a clear explanation that will enable you to draw your own conclusions as to how independent components can be used to fit your own needs.

What is a component?

A component is any group of files that serves a single purpose. That can be a React component, a Node.js module, a CSS module, etc. Each of these can be of different levels of complexity and concreteness. For example, a component can be a full page or a simple UI element. It can be a small utility function or an entire microservice.

What are independent Bit components?

Bit components are independently developed, versioned, and collaborated on. They do not require traditional source-control systems like Git, or project-level tooling and build setup since everything an independent component needs is self-contained.

Bit components enable the composition of truly modular full-stack web projects, with new possible design patterns and architectural styles that add robustness to every web project and enable more effective collaboration methods.

Each Bit component contains the version history of its source code, dependency graph, dev environment setup, and even build artifacts (since components are made to be consumed).

An Bit component contains the entire version history of its source code, configurations, and artifacts.

A network of dependencies between distributed components

Bit components form a network of component dependencies across separate remote scopes. ‘Scopes’ are component hosting servers that enable collaborative actions like fetching components and updating components with new ‘snaps’ or versions.

This network of dependencies enables changes to propagate from one modified component to all its dependent components. That is, a change made to one component triggers the component’s CI and the CI of its dependent components in a cascading fashion.

Bit components make the polyrepo-monorepo dilemma obsolete, as we get the best of both worlds: simple codebases, efficient component-driven CIs, different permission levels for different components, and so on.

A modified component triggers the CI of its dependent componetns (components are not required to be present in the same workspace or repository)

Using Bit components for microarchitectures and component libraries

Bit components can play various roles in backend and frontend applications. They can play the role of (in-memory) libraries when used as standard packages or of separately-run apps and services that communicate over the network when deployed independently.

An independent component — a full-page — deployed independently as a micro frontend

The road to complete independence

Bit components are developed, source-controlled, and composed together in Bit Workspaces.

This section explores the different steps an independent component goes through, beginning with a few source files and ending in a set of shareable git-like objects.

The workflow and commands presented below are used only for educational purposes. Real component development workflows use more abstract commands that streamline the development process. To learn more, read the official Bit documentation.

The path of an independent Bit component, from multiple source files to a set of shareable git-like objects. Much of the steps are autogenerated by Bit.

1. Starting with a few files

$ cd <path/to/workspace-directory>
$ bit init --harmony # init a Bit workspace to manage components
$ touch ...

Every independent component starts its journey as a “regular component”. This step can be done using pre-configured component templates or by creating your own files.

# Example component
.
├── card
├── card.composition.tsx # Bit file for component previews
├── card.docs.mdx # Bit file for component docs
├── card.module.scss # Styles
├── card.spec.tsx # Tests
├── card.tsx # Implementation
└── index.ts # Entry file

2. Mapping files to a single abstract unit, a component

$ bit add <path/to/component>

The component’s files are mapped to a single component ID. This is key to versioning and configuring multiple files as one discrete unit, a component.

Bit auto-generates the .bitmap file to track ‘components’ in a Bit workspace. The .bitmap file will later reference to specific component versions used by the workspace (the working directory).

/* THIS IS A BIT-AUTO-GENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. */{
"button": {
"scope": "",
"version": "",
"mainFile": "index.ts",
"rootDir": "components/ui/button"
},
"trim": {
"scope": "",
"version": "",
"mainFile": "index.ts",
"rootDir": "components/utils/trim"
},
}

3. Configuring a component with dependencies and a dev environment setup

When authoring components in a monolithic project, the dependencies, development environment setup, and CI/CD, are all configured for the project, as a whole.

In Bit workspaces, each independent component has all configurations set on it as an independent project. When versioned, these configurations are stored as part of the component’s config and metadata.

Once an independent component is cloned (‘imported’) into another Bit workspace, its runtime and development environments are generated using its metadata. This step is crucial for simple and easy component-driven collaboration.

Bit uses a combination of tools and methodologies to simplifiy the process of component configuration. It does so by:

1. Auto-generating the component’s dependency-graph

2. Encapsulating pre-configured development environments into a single indepndent component. These environment components, as well as other components used for development, are then registered as dependencies of the authored component.

4. Generating the component’s distributable code and artifacts

$ bit build <component-id>

In addition to a component’s source files and configurations, an independent component encapsulates in its compiled code, generated node package, documentation, and other artifacts valuable for consumers and maintainers of that component.

The component build process happens in a separate directory, isolated from the components hosting project (its workspace), to ensure a truly independent component.

A component tested inside its workspace may pass all tests but still fail once consumed in a different project. That may happen when packages and files are present in the hosting project, the Bit workspace, but are absent from the component’s configurations (and therefore will not get generated in the isolated direcoty).

5. Tagging a component with a release version

$ bit tag <component-id> <version> --message "message"

The tag command executes the component build. For the sake of clarity, the two steps are listed here, separately. Moreover, a ‘bit tag’ can be replaced by ‘bit snap’ to generate a new component verion or “commit” without a release version.

Once a component has gone through its build process, it is ready to have its source code, configurations, and artifacts committed and tagged with a new release version. This process uses git under the hood but is not identical to how git works.

Much like git, Bit handles component versioning using content-addressable storage that uses hashes and objects to manage the versioned component’s files and metadata. Bit’s storage for the component’s release version is called “scope”. You’ll find it in the .bit or .git/bit directory at the root of your Bit workspace.

Run bit cat-scope to list all objects in your local scope.

Learn more about Bit objects here:

Exporting and importing Bit components

Exporting (“pushing”)

$ bit export
  • When exporting a component, its ‘objects‘ are exported (pushed) to a remote scope.
  • ‘Objects’ relating to different components can be pushed to different remote scopes, each according to the scope configured specifically for it.

Importing (“cloning”)

$ bit import <component-id>

When importing a component into a Bit workspace, the following steps take place:

  1. The component’s objects are downloaded to the workspace .bit or .git/.bit directory.
  2. The component’s source files are extracted from its objects and placed at the workspace root, nested in a directory named by the component scope (workspace-dir-name/component-scope-name/component-name/... )
  3. The component's package is extracted from the component’s objects. It is placed in the workspace node_modules directory. The component’s source files are symlinked to that directory, as well (workspace-dir-name/node_modules/@scope-owner-name/component-scope-and-name ).
  4. The component’s dependencies are installed.
  • The component’s configurations are downloaded and extracted, as well. To see your imported component configurations run: $ bit show <component-id>

For example:

Building the web with Bit components

Microservices and Micro Frontends

Microservices and Micro Frontends, in all their shapes and forms, are all about end-to-end modularity. That is, maintaining separate services or features as decoupled codebases that are authored, maintained, and delivered independently. Both of these architectural styles aim to achieve more resilient software and more effective collaboration between autonomous teams, working in parallel.

A key element of all microarchitectures is structuring applications out of decoupled, small, and simple codebases that are easy to understand, develop, and test.

Bit components can play various roles in backend and frontend applications. They can play the role of (in-memory) libraries or that of separately run apps and services that communicate over the network (as mentioned earlier, an independent component has its own CI/CD setup).

Regardless of how they’re used, Bit components are the perfect building blocks of every microarchitecture.

Monorepos

A monorepo is a single repository used for several projects. It is a way to centralize the development workflow, share code more easily, and simplify the dependency management of 3rd party libraries. A monorepo DRYs up your organization’s code and enables cross-project and business-centric collaboration.

As mentioned earlier, Bit generates the dependency graph of not only each component but also that of the entire workspace. Changes to a single component are propagated to dependent components, triggering component builds and versioning.

When used with bit.cloud for component hosting and CI, each independent component becomes part of one virtual monorepo where changes to one component not only propagate inside the limits of the same workspace but across all dependent components hosted on bit.cloud.

Bit components provide all the benefits of a traditional monorepo without well-known scalability challenges like inefficient builds and version control.

Design systems and component libraries

Component libraries are a great way to share code and keep a consistent design & behavior across decoupled projects.

A component library built as a single monolith is versioned as a single unit, built as a single product, and consumed as a single package. All that affects consuming projects in undesirable ways, such as an unnecessarily large bundle size (tree-shaking is not flawless), meaningless updates (updates that have nothing to do with the components a project uses), and more.

Bit components solve many well-known challenges produced by monoliths but offer much more than that. One such benefit is the ease with which better architectural styles can be implemented.

Better architectural styles are styles that are focused on composability. They allow for flexibility in the way components are used and enable the rapid composition of larger structures all the way to full applications. Structuring “component libraries” out of a network of distributed Bit components enables greater access to the library's different aspects (for example, the color-palette CSS component) and underlining building blocks (for example, the fundamental UI elements of which the “library” is composed of).

--

--