Originally many years ago I created multiple repositories for different part of a project (frontend, backend, mobile, worker, auth, shared package). Making changes across many repositories is messy and difficult to track, and testing gets complicated really fast.
I ended up on writing many bash script to sync and run multiple tasks at the same time, then I found Lerna and it changed my whole workflow. Organizing codebase into a multi-package repository made code synchronization and local development a lot easier.
Lerna is a tool that optimizes the workflow around managing multi-package repositories with git and npm. Convinced yet! Many famous open source projects are using Lerna like Babel, React, Angular, Ember, Meteor, Jest.
This post focuses only core principles of using monorepo with Lerna. I don’t write detailed usage guides because the official document has done that brilliantly. You’ll end up with folder structure like this:
monorepo | ├── README.md ├── lerna.json ├── package.json └── packages ├── core │ ├── index.js │ └── package.json ├── server │ ├── index.js │ └── package.json └── worker ├── index.js └── package.json
Fixed mode Lerna projects operate on a single version line. The version is kept in the lerna.json file at the root of your project under the version key. When you run lerna publish, if a module has been updated since the last time a release was made, it will be updated to the new version you’re releasing. This means that you only publish a new version of a package when you need to.
This is the mode that Babel is currently using. Use this if you want to automatically tie all package versions together. One issue with this approach is that a major change in any package will result in all packages having a new major version.
Independent mode Lerna projects allows maintainers to increment package versions independently of each other. Each time you publish, you will get a prompt for each package that has changed to specify if it’s a patch, minor, major or custom change.
Independent mode allows you to more specifically update versions for each package and makes sense for a group of components. Combining this mode with something like semantic-release would make it less painful.
To reduce redundancy, most package managers employ some kind of hoisting scheme to extract and flatten all dependent modules, as much as possible, into a centralized location.
Monorepo can share modules across child projects/packages by hoisting them up to their parent project’s nodemodules: monorepo/nodemodules. This optimization becomes even more prominent when considering these packages will most likely be dependent on each other (the main reason to have the monorepo), i.e. higher degree of redundancy.
Npm and Yarn are both native package managers that have many features in common. One of the reasons for creating Yarn in the first place was performance, it took too long to install dependencies in large projects with Npm.
Yarn workspaces is the only representative that exposes monorepo capabilities natively. Lerna is around for quite some time and came out even before yarn workspaces has existed. Lerna provides monorepo features on the level of user land with the help of npm or yarn as dependency management tools.
Yarn workspaces sole purpose is to ease monorepo workflow. Lerna provides sophisticated publishing and version management features to even publish projects independently from each other. So, you do not have to decide for either side of them. It totally does make sense to use Lerna with Yarn workspaces.