Posted May 18, 2018 by Bryan Wain
Previously, we wrote about monorepos and how Yarn Workspaces makes working with them simpler. Unfortunately, moving to a monorepo is not always an easy choice. Without the right tooling, a monorepo can often harm the developer experience instead of help it.
The Problem With Monorepos
One of the main reasons projects usually move to a monorepo is because they make it easier to make changes across multiple packages by allowing you make those changes in a single pull request. One of the ways Yarn Workspaces improves this workflow is by automatically symlinking sibling package dependencies in the node_modules folder, allowing you to immediately see the results of a change to one package when working on another.
Unfortunately, while symlinking sibling packages all of the time makes cross-package development easier, it comes with a big downside when you want to work on a single package. If package A
depends on packages B
and C
, you need to build all three packages just to make a change in package A
. In a monorepo with many packages, this can be a very slow process compared to a multirepo setup, where you would just pull down a pre-built version of B
and C
when you install.
These opposing benefits of monorepo and multirepo development often leave developers with a difficult choice. Optimize for cross-package development with a monorepo or optimize for single-package development with many repos. We feel that this is a choice you should not have to make, and our goal with focused workspaces was to help turn Yarn Workspaces into a tool that gives you the full benefits of a monorepo without forcing you to give up the benefits of multiple repos.
Introducing Focused Workspaces
yarn install --focus
is a new installation option available in 1.7.0 that shallowly installs a workspace’s sibling dependencies under its node_modules
folder. Shallow installation can best be explained with an example.
Imagine you have a monorepo with packages A
and B
, where A
depends on B
, and B
depends on External
, which is not part of the monorepo. A normal installation might result in something like this:
| my_project/
| package.json
| node_modules/
| A/ (symlink to packages/A)
| B/ (symlink to packages/B)
| External/
| packages/
| A/
| node_modules/ (empty)
| B/
| node_modules/ (empty)
The problem here is that if you want to run A
, you need to build B
as well. If you haven’t made any changes to B
, this is most likely going to be slower than installing B
from the registry.
If you were to go to packages/A
and run yarn install --focus
instead, the result would look like this:
| my_project/
| package.json
| node_modules/
| A/ (symlink to packages/A)
| B/ (symlink to packages/B)
| External/
| packages/
| A/
| node_modules/
| B/ (not a symlink. Pulled from registry. No need to build.)
| B/
| node_modules/ (empty)
This allows you to run A
without rebuilding B
. Because of the way node module resolution works, when building A
, it will find B
in packages/A/node_modules/B
and avoid using the symlink at the root, which points to an unbuilt version of B
.
Yarn will always do a minimal install when using --focus
. Note that External
is not reinstalled under A
. There is no need because when A
tries to resolve External
, it will already point at the hoisted version.
Dealing With Version Conflicts
Let’s look at a slightly more complicated example. Imagine now that both A
and B
depend on External
, but A
depends on External@1.0.0
and B
depends on External@2.0.0
. A
and B
can no longer share a copy of External
because their versions are incompatible.
A regular install would produce this:
| my_project/
| package.json
| node_modules/
| A/ (symlink to packages/A)
| B/ (symlink to packages/B)
| External/ (v2)
| packages/
| A/
| node_modules/
| External (v1)
| B/
| node_modules/ (empty)
and a focused install would produce this:
| my_project/
| package.json
| node_modules/
| A/ (symlink to packages/A)
| B/ (symlink to packages/B)
| External/ (v2)
| packages/
| A/
| node_modules/
| External (v1)
| B/
| node_modules/
| External/ (v2)
| B/
| node_modules/ (empty)
Yarn needs to not only install B
under A
, but also install External
under the nested copy of B
to ensure that B
uses v2 of External
while A
still uses v1.
Unfocusing
If you want to remove the shallow installations from a focused install, just rerun yarn install
without --focus
. All node_modules
will return to the state they were in before you focused. upgrade
, add
, and remove
will also remove any shallow installations.
You can also focus on another workspace and the original workspace will unfocus.
Focusing by Default
If you would like to have a workspace always use --focus
when installing, you can use the CLI arguments feature of .yarnrc
files.
--install.focus true
If you add that to packages/A/.yarnrc
, you will always do a focused install when you run install from A
, but not from any other packages or the root.
If you instead add it to packages/.yarnrc
, you will do a focused install from all packages under packages/
.
What’s Next?
We hope focused workspaces helps make your migration to a monorepo easier. This is only a first iteration and further improvements will likely be made in the future. If you notice anything wrong or have any suggestions on how we can improve your experience with workspaces, please let us know.