Why Rush?
Tags: rush, node, monorepo
Published: 2022-03-02 10:42:22

After happily using Lerna for quite some time, I feel I should move on. 
This article is a fully subjective review of Rush, outlining some pros and cons I find important for my SPFx projects. In the next articles of the series I will show my approach to some obstacles I see.

## Rush
[Rush](https://rushjs.io/) is owned by Microsoft, and since January 2017 is an open source project. You may find a little history [here](https://github.com/microsoft/rushstack/issues/673#issuecomment-394859359).
![Rush statistics](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/88wrohi6ji4i3x3r9gwx.png). 

## Package Manager Support
|Lerna | Rush :star: |
|-|-|
|:heavy_check_mark: npm <br />:x: pnpm <br />:heavy_check_mark: yarn | :heavy_check_mark: npm <br />:heavy_check_mark: pnpm <br />:heavy_check_mark: yarn|

Can I rest my case now? 
**Pnpm** is a big deal. It solves _phantom dependencies_ and _NPM doppelganger_ problems, and if that's not enough, it's also [faster](https://www.voitanos.io/blog/npm-yarn-pnpm-which-package-manager-should-you-use-for-sharepoint-framework-projects/) and users less disk space. There's a request for [pnpm support](https://github.com/lerna/lerna/issues/1818) by lerna, opened in 2018. I'm not holding my breath.

## Monorepo management
### Dependencies support 

|Lerna | Rush :star: |
|-|-|
|For monorepos, `lerna bootstrap --hoist` installs external dependencies at the repo root, so they're available to all packages.<br/> This increases the risk of _phantom dependencies_.|Installs package dependencies in **common/temp/node_modules** and updates the _shrinkwrap_ file in **common/config/rush** folder to store a central inventory of all dependencies and versions for projects in the repo. <br/>Rush uses _symlinks_ to reconstruct an accurate `node_modules` folder for each project, based on the _shrinkwrap_ file.<br/><br/>|

#### Rush & [phantom dependencies](https://rushjs.io/pages/advanced/phantom_deps/)
> Rush's _symlinking_ strategy ensures that each project's **node_modules** contains only its declared direct dependencies. This catches phantom dependencies immediately at build time. If you're using the **PNPM** package manager, the same protections are also applied to all indirect dependencies (with the ability to workaround any "bad" packages by using pnpmfile.js).

#### Rush & [doppelgangers](https://rushjs.io/pages/advanced/npm_doppelgangers/)
> Rush's _symlinking_ strategy eliminates doppelgangers only for dependencies that are local projects in the monorepo. If you're using NPM or Yarn as your package manager, unfortunately doppelgangers are still possible for any indirect dependencies. Whereas if you use **PNPM** with Rush, the doppelganger problem is fully solved (because PNPM's installation model accurately simulates a true directed acyclic graph).

### Selectively build and publish
Yes and yes, although I find rush to give me more flexibility.

|Lerna | Rush  |
|-|-|
|`lerna run --scope PROJECT build`<br/><br/><br/><br/><br/><br/>|`rush build [--to PROJECT]`<br/> `[--to PROJECT]`<br/> `[--from PROJECT]`<br/> `[--only PROJECT]`<br/> `[--impacted-by PROJECT]`<br/> `[--impacted-by-except PROJECT]`|
|`lerna publish` <br/>Control which package should get published using `"private": true` or `"private": false` in the `package.json`. Private packages never get published.<br/>Use `lerna publish  --force-publish=package-2,package-4` to decide which packages get published.|`rush publish` <br/>Control which package should get published using `shouldPublish` in `rush.json`. <br/> Use `rush publish --include-all` so that all packages with `shouldPublish=true` in `rush.json` or with a specified version policy will be published if their version is newer than published version.<br/><br/>|

### Independent versioning
|Lerna | Rush  |
|-|-| 
|Configured in `lerna.json`<br/>![independent versioning in lerna](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/75rn284ysdvq1feio6im.png)<br/><br/><br/><br/><br/><br/><br/><br/><br/><br/><br/><br/>|Configured in `common\config\rush\version-policies.json` ![independent versioning in rush step1](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/mfqpepnhz2wgcckkiyxg.png) and also in `rush.json` ![independent versioning in rush step2](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/1p6x5m65zsf6sn1yq5cj.png)|

### Versioning and Conventional Commits
This was a surprise. 

|Lerna :star: | Rush  |
|-|-| 
|`lerna version --conventional-commits` uses use the [Conventional Commits Specification](https://conventionalcommits.org/) to determine the [version bump](https://github.com/conventional-changelog/conventional-changelog/tree/master/packages/conventional-recommended-bump) and generate [CHANGELOG.md](https://github.com/conventional-changelog/conventional-changelog/tree/master/packages/conventional-changelog-cli) files. Interestingly, `chore` commit will also cause version bump.<br/><br/><br/><br/><br/><br/><br/>|Rush has entirely different approach here.<br/> Developers, apart from committing their changes, are required to run `rush change` command to generate **change files**. `rush change` prompts for a change description and a type of change (major/minor/patch). No Conventional Commits support, no use of commit messages. <br/>The change files are stored in **common\changes** folder and are later used to update changelog.<br/>  | 
 
## Git hooks
|Lerna | Rush :star: |
|-|-| 
|Yes, with [husky](https://typicode.github.io/husky/#/). | Yes, see [installing Git hooks](https://rushjs.io/pages/maintainer/git_hooks/).  | 
I'm giving Rush a :star: here because it offers support, if the code invoked by git hook has dependencies on additional modules.

## npm lifecycle support
|Lerna :star: | Rush  |
|-|-| 
|Lerna runs [npm lifecycle scripts](https://github.com/lerna/lerna/tree/main/commands/version#lifecycle-scripts) during `lerna version`. | There seems to be a [problem](https://github.com/microsoft/rushstack/issues/830), but I think I can overcome it with [custom commands](https://rushjs.io/pages/maintainer/custom_commands/) | 
  
## My decision

Lerna and Rush both have pros and cons. I really see a difference when using pnpm, so I'm going to stick with Rush. 
I only have to solve the issue with changelog generation.