Dependency Resolver
Check out the Dependency Resolver Aspect on Bit.dev
The Dependency Resolver configures and installs dependencies for components in a Bit workspace. It auto-generates the dependency graph for components to spare us the tedious work of manually configuring it ourselves.
It does so by integrating the dependency policies set by various Bit extensions (primarily the workspace) with the components' parsed and analyzed import
/require
statements.
In addition to that, the Dependency Resolver offers an efficient API to manually modify the generated graph using policies that can be applied on groups of components.
#
FeaturesAuto-generated dependency graph: The Dependency Resolver saves us time and effort by generating the dependency graph for each component handled by the workspace. It does so by parsing out all
import
\require
statements in the component's files. It then determines if these dependencies are packages, components or internal implementation files. If they are external components or packages, it goes on to determine their version and dependency-type (dependencies
,devDependencies
,peerDependencies
).An efficient dependency configuration API: The Dependency Resolver strikes the right balance between automation and customization by offering a simple and efficient configuration API. Use it in the workspace configuration file to manually modify the version and dependency type of dependencies in the generated dependency graph. When used in combination with
@teambit.workspace/variant
it allows to define, in a cascading (CSS-like) way, different dependency policies for different sets of components, and even to add or remove dependencies altogether.Optimized dependency installation: The Dependency Resolver directs the package manager to install the correct packages in the correct location in the workspace file structure. When a dependency appears in the workspace dependency graph more than once, the Resolver searches for a common version that satisfies most dependent components and installs it at the workspace root directory, where it can be shared by all relevant components.
dependency versions that are used by a minority of components will be installed in each component's directory.
A single 'install command for packages and components: A single
bit install
command will not only install packages but will also import components listed in your.bitmap
file. These components will then be linked to yournode_modules
directory, to keep the same import pattern for both module types, components and packages.Programmatic API for extensions and environments The Dependency Resolver provides other extensions and environments with an API that allows them to register their own dependency policies. For example, the 'React environment' uses the Dependency Resolver to set 'react-dom' as a peerDependency for all components using it (in case a component author had either left
react-dom
out of its dependencies, or even registered it as a regular dependency).
#
Quickstart & configuration#
Auto-registered dependency version and typeDependency policies define the version and dependency type of each package used by components in the workspace. When installing a package, the Dependency Resolver registers its version in the dependency configuration (if a version is not specified upon installation, it will default to the latest one).
// At the root-level of the workspace configuration JSON{ "@teambit.dependencies/dependency-resolver": { "policy": { "dependencies": { "lodash": "4.17.0" } } }}
#
The dependency policies hierarchyThe Dependency Resolver integrates dependency policies from various sources to determine the component's final dependency graph. Cases of conflicting policies are resolved according to a hierarchy of source types, where a 'higher' rule in the list overrides lower rules.
The hierarchy in a descending order:
- Policies set by Variants (
@teambit/variants
) and thecomponent.json
files of "ejected" components (these two sources are merged by Variants). - Policies set by various extensions/aspects (using
registerDependencyPolicy
) - Policies set by the environment (using
getDependencies
) - Bit’s automated dependency detections, and policies set by the Dependency Resolver at the workspace configuration root-level.
Use the
bit dependencies <component-id>
command to understand the calculations and interactions which resolved to the generated dependency graph of a specific component.
#
Select a package managerThe Dependency resolver does not replace package managers - it uses them and directs them.
To choose between 'Yarn' and 'pnpm', set the packageManager
property to either of the following:
teambit.dependencies/yarn
teambit.dependencies/pnpm
// At the root-level of the workspace configuration JSON{ "teambit.dependencies/dependency-resolver": { "packageManager": "teambit.dependencies/yarn" }}
#
Apply policies on all relevant componentsA dependency policy configured at the root level of the workspace configuration (JSON) will by default affect all components that have that package as their dependency (i.e., components that have this module listed in their generated dependency graph). Components that do not have this package as a dependency will not be affected.
For example:
// At the root-level of the workspace configuration JSON
// Every component in the workspace that has 'lodash' as a dependency will version '3.0.0'.// This policy will not affect any component that does not have 'lodash' as a dependency.
{ "teambit.dependencies/dependency-resolver": { "policy": { "dependencies": { "lodash": "3.0.0" } } }}
#
Apply policies on a selected group of componentsDependency policies can be applied on a specific group of components. This is done using the @teambit.workspace/variants
configuration API.
For example, to set version 1.0.0
of classnames
as a dependency of all components located inside the ./components/react
directory (or any of its sub-directories):
{ "teambit.workspace/variants": { "components/react": { // this creates a variant for all components contained in the components/react directory and it's sub-directories "teambit.dependencies/dependency-resolver": { // this creates dependencies overrides for the current variant "policy": { "dependencies": { "classnames": "1.0.0" } } } } }}
#
Remove a dependencyDependency policies can also be used to move or remove a dependency. That's especially useful when a dependency is defined with an incorrect dependency type.
For example, a module can be "moved" from dependencies
to peerDependencies
by removing it from dependencies
and listing it under peerDependencies
.
"teambit.dependencies/dependency-resolver": { "policy": { "dependencies": { "enzyme": "-" }, "peerDependencies": { "enzyme": "^3.11.0" } }
#
Override cascading policiesPolicies set on a specific group of components will override any conflicting policies that have cascaded from more general configurations.
For example, the following configuration will set classnames
version 1.0.0
on all components using the react-ui
namespace. This policy will override the workspace-level policy that uses version 2.0.0
of that same package.
// All components using the namespace 'react-ui' will use version 1.0.0 of "classnames"// instead of version "2.0.0" which is set as the more general default for all components in the workspace{ "teambit.workspace/workspace": { "name": "my-workspace" }, "teambit.dependencies/dependency-resolver": { "policy": { "dependencies": { "classnames": "2.0.0" } } }, "teambit.workspace/variants": { "{react-ui/*}": { // the {} notation sets the variant grouping via namespace, so in this case any (/*) components whose namespace begins with 'react-ui' "teambit.dependencies/dependency-resolver": { "policy": { "classnames": "1.0.0" } } } }}
#
"Forcibly" add dependencies to a componentDependency policies applied on a selected group of components will "forcibly" add the listed packages to any [selected] component for whom the Dependency Resolved has not already identified that package as a dependency.
This can be useful when a component depends on another module but has no import
/require
statement to be parsed by the Dependency Resolver (for example, in a Webpack configuration file, or as a peerDependency of a consumed package).
In the following example, classnames@1.0.0
will be "forcibly" added as a dependency to any component using the react-ui
namespace.
"teambit.workspace/variants": { "{react-ui/*}": { "teambit.dependencies/dependency-resolver": { "policy": { "classnames": "1.0.0" } } } }
#
Set dependency types#
dev dependenciesDev dependencies are determined by the type of file in a component that uses the dependency.
If it is a development file (e.g, doSomething.test.ts
) then the packages consumed by it are also considered
to be used for development and will therefore be registered as devDependencies
. In cases where a module is consumed by both a runtime file and a development file, the module will be considered as a runtime (regular) dependency.
devDependencies
that are set by the Dependency Resolver will not be visible in the component's configuration. To validate that a dependency is registered as a dev dependency, use thebit show <component>
command.
The list of file patterns to be considered as development files is determined by the various Bit extensions. For example, the @teambit.react/react
environment lists all *.spec.tsx
files as dev files.
Any component using that environment will have its .spec.tsx
files considered as dev files and all these files' dependencies considered as devDependencies
.
#
Register file patterns to be considered as dev filesSet the devFilePatterns
property to add your own list of file extensions to be considered as development files (and to have all their dependencies considered as devDependencies
):
// At the root-level of the workspace configuration JSON{ "teambit.dependencies/dependency-resolver": { "devFilePatterns": [".spec.ts"] }}
#
Configure specific dependencies as devDependenciesDependencies can be directly configured as
devDependencies
only via thevariants
config API.
{ "teambit.workspace/variants": { "*": { "teambit.dependencies/dependency-resolver": { "policy": { "devDependencies": { "react-test-renderer": "17.0.1" } } } } }}
#
peer dependenciesSetting a package as a peer dependency ensures the package manager installs only a single version of that package. If that is not possible, i.e. if there is no single “agreed upon” version for all components in the workspace, then an error will be thrown.
This can be crucial when different components communicate with each other using shared objects that are instantiated by an installed package (the dependency). If different versions of the same package create different object instances then the “means of communication” is broken. There is no single object to address, no single source of truth. This can turn out to be critical when working with modules that are used as “plugins” of another module (for example, Babel), or when working with components that are coordinated in runtime using a shared library (for example, React).
To set a package as a peer dependency, place it under the peerDependencies
entry, like so:
{ "teambit.bit/dependency-resolver": { "policy": { "dependencies": {}, "peerDependencies": { "react": "^16.13.1", "react-dom": "^16.13.1", } }}
Peer dependencies are usually used in the context of a single "hosting code". That could be an application or a single component library. Bit may generate multiple "hosts", one for each environment being used, to run components of different types. That could translate into multiple versions of the same peer dependency, one for each environment.
#
Enforce the installation of a specific package versionThe dependency resolver determines the package version that best fits the requirements of most components consuming the same package. It then installs it at the root of the workspace to make it available to all components sharing the same package (those that cannot use it will have their dependency installed inside their own directory).
As a result, a package installed by the Dependency Resolver may not have the same version that was specified in the Dependency Resolver policy.
To enforce the installation of the exact version specified in the policy, set the preserve
property to true
.
{ "teambit.dependencies/dependency-resolver": { "policy": { "dependencies": { "lodash": { "version": "3.0.0", "preserve": true } } } }}
#
Set a proxy for outgoing HTTP/HTTPS requestsThe package manager can be configured to use a proxy for outgoing network requests.
proxy
- A URL for a proxy to be used in both HTTP and HTTPS requests.
httpsProxy
- A URL specific for HTTPS requests (this will override the value set in proxy
for HTTPS requests).
{ "teambit.dependencies/dependency-resolver": { "proxy": "http://domain-one.proxy.com:8080", "httpsProxy": "http://domain-two.proxy.com:8080" }}
#
A proxy can also be set in NPM's and Bit's global configurationsTo get the value for 'proxy'/'https-proxy':
bit config get proxy
bit config get https-proxy
To set a new 'proxy'/'https-proxy' value:
bit config set proxy http://domain-one.proxy.com:8080
bit config set https-proxy http://domain-one.proxy.com:8080
Read about setting a proxy in NPM's global configuration here
Notice the Dependency Resolver config will override all other proxy configurations. The NPM config will override Bit's global proxy configurations.
#
CLI reference#
installBy default, the Dependency Resolver installs packages from Bit.dev's registry. The authentication for that registry is via your Bit.dev token, listed under
@bit
, in your.npmrc
file. If that token cannot be found in the.npmrc
file, Bit will look for it in your global Bit configurations (use thebit config
command to output youruser.token
property).
If your npm is configured to use a registry other than npmjs's, then the Dependency Resolver will use that configured registry, instead.
The 'install' process includes importing components and linking them to the
node_modules
directory.
#
Install all dependencies listed in the Dependency Resolver configuration:bit install
#
Install the latest version of a package:bit install <package>
// For examplebit install lodash
#
Install a specific version of a package:bit install <package>@<version>
// For examplebit install lodash@1.0.0
#
Install packages that are already listed in the Dependency Resolver policiesWhen trying to install a specific package that is already listed in the Dependency Resolver policies, an error will be thrown.
To override it:
bit install <package> --update-existing
bit install <package> -u
#
Install packages without importing componentsThe 'install' process includes importing components listed in the .bitmap
file and linking them to the node_modules
directory.
To disable importing and install all packages and components as standard packages use:
bit install --skip-import