Advanced Bit Dependency Management and Configs

zo
zoltan2 years ago

Our goal at Bit is to make bit install work for everything and everyone out of the box. However, the JavaScript ecosystem is huge and sometimes some tweaks are required to make your tooling work properly. In this post, I will write about a few configuration settings that can change how bit installs your dependencies.

In case you prefer to watch rather than read, you can see me talking about Bit dependency resolver settings here:

Choosing the package manager and the linker

When you use Bit, you can choose between two package managers: pnpm and Yarn. By default, when you create a new workspace, pnpm is set as the package manager.

/**
   * main configuration for component dependency resolution.
   **/
  "teambit.dependencies/dependency-resolver": {
    /**
     * choose the package manager for Bit to use. you can choose between 'yarn', 'pnpm'
     */
    "packageManager": "teambit.dependencies/pnpm",
    "policy": {
      "dependencies": {},
      "peerDependencies": {}
    }
  },
CopiedCopy

To change the package manager to Yarn, just modify the packageManager field to teambit.dependencies/yarn, remove the node_modules directory, and run bit install.

Both pnpm and Yarn have two modes for linking the node_modules directory: isolated and hoisted. This can be configured using the nodeLinker setting. By default, pnpm uses the isolated linker while Yarn uses the hoisted one.

By default, Bit will use pnpm with the isolated linker. It is the optimal choice as the isolated linker creates a clean node_modules where only the direct dependencies of the workspace are in the root. For instance, if you have is-odd in your dependencies, this is how your node_modules will look like:

node_modules
├── .pnpm
│   ├── is-odd@3.0.1
│   │   └── node_modules
│   │       ├── is-odd
│   │       └── is-number --> ../../is-number@6.0.0
│   └── is-number@6.0.0
│       └── node_modules
│           └── is-number
└── is-odd --> ./.pnpm/is-odd@3.0.1/node_modules/is-odd
CopiedCopy

As you can see, only is-odd is in the root of node_modules, and even that is only a symlink to the sources which are inside the store (node_modules/.pnpm). The dependency of is-odd is not hoisted to the root node_modules but is symlinked instead to the dedicated node_modules directory of is-odd. This is a clean structure where everything is well organized and each dependency has only access to its dependencies.

However, the isolated linker uses symlinks, and some tools in the JavaScript ecosystem don't work well with symlinks (for instance, React Native). For those cases, you may set the nodeLinker setting to hoisted:

/**
 * main configuration for component dependency resolution.
 **/
"teambit.dependencies/dependency-resolver": {
  /**
   * choose the package manager for Bit to use. you can choose between 'yarn', 'pnpm'
   */
  "packageManager": "teambit.dependencies/pnpm",
  "nodeLinker": "hoisted",
  "policy": {
    "dependencies": {
      "is-odd": "3.0.1"
    },
    "peerDependencies": {}
  }
},
CopiedCopy

With this setting, no symlinks will be used and node_modules will look like this:

node_modules
├── is-number
└── is-odd
CopiedCopy

As you can see, both is-number and is-odd are in the root of node_modules.

Overriding dependencies

In some cases, you might want to change what dependencies are installed for your dependencies. Let's consider a few scenarios.

  • Maybe is-odd has is-number@5.0.0 (as an exact version) in the dependencies but is-number has a vulnerability in that version. In this case, you want to force is-number with the security fix to be installed instead of v5.0.0.
  • Or maybe you want to deduplicate dependencies.
  • Or you have forked a subdependency and want to replace the original package with your fork.

For these cases, you may use the overrides field in the dependency-resolver config. In the example below, any version is-number is replaced with v5.0.1:

/**
 * main configuration for component dependency resolution.
 **/
"teambit.dependencies/dependency-resolver": {
  /**
   * choose the package manager for Bit to use. you can choose between 'yarn', 'pnpm'
   */
  "packageManager": "teambit.dependencies/pnpm",
  "nodeLinker": "hoisted",
  "policy": {
    "dependencies": {
      "is-odd": "3.0.1"
    },
    "peerDependencies": {}
  },
  "overrides": {
    "is-number": "5.0.1"
  },
},
CopiedCopy

If you want to only replace is-number in the dependencies of is-odd, you can use the next override:

{
  ...
  "teambit.dependencies/dependency-resolver": {
    ...
    "overrides": {
      "is-odd>is-number": "5.0.1"
    },
  },
  ...
}
CopiedCopy

Or you may also override only a specific version of is-number. The next override will only replace is-number@5:

{
  ...
  "teambit.dependencies/dependency-resolver": {
    ...
    "overrides": {
      "is-number@5": "5.0.1"
    },
  },
  ...
}
CopiedCopy

If you want to replace is-number with your fork, you may use the alias syntax in the override:

{
  ...
  "teambit.dependencies/dependency-resolver": {
    ...
    "overrides": {
      "is-number@5": "npm:@my-fork/is-number@5.0.1"
    },
  },
  ...
}
CopiedCopy

So, overrides are very powerful and may help out in some scenarios. However, be aware that overrides work only during local development, so they will not have any effect when someone imports or installs your components.

Muting peer dependency warnings

As of now, Bit doesn't show peer dependency warnings, when pnpm is used. However, it is important to install any required peer dependencies of your dependencies, so we plan to turn these warnings on in the future. For now, you can opt-in to peer dependency warnings by setting peerDependencyRules:

{
  ...
  "teambit.dependencies/dependency-resolver": {
    ...
    "peerDependencyRules": {
      "allowAny": [],
      "ignoreMissing": []
    }
  },
  ...
}
CopiedCopy

Now let's install a dependency that requires a peer dependency.

bit install with missing peer dependency warning

As you can see, ts-node requires typescript to be in the dependencies as well. The right way to fix this would be to install typescript v2.7 or newer. But if you know that in your case typescript is not needed, you may ignore this warning by adding typescript to peerDependencyRules.ignoreMissing:

{
  ...
  "teambit.dependencies/dependency-resolver": {
    ...
    "peerDependencyRules": {
      "allowAny": [],
      "ignoreMissing": ["typescript"]
    }
  },
  ...
}
CopiedCopy

ignoreMissing may also ignore package by patterns, so, for instance, you may ignore all issues with babel as well, using:

{
  ...
  "teambit.dependencies/dependency-resolver": {
    ...
    "peerDependencyRules": {
      "allowAny": [],
      "ignoreMissing": ["typescript", "@babel/*"]
    }
  },
  ...
}
CopiedCopy

Now let's install an unsupported version of typescript:

bit install with bad peer dependency warning

If you know for sure that the dependency you are installing works fine with the peer dependent, you may allow either any version of the peer or specific versions. The configuration below will tell the package manager to accept typescript v1 as a valid peer for any dependency:

{
  ...
  "teambit.dependencies/dependency-resolver": {
    ...
    "peerDependencyRules": {
      "allowAny": [],
      "ignoreMissing": ["typescript", "@babel/*"],
      "allowedVersions": {
        "typescript": "1"
      },
    }
  },
  ...
}
CopiedCopy

But you can also ignore all warnings related to peer dependency version mismatches, using the peerDependencyRules.allowAny field that also accepts patterns. The below configuration will allow any version of react and any version of babel to resolve any peer.

{
  ...
  "teambit.dependencies/dependency-resolver": {
    ...
    "peerDependencyRules": {
      "allowAny": ["react", "@babel/*"],
      "ignoreMissing": ["typescript", "@babel/*"],
      "allowedVersions": {
        "typescript": "1"
      },
    }
  },
  ...
}
CopiedCopy

Summary

In most cases, bit install will work with any stack and no configuration changes. But if you have issues with dependencies due to some buggy tools in the ecosystem, you should try setting nodeLinker to hoisted. If changing the nodeLinker doesn't help, or you know exactly which packages cause issues, the overrides field might help.

Bit handles most of the peer dependency issues out-of-the-box using environments. But some 3rd party non-component dependencies might also use peer dependencies. For those cases, peer dependency warnings are great for identifying issues. However, peer dependency warnings are sometimes too strict, so you can use the peerDependencyRules field whenever some warnings should be muted.

These are not the only settings that you can set for the dependency resolver aspect. See these docs for all the settings.

Zoltan is the master of dependencies and packages at Bit and the maker of pnpm. He likes Tacos🌮.