“Monorepo” is one of those words you could break down – Mono meaning one, and Repo meaning rail repository. Better phrased: you took a bunch of similar repos or npm packages and bundled them onto a single repo.
Now what?
Well, let’s back up a little. Monorepos are an increasingly popular way to handle managing packages, especially as we continue to write (and consume) more atomic modules. A monorepo means you can centralize config, commits, releases, and anything you do in a normal repo in one place. Of course, it has all sorts of pros and cons. One of the biggest questions when it comes to monorepos is: do the pros outweigh the cons?
Well…. There’s a pretty quick checklist you could make just to find that out:
If you answered yes to all three, consider yourself lucky and let’s go! If not, keep thinking about your approaches—a monorepo might not be the best answer. Another additional question you might ask is “who consumes this?” While it’s not a hard and fast rule, high-usage frameworks or libraries tend to be structured as monorepos (React, Angular, Meteor, etc).
Let’s make a brand new project, called links-and-stuff
in a directory somewhere, and then run the following
cd links-and-stuff
npm i -g lerna
lerna init --independent
lerna add links
This will create boilerplate with a packages
folder. The --independent
flag means we’ll use separate versioning for each project. Your layout should look like this now
|-- package.json
|-- lerna.json
|-- packages
|---- links
|---- stuff
Now run lerna add lodash
to install lodash into each package. (If we ran lerna add lodash --scope=links
for example, it would only install lodash in the links package).
There are some clear advantages that you’ll quickly find when managing a monorepo.
For one, Lerna’s exec
command allows you to run a command in each package. For example,
lerna exec -- rm -rf ./node_modules
Will delete the node_modules
in each package. A similar command is run
, which is specifically for npm scripts.
lerna run build
Will run npm run build
in each package, building them in parallel. (You can use the --scope
argument previously discussed to only affect certain packages). Another example: let’s say we wanted to run tests in each package, but we also wanted to update our jest snapshots. We can pass arguments the way you would with any npm script.
lerna run test -- -u
Another awesome thing about lerna is its publish
command. I personally use this variation of it:
lerna publish --conventional-commits --yes
What this will do is publish each of your npm packages and generate the appropriate npm version based off the commits (with the --conventional-commits
flag) and generate a changelog for each (using the --changelog-preset
argument, which defaults to the angular preset). The --yes
flag auto-confirms the changes; you may want to to turn this off if you want to verify the versions before publishing.
Monorepos do have some issues, but Lerna monorepos do have fewer issues than a regular monorepo. For example, publishing is a breeze and versioning, maintained via npm, is not singular or lost. Some of them are specific to Lerna.
If you’re using yarn, you may face some issues regarding compatibility with lerna now that it has moved back to npm, and using both in one monorepo is an absolutely bad idea due to incompatibilities between yarn.lock
, package.json
, and the pack
command.
At scale, lerna starts to slow down significantly as the number of packages increases. Monorepos such as babel don’t even use the lerna run
command for this reason, as it cycles through each package, which can become cumbersome—at that point, it makes more sense to write a custom build script.
Another potential problem comes down to CI/CD, as it sometimes requires unecessarily complicated setups in the pipeline. While parallel pipelines in continuous deployment tools like CircleCI are useful for this, it’s not an answer at scale. Like before, this sometimes requires custom build scripts and the like.
And the last thing that I think is worth bringing up is the suggestion that monorepos hamper autonomy of teams, and that code sharing can introduce coupling. I’ve thought about this one the most, since it doesn’t have a “clear” solution. It’s philosophical. When code gets housed in the same repository, everyone can see all the code. But like an open space lined with desks, it often gets cramped, and that same transparency can make you feel like you’re stepping on other people’s toes. Sometimes you just want a closed room.