Best Practices
Below you can find some guidelines that can help make Bit an efficient tool in your organization:
Check out the Reusable components styleguide to learn how to make your components more robust for sharing.
Component Completeness
Components should have a sole responsibility. In other words, a component represents a clear and meaningful functionality.
When tracking files as components, include all files related to that functionality and that are only relevant to this functionality.
Each component should include the code, styling, unit tests, documentation, and usage examples, such as storybook stories.
Ensure Singleton Objects
Some libraries need to be singleton in a project. For example, you cannot have more than one instance of framework libraries during run time such as React or Angular. To ensure the libraries are singletons, and assuming the containing project has those libraries, we should define them as peerDependencies for our component.
The peerDependencies version should also be as relaxed as possible, e.g. "react": ">=16.9.0"
. This will cover a wide range of versions used in the consuming project. If the peerDependency version range does not cover the range installed in the consuming project, the package is installed with multiple versions.
Two methods to define peerDependencies for Bit components:
- Define the dependency in the authoring project. Bit dependency algorithm takes the package as peer dependency.
- Provide override rules for setting the packages as peer dependencies.
You can run bit show
to view the components dependencies before tagging and tracking the component. There you can see the exact dependencies the component has and verify the dependencies are marked as peers.
Use Namespaces
You can use namespaces inside a scope to group related components. Namespaces act as folders inside a Bit workspace, or inside a scope on bit.dev.
To track a component under a namespace, add the namespace with a slash on the component's id:
$bit add src/utils/my-util.js --id utils/my-utils
tracking component utils/my-utils:
added src/utils/my-util.js
You can also use the bit DSL to add multiple components in a single add
command and use a namespace.
Specifying a namespace lets you perform actions on multiple components at once:
$ bit tag "utils/*"
Namespaces are also useful in specifying overriding rules for specific components. For example, you can override a compiler for all components under the utils/*
namespace:
{
"overrides": {
"utils/*": {
"env": {
"compiler": "@bit.envs/compilers/typescript@3.0.34"
}
}
}
}
Publish shared files
If multiple components use the same file or directory, e.g., helpers
or utils
, extract the common code into its own Bit component. Consider splitting those components by their functionality.
Publishing a shared file together with another component creates an undesired and unneeded coupling between components that is not inherent to their functionality. By splitting shared modules into smaller ones, consumers can import the specific functionality they desire, with a slimmer dependency graph.
It is recommended that all such files resides under the same namespace.
Handling Assets
Components may require using assets from your projects, such as images, graphics, or fonts.
You can define assets component that do not require a compiler. To simplify removing a compiler, group all assets under a dedicated namespace, such as assets
. Then, in the package.json, you can specify that all components under the assets
namespace do not include a compiler, by using the overrides option:
{
"overrides": {
"assets/*": {
"env": {
"compiler": "-"
}
}
}
}
Images
Multiple ways to handle images and fonts:
- Publish the assets to a CDN and access them in the components via full path URL.
- Include the assets files in the components that use them and share the component, or wrap the asset in a dedicated component and include the image and publish it.
- Share the image as component and import it. See this example.
SVGs
SVGs are in fact plain html. A good approach for handling SVGs is described in this article.
It is also possible to include the assets in their own components and reuse them, among other components.
Assets only components should not be associated with a compiler, as the compiler cannot find a proper entry point to start the compilation.
Handling Styles
Typically, an application contains style files that shared between different components in the application. Styling files may be pure CSS or using a pre-processor such as scss
or less
.
An application may also contain a set of variables (e.g., scss variables) used as design tokens to denote reusable elements such as colors or breakpoints.
Those variables are reused across multiple components, and thus should be created as their own components. You can define them as a single component or as a set of separate components. If the styles are split across multiple components, it is highly recommended to group them under a dedicated namespace such as styles
, to facilitate working with them.
The style files are targeted to be eventually processed by the containing project. This is especially critical if a matching process runs that aligns styles with the relevant HTML (as an example, React CSS Modules is creating a style hash that matches the class in the generated Html). Therefore, components that only contain styles do not need a compiler associated with them.
The simplest way to remove the compiler from the style only components is to specify an override rule in the package.json. Grouping all the styles components under a single namespace simplifies the rule as follow:
{
"overrides": {
"styles/*": {
"env": {
"compiler": "-"
}
}
}
}
Styles only components are now available for consumption, and in the target application, the CSS files are processed and bundled by the application's bundler.
Components' Paths
Use absolute paths for imports and define path aliases in your project to resolve relative paths. As a rule of thumb, you should use current and forward references (i.e., ./slider.component
) and avoid backward references (i.e., ../button
).
Having a path like ../../../components/component.ts
makes it hard to move the component files around. Relative paths also couple the component to the specific file structure of the project in which the component was shared and hence created a more complex component file structure in the consuming project.
Components Tagging
Tags work as commits in Git. When exported, all the intermediate tags are also available for consumption by other developers.
Just like a committed code, it is essential to:
- Tag complete work.
- Test before you tag.
- Use SemVer to communicate changes by using
patch
,minor
, andmajor
versions. - Make the tag messages meaningful.
State managers
Components may use state managers such as Redux, MobX, React Context or VueX.
These managers may be difficult to encapsulate, since they tend to be contextual and global.
For example, a user avatar component may read state.data.profile.user
from the Redux global state, and use action Logout(username)
.
To use this avatar in another project, the consumer would be forced to use Redux, with the same structure, in their project.
This clearly makes the component less reusable, and less attractive to consumers.
Here are some workarounds:
Decouple component from the state manager
This is the recommended method. If the component receives its state and actions directly as arguments, and does not use the context api, it is completely reusable, and can even be used with another state manager. Most state managers support this, and only provide a thin state injector on top of the component. For example:
@connect(state => ({ isLoggedIn: state.data.profile.user }), { Logout })
class UserAvatar extends React.component{
...
}
in this case, the connect()
method injects relevant state to the component.
Separate the code into a "dumb" component (just the UserAvatar), and the thin wrapper (connect).
The "dumb" component is now shareable. The wrapper is coupled to the original project, and gives little value to make into a component.
You could use UserAvatar directly in other projects, or create a thin wrapper that is appropriate for that specific project.
Encapsulate the state inside the component
Some components benefit from internal state, and can be safely exported with the manager as a dependency or a peer dependency. A great example of this is ReactDnD.
State component
If the component cannot be isolated from the state, it is possible to encapsulate the state as part of the component.
To make encapsulation easy, it is better to use micro-state, that follow the Single Responsibility Principle.
For example, Current-User can be a shared component that has both UI and a state. The component can export a state, a Context class, a Redux reducer or a Mobx observable.
However, it is unlikely that the component can be really usable across projects, especially when they scale. This may create undesired coupling between projects.
Working with VCS (git)
It is recommended to commit the following to your VCS (e.g., git) from your workspace:
- Workspace configuration. The configuration may reside in
package.json
orbit.json
in the root of the workspace. - Bit index, i.e.
.bitmap
file in the root of the workspace. This file is used to restore all the components exported to the server by using thebit import
command. - Optionally, you can commit imported components, but you can also restore them from the server. However, any changes made to local components after they are imported and are not re-exported to the server should be committed.
The components storage (scope) should not be committed. By default, it is created under the git
folder, so they are gitignored.
Build Components for Discovery
Build components in a way that they can be easily discovered by other developers. That includes proper naming, adding documentation, tagging the components with meaningful labels, and adding examples so that they can be played with. When publishing a component, it is best to think about how other developers are likely to search for the components.
Non-descriptive naming (such as utils) or bad tags makes the components hard to find. Developers are more likely to select and reuse components that they can interact with and quickly evaluate their functionality. Good documentation promotes quick, widespread adoption.
Add multiple examples of component usage, showing how the different inputs should be used.