How to replace Bower to Yarn with Yarn Workspaces

Example: mean-project‘s structure

| mean-project/
| ---- package.json      <----- `Node.js Server & development packages' list`
| ---- bower.json        <----- `Web Client packages' list`
| ---- node_modules/
| ----------------- express/   <--- `'package.json' install dependencies`
| ----------------- grunt/     <--- `'package.json' install devDependencies`
|
| ---- client/
| ----------- index.html
| ----------- bower_components/
| ---------------------------- angular/  <--- `'bower.json' install dependencies`
|
| ---- server/
| ----------- index.js   <----- `Node.js Server Files`
|  . . .
mean-project/package.json:
    {
      "private": true,
      "name": "mean-project",

      "dependencies": {
        "express": "^5.0.0-alpha.6"
      },
      "devDependencies": {
        "grunt": "^1.0.1"
      }
    }
mean-project/bower.json:
    {
      "private": true,
      "name": "mean-project",

      "dependencies": {
        "angular": "^1.6.8"
      },
      "devDependencies": {

      }
    }

Migrate to Yarn Workspaces

— Step 1 —
Move the packages in mean-project/bower.json to mean-project/client/package.json

— Step 2 —
Move the packages’ dependencies in mean-project/package.json
to mean-project/server/package.json dependencies.

— Step 3 —
Enable Yarn Workspaces

$  yarn config set workspaces-experimental true

It will add workspaces-experimental true to the .yarnrc file in your OS home folder.

— Step 4 —
If using Grunt(Gruntfile.js) or other build tools,
change the bowerJson path to './client/package.json'

mean-project Yarn Workspace’s structure

| mean-project/
| ------------ package.json   <------------ `Root`
| ------------ client/
| ------------------- package.json    <----- `dependencies moved from 'bower.json'`
|
| ------------ server/
| ------------------- package.json    <------ `dependenies moved from root 'package.json'`
| . . .
mean-project/package.json:
    {
      "private": true,
      "name": "mean-project",
      "version": "1.0.0",

      "devDependencies": {
        "grunt": "^1.0.1"
      },
      "workspaces": [
        "client",
        "server"
      ]
    }

devDependencies packages are NOT required and included to production bundle,
and only used to development process.

Two small Workspaces packages :
1. client Workspace :

mean-project/client/package.json

    {
      "private": true,
      "name": "mean-project-client",
      "description": "...",
      "version": "1.0.0",
      "license": "...",
      "main": "...",
      "browser": "...",

      "dependencies": {
        "angular": "^1.6.8"
      }
    }
2. server Workspace :

mean-project/server/package.json

    {
      "name": "mean-project-server",
      "version": "1.0.0",
      "license": "...",
      "main": "...",
      "browser": "...",

      "dependencies": {
        "express": "^5.0.0-alpha.6"
      }
    }
Install all packages :
# enable yarn workspaces:
mean-project$  yarn config set workspaces-experimental true

# install packages of dependencies
mean-project$  yarn install

Workspaces in Yarn | Yarn Blog

Workspaces in Yarn

Posted Aug 2, 2017 by Konstantin Raev
Projects tend to grow over time, and, occasionally, some pieces of a project can be useful elsewhere in other projects. For example, Jest, being a generic testing tool, gave birth to many packages, one of them is jest-snapshot that is now used in other projects like snapguidist and chai-jest-snapshot.

Monorepos

Those who have tried splitting a project into multiple packages know how hard it is to make changes across multiple packages at one time. To make the process easier, some big projects adopted a monorepo approach, or multi-package repositories, which reduces the burden of writing code across packages.

Introducing Yarn Workspaces

Yarn Workspaces is a feature that allows users to install dependencies from multiple package.json files in subfolders of a single root package.json file, all in one go.

Making Workspaces native to Yarn enables faster, lighter installation by preventing package duplication across Workspaces. Yarn can also create symlinks between Workspaces that depend on each other, and will ensure the consistency and correctness of all directories.

Setting up Workspaces

To get started, users must enable Workspaces in Yarn by running the following command:

$ yarn config set workspaces-experimental true

It will add workspaces-experimental true to the .yarnrc file in your OS home folder. Yarn Workspaces is still considered experimental while we gather feedback from the community.

Example of Yarn Workspaces

Workspaces in Yarn | Yarn Blog

Jest‘s project structure:

| jest/
| ---- `package.json`       <----- `Root`
| ---- packages/jest-matcher-utils/
| ------------------- `package.json`  <----- `Workspaces`
|
| ---- packages/jest-diff/
| ------------------ `package.json`   <----- `Workspaces`
...

The top-level package.json defines the root of the project, and folders with other package.json files are the Workspaces. Workspaces usually are published to a registry like npm.

While the root is not supposed to be consumed as a package, it usually contains the glue code or business specific code that is not useful for sharing with other projects, that is why we mark it as “private“.

A simplified root package.json that enables Workspaces for the project and defines third-party packages needed for the project build and test environment.

jest/package.json:
    {
      "private": true,
      "name": "jest",
      "devDependencies": {
        "chalk": "^2.0.1"
      },
      "workspaces": [
        "packages/*"
      ]
    }
Two small Workspaces packages:
1. jest-matcher-utils Workspace:

jest/packages/jest-matcher-utils/package.json

    {
      "name": "jest-matcher-utils",
      "description": "...",
      "version": "20.0.3",
      "license": "...",
      "main": "...",
      "browser": "...",
      "dependencies": {
        "chalk": "^1.1.3",
        "pretty-format": "^20.0.3"
      }
    }
2. jest-diff Workspace that depends on jest-matcher-utils:

jest/packages/jest-diff/package.json

    {
      "name": "jest-diff",
      "version": "20.0.3",
      "license": "...",
      "main": "...",
      "browser": "...",
      "dependencies": {
        "chalk": "^1.1.3",
        "diff": "^3.2.0",
        "jest-matcher-utils": "^20.0.3",
        "pretty-format": "^20.0.3"
      }
    }

With Workspaces enabled(yarn config set workspaces-experimental true),
Yarn can produce a much more optimized dependency structure
and when you run the usual yarn install anywhere in the project
you’ll get the following node_modules.

# enable yarn workspaces:
jest$  yarn config set workspaces-experimental true

# install packages of dependencies
jest$  yarn install

Output structure:

| jest/
| ---- package.json   <------------ `Root`
| ---- node_modules/
| ----------------- chalk/  <----- (v2.0.1)
| ----------------- diff/
| ----------------- pretty-format/
| ----------------- jest-matcher-utils/ (symlink) -> ../packages/jest-matcher-utils
|
| ---- packages/
| ------------- jest-matcher-utils/
| -------------------------------- package.json  <--- `Workspaces`
| -------------------------------- node_modules/
| --------------------------------------------- chalk/  <---- (v1.1.3)
|
| ------------- jest-diff/
| ----------------------- package.json   <--- `Workspaces`
| ----------------------- node_modules/
| ------------------------------------ chalk/   <------------ (v1.1.3)
 ...

Packages like diff, pretty-format and the symlink to jest-matcher-utils
were hoisted to the root node_modules directory,
making the installation faster and smaller.

The package chalk(v1.1.3) however could not be moved to the root
because the root already depends on a different,
incompatible version of chalk(v2.0.1).

If you run code inside the jest-diff Workspace,
it will be able to resolve all its dependencies:

  • require('chalk') resolves to ./node_modules/chalk
  • require('diff') resolves to ../../node_modules/diff
  • require('pretty-format') resolves to ../../node_modules/pretty-format
  • require('jest-matcher-utils') resolves to ../../node_modules/jest-matcher-utils
    that is a symlink to ../packages/jest-matcher-utils

Managing dependencies of Workspaces

If you want to modify a dependency of a Workspace,
just run the appropriate command inside the Workspace folder:

$ cd packages/jest-matcher-utils/
$ yarn add left-pad
Done in 1.77s.

$ git status
modified: package.json
modified: ../../yarn.lock

Note that Workspaces don’t have their own yarn.lock files,
and the root yarn.lock contains all the dependencies for all the Workspaces.

When you want to change a dependency inside a Workspace,
the root yarn.lock will be changed as well as the Workspace’s package.json.

Leave a Reply