Monorepos with pnpm and changesets
pnpmchangesets

Monorepos with pnpm and changesets


I have mostly used one github repo for each npm package or app that I worked on, but in some cases using a monorepo has some advantages such as sharing code between projects without having to publish a npm package for it.

One popular option lately is pnpm which has support for workspaces which is sufficient for smaller projects and to me seems to be easier to understand. The only thing you need to add is pretty much a pnpm-workspace.yml file in the root and you're good to go.

Setting up pnpm with workspaces

This blog post describes step by step how to setup the workspaces for Tailwind and Typescript which can be simplified into a few steps:
Add the pnpm-workspace.yaml file that defines where apps and packages are located:

packages:
  - "apps/*"
  - "packages/*"

Add a tsconfig.json file which will be the default for all packages:

{
  "compilerOptions": {
    "target": "ESNext",
    "lib": ["DOM", "DOM.Iterable", "ESNext"],
    "module": "ESNext",
    "skipLibCheck": true,
    "noImplicitAny": false,
    "allowJs": true,
    "noErrorTruncation": true,
 
    /* Bundler mode */
    "moduleResolution": "bundler",
    "allowImportingTsExtensions": true,
    "resolveJsonModule": true,
    "isolatedModules": true,
    "noEmit": true,
    "jsx": "react-jsx",
 
    /* Linting */
    "strict": true,
    "noUncheckedIndexedAccess": true,
    "noUnusedLocals": true,
    "noUnusedParameters": true,
    "noFallthroughCasesInSwitch": true,
 
    "declaration": true,
    "composite": true,
    "sourceMap": true,
    "declarationMap": true
  }
}

Add a tesconfig.node.json file:

{
  "compilerOptions": {
    "composite": true,
    "skipLibCheck": true,
    "module": "ESNext",
    "moduleResolution": "bundler",
    "allowSyntheticDefaultImports": true
  }
}

Create your first package (from either the apps or packages folder):

pnpm create vite frontend --template react-ts

Update the tsconfig.json in the created project:

{
  "extends": "../../tsconfig.json",
  "compilerOptions": {
    // "noEmit": true,
    "baseUrl": ".",
    "outDir": "./dist",
    "rootDir": "./src"
  },
  "include": ["src"],
  "references": [{ "path": "./tsconfig.node.json" }]
}

Add a tsconfig.node.json in the created project:

{
  "extends": "../../tsconfig.node.json",
  "include": ["vite.config.ts"]
}

Add a shortcut for the project in the package.json in the project root. This makes it possible to for instance write pnpm frontend dev from the project root.

  "scripts": {
    "frontent": "pnpm --filter frontend"
  },

And.. that's it! Just run pnpm frontend dev and you should see the vite startpage!

Changesets

Once there's been some changes to a project you can create a changeset by running pnpm changeset. It will detect which projects have been update and that you want to include, if it's a major/minor/patch update and a changelog message.

A changeset is really just a changelog message and a list of the files affected:

If you have one or many changesets you can create a new version by running pnpm changeset version. This will bump the versions of the package.json files and update the CHANGELOG.md files. At this point you need commit the changes to git.

Once the changes are commited you can publish any updated packages by running pnpm publish -r. This will publish any packages to npm.

Github actions

TODO

Other monorepo alternatives

Another popular option for monorepos is NX which takes a different approach to than pnpm workspaces. First of all it's not node/javascript specific but works for any language, whereas pnpm workspaces are specifically geared for javascript.

Another important difference is that it recommends that you have the same dependencies for all your projects which makes it easier to ensure that all packages are updated at the same time. The downside with this is that the dependencies in my experienced aren't updated at all as any update will cause all packages in the project to be updated which can be scary unless you're very confident in your tests. I think I prefer using something like Renovate bot to keep the dependencies up to date.