Component-Driven Development with Bit

Using Bit to solve all major challenges presented by the CDD development strategy.

Eden Ella
Bits and Pieces

--

In the world of web dev, component-driven development (CDD) has become almost synonymous with component-driven user interface. This limited use of the term is quite unfortunate as it has conceptually distanced us from better software design and development strategies that are by no means exclusive to UIs.

For an updated version of this article:

What do I mean by CDD/CBD?

Component-based development is the structuring of software out of components or modules, where each component addresses a specific concern and exposes a well-defined interface for other components to communicate through.

Components can be of all types and shapes, for frontend and backend, UI and non-UI, they can be simple units or complex composites.

Do we really use CDD as a development strategy?

CDD checks many important NFR boxes. It makes our code more reusable, more maintainable, more testable, and easier to understand and debug.

But if that’s the case, and CDD is all sunshine and roses, why do we see such limited use of that development strategy?

For example, what’s usually considered as a “proper implementation” of CDD is to have one team develop and share a small set of UI components, the “design system”, for other teams in an organization to use. Each team carries on this bottom-up development process, where components are used to compose more complex composite components. However, that all happens “in private”. Compositions, i.e, composite components, usually remain in their own hosting project, “hidden” from other teams/projects.

To fully enjoy the benefits of CDD — to accelerate development, and maintain high standards of code, components need to be shared and collaborated on across the entire organization. This process cannot be limited to just a small team “prescribing” a small set of components to the rest of the organization.

Collaborating on individual components is hard

Achieving cross-project component collaboration is not easy. It requires solving a long list of challenges. How should these components be collaborated on?

Should you use a separate repository for each component? How will you maintain consistency in their development environment setups? How do you update a ‘feature’, a composite component, when its building blocks are each source-controlled separately?

Perhaps you should use a monorepo for all components? How do you set different permission levels for different components in the same repository? How do you make sure builds are running only when and where they are relevant?

The list of challenges hardly stops here, but you get point.

Using Bit for component-driven development

Bit is an end-to-end solution for component-driven development that solves all major challenges presented by that development strategy.

It offers three main features critical for component development and component collaboration:

  • Isolated component development — including isolated rendering, tests, and builds.
  • Component source-control — essentially, git for individual components.
  • Component dependency management —auto-generated component dependency graphs, and smart dependency management. That includes two types of dependencies: node packages and other components.

Independent components: the new building blocks

An independent component is a component that is independently developed, source-controlled, and collaborated on.

Independent components are developed and versioned in Bit workspaces and pushed to ‘remote scopes’, remote hosting for components, where they’re made available to be consumed and collaborated on in other Bit workspaces.

Unlike a standard source-controlled project, an independent component includes built artifacts that are useful for consumers and maintainers of it. For example, its distributable Node package, component previews, build logs, etc.

An independent component contains the history of its source files, configurations, and generated artifacts

An independent component starts its way as a few files. Bit will map these files to a specific component ID, and from that point on, track them and manage them as one discrete unit, a component.

Let’s say I’m developing a ‘button’ React component, with the following files:

├── my-workspace          
└── tech-jokes/ui/elements/button
├── button.composition.tsx # component simulated previews
├── button.docs.mdx # component documentation
├── button.module.css # styles
├── button.spec.tsx # tests
├── button.tsx # implementation file
└── index.ts # the component's entry file

To start tracking it using Bit, i.e, to start source-controlling it independently and manage it as a component, I’ll run the following:

$ bit add tech-jokes/ui/elements/button --namespace ui/elementstracking component ui/elements/button:
added button.composition.tsx
added button.docs.mdx
added button.module.css
added button.spec.tsx
added button.tsx
added index.ts

To build it (test it, compile it, generate a package, etc.) and tag it with a new release version, run:

$ bit tag ui/elements/button 1.0.0 --message "initial release"new components
(first version for components)
> ui/elements/button@1.0.0

You might be wondering, who configured the build worflow? more on that later :)

To export (push) the ‘button’ component to a remote scope, run:

$ bit exportexported the following 1 component(s):
our-org.tech-jokes/ui/elements/button

The specific remote scope is set in the workspace configuration file.

The component is now available in https://bit.dev/our-org/tech-jokes/.

The exported component in its remote scope

Component dependency management

Bit auto-generates your components’ dependency graph and handles the dependencies between components. That means the dependency graph of an independent component will contain not only Node packages but also other independent components.

This new dependency graph is what enables a cross-project component collaboration.

One example of that is Bit’s CI which propagates from one modified component to all its dependent components. That is, it tests, builds, and tags with a new release version, each of these dependent components.

When used with bit.dev, Bit’s CI runs on dependent components across remote scopes (and not only in a single local Bit workspace).

Bit.dev’s “Ripple CI” going through all the dependent of the updated ‘tabs’ components

Building a new composite component out of independent components

Let’s say I want to create a new composite component, useJokes . This component will be a React hook that fetches jokes and manages them in the local storage. It will be composed out of two independent components (also hooks) useRemoteJokes and useLocalJokes .

Importing independent components is the same as importing packages:

// ...import { useLocalJokes } from '@our-org/tech-jokes.hooks.use-local-jokes';import { useRemoteJokes } from '@our-org/tech-jokes.hooks.use-remote-jokes';export const useJokes ...

Independent components, whether are authored in your local workspace or cloned into it, are always required/imported using their absolute node package name — not their relative paths. This is done in order to keep components agnostic to any specific context.

Bit makes that possible by recompiling the dist files in the component’s package whenever the source files change.

Going to the dependencies tab in the workspace UI (or remote scope UI) I can see that Bit has automatically added the imported components into the useJokes dependency graph. That means any change to either of these components will result in a CI running on the composite component, the useJokes hook.

see this component’s dependencies its remote scope: https://bit.dev/our-org/tech-jokes/hooks/use-jokes/~dependencies

To validate this independent component functions properly when it is used by consumers of it, I’ve also created a ‘Bit composition’. This ‘composition’ is sort of a mini-app that tests this component in possible contexts (in that specific case, since it is a React hook, using it in the context of a React component is quite the obvious thing to do).

Everything is componentized, even development environments

Bit componentize everything. That includes complete component development environments, which can be shared and collaborated on just like any other independent component.

That’s important both for maintaining consistent development environments across independent components and to simplify component collaboration (no need to set up an environment ourselves just to update a component).

For example, the following workspace configuration (workspace.jsonc ) sets a React environment on components under the namespace ui and a Node environment for components under the namespace utils :

{
"$schema": "https://static.bit.dev/teambit/schemas/schema.json",
"teambit.workspace/workspace": {
"name": "my-workspace-name",
"defaultDirectory": "{scope}/{name}",
"defaultScope": "my-scope"
},
"teambit.dependencies/dependency-resolver": {
"packageManager": "teambit.dependencies/pnpm",
"policy": {
"dependencies": {},
"peerDependencies": {
"react": "16.13.1",
"react-dom": "16.13.1"
}
}
},
"teambit.workspace/variants": {
"{ui/**}": {
"teambit.react/react": {}
},
"{utils/**}": {
"teambit.harmony/node": {}
}
}

}
Bit’s official React and Node component environments (shared as independent components)

Each of these component development environments is packed with tools and configurations. For example, the React dev environment uses TypeScript for compilation, Webpack for component preview bundling, Jest for testing, ESLint for lining, and its own specific build workflow.

These tools will all run using the same CLI commands, regardless of the specific tool and configurations set for each task: bit test , bit lint , bit compile , bit build , etc.

Conclusion

Bit makes component collaboration safe and easy. We no longer have to try to solve the challenges of CDD ourselves, we can use Bit as an end-to-end solution. And, if anything’s missing or needs to be changed, we can easily extend and customize it to our own needs.

Bit was built to be as extensible as a tool can possibly be.

--

--