npm workspaces: an introduction

Workspaces provides tooling for managing multiple packages within a single repository. npm’s implementation aids with more efficiently managing shared depedencies, and running scripts across packages.

Setting up

Given a repository npm-workspaces with two packages a and b:

./npm-workspaces
├── package.json
└── packages
    ├── a
    │   └── package.json
    └── b
        └── package.json

Workspaces are defined in the root package.json under the workspaces property:

{
  "name": "npm-workspaces",
  "workspaces": ["packages/a", "packages/b"]
}

Dependencies

Running npm install at the root will search for all dependencies in the multi-package repository to install.

All dependencies are hoisted to the root node_modules unless their versions differ across packages, so dependencies shared by multiple packages are only saved once. Packages are also treated as dependencies, and are automatically symlinked in the root node_modules.

Installing the dependency lodash for a specific workspace a (from anywhere within the repository):

npm i lodash -w a

Then generates the following tree:

./npm-workspaces
├── node_modules
│   ├── a -> ../packages/a
│   ├── b -> ../packages/b
│   └── lodash
├── package-lock.json
├── package.json
└── packages
    ├── a
    │   ├── package.json
    └── b
        └── package.json

If the same dependency is shared by package b then the above tree is unchanged and the dependency is still only saved once in the root node_modules, as confirmed below:

/npm-workspaces npm ls
npm-workspaces@ /npm-workspaces
├─┬ a@1.0.0 -> ./packages/a
 └── lodash@4.17.21
└─┬ b@1.0.0 -> ./packages/b
  └── lodash@4.17.21 deduped

If a package diverts it’s version of a shared dependency, then it will have its own node_modules within the package. Workspaces will continue to share a single package-lock.json at the root:

./npm-workspaces
├── node_modules
│   ├── a -> ../packages/a
│   ├── b -> ../packages/b
│   ├── lodash
├── package-lock.json
├── package.json
└── packages
    ├── a
    │   ├── node_modules
    │   │   └── lodash
    │   └── package.json
    └── b
        └── package.json

Running scripts

npm workspaces supports running workspace scripts from anywhere within the repository. Running the script start for workspace a:

npm run start -w=a

Or for multiple workspaces:

npm run start -w=a -w=b
npm run start -ws # all

However, the above will fail if a specified workspace does not support the script start. To ignore workspaces missing the target script:

npm run dev -ws --if-present