yarn: setting up workspaces

2020-10-08

 | 

~3 min read

 | 

554 words

Yarn workspaces are ways to register applications with Yarn without needing to install them globally and even avoid a link or pack (both of which mirror to the global /bin directory for node).

Let’s take a contrived example and see how it might work and why it’s useful.

Imagine you have a CLI that you’re developing, mycli. It starts as a humble directory

.
└── mycli
    ├── README.md
    └── bin
        └── run

We would be able to run this cli from the command line with

$ ./mycli/bin/run

Note: We don’t need to tell the shell to use the node interpretter because the run script starts with a shebang:

run.js
#!/usr/bin/env node
//... entry point

But how could we register it so that we can just call it as mycli? The first and most straightforward way is to set the bin property within package.json and then install it globally:

package.json
{
    "name": "mycli",
    "bin": {
        "mycli": "./bin/run"
    }
}

Then, if the cli is published to a package registry like npm, you can install it globally:

$ yarn install -g

If it’s not, you can link it:

$ yarn link

But today, we want to talk about workspaces which give us the same benefits of rapid iteration of our CLI without polluting our global namespace or having to manage links. Even better, we can use the application as if it’s installed within other packages within the workspace.

The first thing to understand about Yarn Workspaces is that they are built around a monorepo approach. By this, I simply mean that in order to use a package within a workspace as if it’s installed, it needs to be managed by a shared package.json.

The bare minimum we need in this controlling package.json are two properties: private and workspaces. The latter is where we’ll store our CLI.

package.json
{
    "private": true,
    "workspaces": ["packages/*"]
}

Now run yarn to register our project. Our project directory now looks like:

.
├── package.json
├── packages
│   └── mycli
│       ├── README.md
│       └── bin
│           └── run
└── yarn.lock

At this point, as long as we’re within the project, we can run our CLI with:

$ yarn mycli

While that’s helpful, we can go one step further and enable the workspace to be run within a series of examples that we create alongside the source code for the CLI.

For example, let’s add an examples folder:

.
├── examples
│   └── package.json
├── package.json
├── packages
│   └── mycli
│       ├── README.md
│       └── bin
│           └── run
└── yarn.lock

The examples/packages.json is very basic and just requires a name:

example/package.json
{
    "name": "example",
    "version": "1.0.0",
    "main": "index.js",
    "license": "MIT"
}

In our package.json in the root, we’ll update our workspaces to include example:

package.json
{
    "private": true,
    "workspaces": ["packages/*", "example"]
}

Now, we can specify that we want to run mycli from within the example workspace from the command line:

$ yarn workspace example mycli

This is a contrived example, so we’ll only create a package.json within examples, but you can imagine how it might work if you had more fleshed out examples that would benefit from being able to run with mycli as if it were installed.


Hi there and thanks for reading! My name's Stephen. I live in Chicago with my wife, Kate, and dog, Finn. Want more? See about and get in touch!