Introduction to Lerna
What is Lerna?
Lerna is a library that provides tooling to manage multi-repository structure inside a single repository by separating out subsets of the repository into their own “sub” repositories. A repository structured in this way is called a mono-repo. The example below shows the tree structure of a mono-repo with it’s two “sub” repos Foo and Bar,
Why use it?
Lerna is used mostly in bigger projects which can become hard to maintain over time. It allows modularizing the code into smaller manageable repositories and abstracting out shareable code which can be used across these sub repos.
It does the heavy lifting for managing the tasks like versioning, shared dependency management, code deploying, tooling etc. paving a smooth path for the developers without having to think of the baggage that comes with a mono-repo architecture.
I recently used Lerna in my personal project which has 4 different repositories viz. web-client, api-service, mobile and tooling existing inside the mono-repo. The tooling package consists of eslint
, prettier
and babel
base configs which is a shared dependency that’s consumed by the other packages.
Sharing some key observations I made during my work in this post below, with the help of tree structure diagrams, that helped me understand Lerna better and apply it in my project.
Lerna bootstrap in action
Lerna allows for installing and linking all the external/shared dependencies, also called bootstrapping, in the mono repo and creating symlinks to the shared dependency locations in the repo. Since it’s symlink any update done in the shared dependency will immediately take effect in your code which is using it. (Disclaimer: You might need to build the shared dependency first depending on your config.)
The example below shows the dependency tree of a mono-repo (with child_A, child_B as child repos) before, on the left, and after, on the right, of running Lerna bootstrap
command. A symlink is created to child_b’s location inside child_a’s node_modules
.
The shared repository resides as a symlink in the node_modules of the dependent repository.
Let’s look at the dependency X which is an external dependency to both repos child_A and child_B.
One may ask — Can I reduce installation time or space with X, it being installed inside multiple repos?
Lerna provides the ability to club shared packages together by hoisting dependencies at the top level. This save installation time and also optimizes on space considerations.
In the above case, a configuration flag —-hoist
is used with lerna bootstrap
command. This will install all dependencies (shared and non-shared) at the root level node_modules
and create symlinks in the child to the root node_modules
where the dependencies actually exist.
What if the chid repos install different versions of the same package dependency?
In that case, a local version of the shared package is installed in the child node_modules
. It’s been my observation that if semver is being followed, usually the higher version gets hoisted to the top level.
Lerna also provides many other set of commands like lerna publish
and lerna version
that can be executed from the root of the mono-repo using npm scripts or through the CLI by installing Lerna globally.
While the above is true for most packages but hoisting can fail if the dependency packages don’t follow the node modules resolution algorithm closely in which case the symlink from the top-level to the local node_modules
needs to be created manually. This is not supported by Lerna currently.
If you want to learn more about other commands, a good place to start would be their documentation.
Happy coding!