What’s a workspace?
A pnpm workspace is a monorepo setup driven by a root-level pnpm-workspace.yaml file that lists all local package folders.
What do we want to see?
pnpm installcontinues to workpnpm run { build, lint, test }continue to work- We haven’t ruined our
githistory - We haven’t lost our lockfile package versions
- …more benefits to come later
We’re not looking to break the monolith up yet — that comes later. We’ll focus on making small incremental changes, and verify that things work early and often.
Let’s go
Begin by creating a packages/ui folder
The monolith becomes the first monorepo package
shmkdir -p packages/ui
Move everything that was in the repo root folder into packages/ui except for the following files
.gitignore.nvpmrc.prettierrcpnpm-lock.yamlREADME.md
Now create a top-level file in the repo called pnpm-workspace.yaml.
yaml# pnpm-workspace.yamlpackages:- 'packages/*'
You’ll need to create a very thin top-level package.json file
json{"name": "seeds","repository": "https://github.com/mike-north/ts-monorepos-v2","private": true,"volta": {"node": "22.16.0"}}
…and because “seeds” was the name of our monolith package, let’s change that to @seeds/ui
diff{+ "name": "@seeds/ui",- "name": "seeds","private": true,"version": "0.0.0","type": "module",...}
What we’re leaning into with this package naming convention is npm scopes — we could use @seeds/* for all of our monorepo packages, and potentially the workspace root as an “umbrella” package, so that those who already depend on it have a very clear upgrade path.
Alternatively, we could have called the monorepo package something else like seeds-workspace and created packages/seeds with the seeds package. This might be a good choice if your existing users need CLI commands, and there’s more to maintaining general compatibility with users’ existing expecations of seeds than its existence in someone’s package.json file
Now run
shpnpm i
And it should complete successfully
Next, let’s get those build, lint and test commands working
In your root package.json add the following
json{"scripts": {"build": "pnpm --color run -r build","lint": "pnpm --color run -r lint","check": "pnpm --color run -r check","test": "pnpm --color run -r test","format": "pnpm --color run -r format","dev": "pnpm --color run -r dev"},"volta": {"node": "22.16.0"}}
Ok, let’s stage all the files we have changed
shgit add -A .
and run git status
Look at all those
renamed: <file> renamed: <file> renamed: <file> renamed: <file> renamed: <file>
This means we’re preserving our git history. What you don’t want to see is files deleted and new files re-created.
Check out our lockfile too
shgit diff HEAD pnpm-lock.yaml
You should see
diffindex a499dd8..9b6f4be 100644--- a/pnpm-lock.yaml+++ b/pnpm-lock.yaml@@ -6,7 +6,9 @@ settings:importers:- .:+ .: {}++ packages/ui:dependencies:cors:specifier: ^2.8.5
This should give you confidence that we’re not releasing all of our locked versions as part of this process.
At this point you should be able to run
shpnpm build && \pnpm test && \pnpm lint && \pnpm check && \pnpm format
and see that everything works.
Make a git commit, and let’s move on!