Trust is earned, not given

A different perspective

2021-03-02 · Projects

Node.js, part 13: npm workspaces — a monorepo without adding a tool

Part 13from the Node.js series · 24 parts in all

Splitting a codebase into packages usually comes with a decision: adopt a monorepo tool (Lerna, Nx, Turborepo) or duplicate everything. npm 7 — bundled with Node 16 — added workspaces, which covers the common case of "a few packages and an app that depends on them" with no extra dependency at all. Part 13 is that, and where it stops being enough.

The whole setup

{
  "name": "maplecart",
  "private": true,
  "workspaces": ["packages/*", "apps/*"]
}
maplecart/
  package.json
  packages/
    pricing/package.json      name: @maplecart/pricing
    db/package.json           name: @maplecart/db
  apps/
    api/package.json          depends on @maplecart/pricing

One npm install at the root installs every workspace and hoists shared dependencies into the root node_modules. Crucially, a local package is linked by name — apps/api imports @maplecart/pricing and gets the working copy, not a published version, so an edit in one package is live in the other with no build step or npm link ceremony.

The commands

npm install                       # all workspaces, one lockfile at the root
npm test --workspaces             # run the script in every workspace
npm run build --workspace apps/api
npm install lodash --workspace packages/pricing
npm ls --all                      # who depends on what, across the tree

That last command is the one that pays for itself: with hoisting, "which package pulled this in?" is no longer guessable from the folder you are standing in.

Where it stops being enough

For two to ten internal packages, workspaces is the right answer and a framework would be overhead. Next: measuring what actually runs, with the profiler Node already has.