A CLI to manage monorepos with predictability and stability.
- 1. Introduction
- 2. Motivation
- 3. Prerequisites
- 4. Install
- 5. Requirements
- 6. Guide
- 7. CLI Reference
- 8. Appreciation
- 9. Further Reading / References
Monilla is a CLI tool which improves the development experience against npm-based monorepos.
It avoids the use of workspaces or hoisting, treating each of your monorepo packages as though they were independent packages. We find that this approach increases the predictability and stability of packages within your monorepo. Dependency updates to one package should not affect another.
We lean heavily into a "standard" npm experience and promote the capability for packages to be easily lifted in or out of your monorepo.
- Install dependencies across your monorepo, and link internal packages;
monilla install
- Declare a link between internal packages;
monilla link --from @my/components --to @my/ui
- Perform interactive dependency upgrades across your monorepo;
monilla upgrade
- Watch your packages for changes, updated linked packages;
monilla watch
Coming soon
Node.js version 18 or higher is required to use this CLI.
Note
There was a change in behaviour between npm versions in how linked package dependencies were installed. A flag (
--install-links) was introduced to the npm CLI to address this issue.Node 18 by default ships with a version of npm which includes support for this flag. Therefore we are making it a requirement that you utilise Node 18.
We highly recommend installing nvm on your machine. It enables you to manage multiple versions of Node.js seamlessly. Utilising nvm you can install the required version of Node.js via the following command;
nvm install --default 18Note
The
--defaultflag will make this installation the default version on your machine.
We recommend installing monilla as a dev dependency in the root of your monorepo;
npm install monilla --save-dev
Linked Packages package.json Design
We expect that each package within your monorepo is built almost as if it were independent package, that could be published to npm.
This means that you need to;
- add all the expected dependencies to your
package.jsonto meet your package's needs; - define the
mainorexportsormodulefields to indicate the entries and available imports from your package; - define the
fileslist, declaring which dirs/files should be exposed by your package;
For e.g.
{
"name": "@my/stuff",
"version": "0.0.0",
"private": true,
"type": "module",
"exports": "./dist/index.js",
"types": "./dist/index.d.ts",
"files": ["dist"],
"scripts": {
"build": "tsup"
},
"dependencies": {
"chalk": "^5.0.1"
},
"devDependencies": {
"@types/node": "^16.11.41",
"tsup": "^6.1.2",
"typescript": "^4.7.4"
}
}Note
We declare our package as "private" with a version of "0.0.0". This is intentional as we never intend to actually publish our package to npm. It will only be used by other packages within the monorepo.
We will perform an "npm pack" of your linked packages, carrying across the expected files that would have been exposed if it were published to npm.
This enables us to produce an npm install behaviour for your linked packages that is essentially the same as a vanilla install should the package have been downloaded from the npm registry.
Imagine a monorepo with the following structure:
my-mono-repo
|
|- apps
| |
| |- @my/mobile-app
| |- src
| | |- index.js
| |- package.json
|
|- packages
| |
| |- @my/components
| |- src
| | |- index.js
| |- package.json
|
|- package.json
Using this as a reference, we'll describe a few scenarios below.
Note
All of the below commands should be executed at the root of your monorepo.
We have prefixed the commands with
npxwhich enables you to quickly execute your local installation of the Monilla CLI.
npx monilla installThis performs two functions;
- Installs the required dependencies for each package within the monorepo, including the root
- Ensures that any linked packages within the monorepo are bound
Linking enables you to utilise one of your monorepo packages within another as though it were installed via the NPM registry.
The example monorepo contains a mobile app, and a components library. If we wished to utilise the components library within the mobile app we can link the package.
You can do so by running the link command within the root of your monorepo;
npx monilla link --from @my/components --to @my/mobile-appNote
Monilla will throw an error if you create a circular dependency between your packages.
Note
If the source package has a
buildscript we will execute it prior to link, ensuring that all the required source files are available.
If you've performed updates to one of your linked packages, you can ensure that all dependants are using the latest version of them via the following command;
npx monilla refreshNote
If your linked package has a
buildscript it will be executed prior to performing the refresh.
Watching your linked packages results in automatic building and pushing of the updates;
npx monilla watchThis command is especially useful when performing local development across your monorepo.
Note
We utilise your
.gitignorefile to determine which files to ignore when executing this process.
We support interactive upgrading of the dependencies for all the packages within your monorepo;
npx monilla upgradeYou'll be asked which packages you'd like to update for each of the packages within your monorepo. After this has completed we'll take care of the installs and refreshing of your linked packages.
Note
The command has additional flags allowing you to select the type of upgrades you wish to consider. By default we will only look for patch or minor updates for existing dependencies.
Work in progress. You can get help via the CLI --help flag;
npx monilla --helpnpx monilla cleanRemoves all node_modules folders and package-lock.json files from across the monorepo. Supports the good old "nuke and retry" strategy when in dire need.
npx monilla installThis performs two functions;
- Installs the dependencies for every package, including the root.
- Ensures that any linked packages are bound.
npx monilla link --from @my/components --to @my/mobile-appLink monorepo packages, declaring the from package as a dependency within the to package.
npx monilla refreshEnsures that packages are using the latest form of their internal packages dependencies that have been linked against them.
npx monilla watchStarts a "development" process that will watch your linked packages for any changes, and will automatically update consuming packages to use the updated versions.
npx monilla upgradePerform an interactive upgrade of the dependencies for all the packages within your monorepo.
A huge thank you goes to @wclr for the outstanding work on Yalc. The Yalc workflow is the specific seed which enabled this idea to grow. 🌻
An additional thank you is extended to @raineorshine for the amazing work on npm-check-update. Updating dependencies would be too laborious without this amazing tool. We couldn't have done it better ourselves, so we have incorporated npm-check-update directly. ☀️
- monorepo.tools - Everything you need to know about monorepos, and the tools to build them.
- An abbreviated history of JavaScript package managers
- Exploring workspaces and other advanced package manager features
- Inside the pain of monorepos and hoisting
- The Hoisting Madness in Monorepos
- Monorepos will ruin your life -- but they're worth it!
- nohoist in Workspaces
- Tweet from Dan Abramov