The problem
I have tried several ways of managing JavaScript/TypeScript Library Monorepos in the past: lerna
, yarn workspaces
, etc.
Now don't get me wrong: These are great tools and I very much appreciate the effort their authors have put into them.
But they always felt a little bit like gambling. It never felt like I was really in control of what was happening (with a lot of black magic), and I found that they felt a bit ... fragile (I was alsways worried about breaking some symlinks or stuff like that when running any commands).
A solution?
I wanted to try both pnpm
and Parcel. I had heard good things about both tools and have recently gotten more and more frustrated with their more established competitors.
When I looked at their respective documentation pages, it looked like both had great monorepo support. Since I was also still on a long-lasting search for some "building an npm library"-compatible monorepo solution with a better developer experience than what I had seen so far, I decided to give it a shot.
The repository
So, I built (and documented) a test repository to try this new monorepo setup:
pklaschka / pnpm-parcel-monorepo-test
A repository for trying a JS/TS npm library monorepo setup using pnpm as package manager and parcel as a build tool.
pnpm
& parcel
monorepo test
A repository for trying a JS/TS npm library monorepo setup using pnpm
as package manager and parcel
as a build tool.
Development prerequisites:
Tech Stack
This is an overview of the most important components of this monorepo's tech stack
- pnpm -- package and monorepo workspace manager
- Parcel -- build tool
-
Jest /
ts-jest
-- Unit testing framework -
TypeScript /
tsc
-- TypeScript type checking - TypeScript ESLint -- linting
- Prettier -- code formatter
- fliegdoc -- documentation generator
- GitHub Actions -- CI pipeline
- Renovate -- Dependency Update Management
Usage
Setting up the development environment
Install the dependencies:
pnpm install
- automatically runs recursively for the workspace, cf. https://pnpm.io/cli/install
- Alias:
pnpm i
-
npm ci
-equivalent:pnpm i --frozen-lockfile
(automatically true in CI environment)
You can also run pnpm install
when anything about your dependencies becomes out of date to fix it back up.
Dependency Management
Installing
β¦The repository contains a test setup with a more or less full tech stack consisting of, among others:
- TypeScript
- ESLint
- Prettier
- fliegdoc (a self-built documentation generator)
- jest / ts-jest
- GitHub Actions
I described most things in the README.md
, but I also created an additional public Notion page describing more details.
Results
I'm really happy with how it works and will definitely use this approach in the future. I'll also probably migrate existing monorepos to this approach, in the future.
Advantages
- π’ you feel like you're in control with
pnpm
, it's pretty straight-forward to understand how their workspace system works, so you feel like you're in control and don't have to guess about fixes to your problems π. E.g.,pnpm install
sets up everything. Before, I always was unsure if I should now runnpm install
,lerna bootstrap
, or something else. - π’ quick build times since
parcel
builds all the packages at once (instead of running build scripts one package at a time), build times (especially with the build cache in place) are incredibly fast - π’ development experience with
parcel watch
, it's possible to very quickly develop - π’ "native" workspaces working with workspaces / multiple packages feels "native" to
pnpm
(compared to its competitors, where I unfortunately found that it feels more like a "hacky side feature"). Commands work how I would expect them to work, "internal" dependencies betweeen packages automatically get hydrated with version numbers on publish, and so on.
Drawbacks
Of course, every approach comes with a few drawbacks. Here are the ones I've found so far:
- π‘ less ecosystem support while
pnpm
andparcel
work great in 99 % of cases, tools often don't test their support for these as much as, for example, foryarn
andwebpack
- π’ (no Dependabot support) at the time of writing this, GitHub's Dependabot doesn't support
pnpm
. Thankfully, Renovate seems to work well. - π’ (no included "release automation" tooling)
lerna
came with great Changelog / Conventional Commit / ... based release automation tooling. Unfortunately, I haven't yet found a great solution for doing something similar withpnpm
. Do you have any recommendations?
A quick fix for a Parcel bug that almost made me dismiss it
When I initially tested Parcel, it felt unstable. It wouldn't shut down, would from time to time just overwrite my package.json
, and just overall not work very well at all.
I was almost ready to give up when I found this issue on GitHub. It turns out that I had a package-lock.json
somewhere higher up the file tree (probably some backup I had created before). This lead to Parcel showing all kinds of weird behavior (not only the one described in the issue). So if you decide to try this approach and feel like Parcel is "freaking out" in a weird way, it might be worth checking for package.json
, packaage-lock.json
or similar files higher up in the file tree.
So overall, this is easy to fix. But this almost made me (which would have been a shame!) turn away from Parcel, so I wanted to include this note here.
Even more details
Furthermore, I've documented everything I learned about the process / how the repo is structured in a Notion Page. If you decide to try this monorepo configuration, this could be useful to you as it includes all the commands you need to know, links to various important resources, and so on.
Top comments (3)
I like pnpm!
Have you looked into nx.dev or turborepo? I've been using nx and noticed some lack of control, especially with some community based plugins.
Thanks for your comment!
For nx.dev: I had looked into it, but decided against it. While nx looks really cool, it felt like that weird combination of having "too little" (the tool is pretty opinionated about how everything should be structured, e.g., with the
--publishable
flag and how essentially the entire repo is then controlled by nx) and "too many" (skimming through their documentation didn't really give me an overview of everything nx could do) options at the same time.Based on that, I think nx is (probably; I haven't tried it π) a great solution for a big internal project with an engineering team onboarded onto the project. My use case is an open-source project (i.e., contributors who don't have in-depth knowledge about all the build tools), which is why I decided against it. I didn't feel like I would have been able to write a comprehensive enough, yet short
CONTRIBUTING.md
if I had used that.Also, I actively dislike tools where I have no clue what's going on. I'm sure it's possible to learn nx enough to understand what's happening. But not in the amount of time I currently have available for that π
For Turborepo: This definitely looks cool. While I had taken a quick look at it, I didn't really look into it in detail (as I had already found the solution presented in the article and was happy with it).
Personally, I tend to dislike tools trying to "solve everything" (I've seen too many tools trying to solve everything and then being unable to handle the complexity of that). While
turborepo
seems to be a bit better in this regard, the fact that their Getting started section shows building a "pipeline" with building, testing, and linting was what turned me off when I initially looked into it. This is just personal preference, though.Overall, I would say that I looked at (but not deeply into) both of them but (for my use case) they felt like they tried to solve too much for my taste. Don't get me wrong: I think both of these tools are great and have their advantages, but especially with nx, I feel like it doesn't really work in an environment with a lot of inexperienced contributors. And with turborepo, I haven't (yet π) found a need for it in my project. Maybe that day will come. But until then, I prefer to keep my tech stack for something as elementary as "Repo Management" / "building stuff" as small as possible (giving me a better chance of stuff not breaking and the ability to better understand what's going on if something breaks).