Yarn workspaces solve the Drupal monorepo problem

If you’re developing a very simple Drupal site or you’re maintaining a single Drupal module or theme that requires building front-end assets, you can get away with just plonking a package.json in its directory and opening a terminal there. However, if your project begins to span multiple modules and themes, it quickly becomes impractical to build each one without hard-coding a bunch of stuff in a custom script so you can invoke it from the root of a Drupal project. There has to be a better way, right?

Yarn workspaces and the yarn.BUILD plug-in completely solve this.

Workspace

These will usually be an individual module or theme. Each one must have a package.json which defines its dependencies, scripts, etc.

Work tree

The Drupal site as a whole, which defines what its workspaces are.

The demo

Clone and set up the demo repository I’ve created. You’ll notice that it’s extremely bare bones, with the demo modules and theme not containing any code - this is intentional to keep it as simple as possible.

After you look through each module and the theme, you’ll also notice they use different packages and build tools - each workspace defines its own build script and has full control over what it does, despite all workspaces being part of the same work tree and sharing one Yarn install via the top-level package.

Let’s get building

Now that you’ve familiarised yourself with the structure, it’s time to build things. In your terminal at the repository root, type ddev yarn build, hit Enter, and you should see something similar to this:

Simple enough. It built every workspace.

Now we can get to the really clever stuff: subsequent builds. Type ddev yarn build again and hit Enter. You should see this:

Wait, why didn’t it build anything? That’s because none of the workspaces had any changes. Let’s make a change. Open web/modules/custom/demo_module2/stylesheets/demo.scss in your editor and save it without any other edits. Now run ddev yarn build again. You should see the following:

yarn.BUILD not only saw that the workspace had been modified, but it also built the other workspaces that depend on it directly or indirectly.

Now try saving each of the following like above, one at a time, and run ddev yarn build after each:

  1. web/modules/custom/demo_module1/stylesheets/demo.less
  2. web/modules/custom/demo_module3/stylesheets/demo.styl
  3. web/themes/custom/demo_theme/stylesheets/demo.scss

Note how it only builds what’s changed or what’s had a dependency change. Feel free to experiment with different combinations of the source stylesheets to get a good sense how it does things.

How does this work?

You need the following key parts:

The top-level package.json in your Drupal root is analogous to the top-level composer.json and tells Yarn where to find your workspaces like so:

{
  "name": "your-project-name",
  "version": "1.0.0",
  "license": "GPL-3.0-or-later",
  "workspaces": [
    "web/modules/custom/*",
    "web/themes/custom/*"
  ],
  "dependencies": {
    "your-theme": "workspace:^1.0.0"
  }
}

The important parts are the glob patterns under "workspaces" which tell Yarn where to search, and "dependencies", which define the specific workspaces that are the direct dependencies of the root, usually the tips or leaves in the dependency tree. In this fictional example, "your-theme" is a top-level dependency because it’s the default Drupal theme for the site. "your-theme" can in turn depend on other workspaces in the Drupal tree that aren’t listed in your root package.json but will still be picked up by ddev yarn build recursively and their build scripts called, as long as they match the glob patterns above.

This requires a modern version of Yarn, meaning at least 3.x or newer. If you’re using DDEV:

  1. Add corepack_enable: true to your .ddev/config.yaml
  2. ddev restart
  3. ddev yarn set version stable

If you’re not using DDEV, follow the Yarn installation instructions.

The yarn.BUILD plug-in uses your workspaces to build everything as needed when you type ddev yarn build.

Coming from another package manager?

There isn’t one! By default, Yarn keeps packages as ZIP files in .yarn/cache (at the root of your work tree) and translates all access to files automatically, so your build tools shouldn’t even notice anything is different. You can read about the how and why in Yarn’s Plug’n’Play documentation.

If you need to make a library web-accessible without a node_modules directory, I wrote @neurocracy/pnp-vendorize to do exactly that. You’re welcome.