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 18
Note
The
--default
flag 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.json
to meet your package's needs; - define the
main
orexports
ormodule
fields to indicate the entries and available imports from your package; - define the
files
list, 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
npx
which enables you to quickly execute your local installation of the Monilla CLI.
npx monilla install
This 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-app
Note
Monilla will throw an error if you create a circular dependency between your packages.
Note
If the source package has a
build
script 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 refresh
Note
If your linked package has a
build
script it will be executed prior to performing the refresh.
Watching your linked packages results in automatic building and pushing of the updates;
npx monilla watch
This command is especially useful when performing local development across your monorepo.
Note
We utilise your
.gitignore
file 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 upgrade
You'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 --help
npx monilla clean
Removes 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 install
This 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-app
Link monorepo packages, declaring the from
package as a dependency within the to
package.
npx monilla refresh
Ensures that packages are using the latest form of their internal packages dependencies that have been linked against them.
npx monilla watch
Starts 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 upgrade
Perform 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