A presentation at Kul Kode in December 2020 in Drammen, Norway by Ronny Siikaluoma
Managing JavaScript projects in a MonoRepo Using Lerna
Agenda MultiRepo vs MonoRepo Simple Lerna Workflow Use cases Changelog automation
The Problem Projects tend to grow Split them into sub-projects Create multiple repos Broken dependencies hell
MultiRepo MonoRepo 1 project
= 1 project 1:1 of repo to package 1:n of repo to package Slower refactoring Faster refactoring Forces decoupling Allow but doesn’t force decoupling More decentralized More centralized More individualistic More communal Harder to test Easier to test Harder to share config Easier to share config Harder to share dev tooling Easier to share dev tooling Multiple CI pipelines Single CI pipeline Many places to report issues Single place to report issues Good for simple interdependencies Good for complex interdependencies Faster setup, builds and tests Slower setup, builds, and tests Discourages consistency Encourages consistency More duplication Less duplication Harder to coordinate changes Easier to coordinate changes Harder to browse full project Easier to browse full project Harder to sync project deps Easier to sync project deps Smaller repo Larger repo Less activity More activity
MultiRepo MonoRepo 1 project
= 1 project 1:1 of repo to package 1:n of repo to package Slower refactoring Faster refactoring Forces decoupling Allow but doesn’t force decoupling More decentralized More centralized More individualistic More communal Harder to test Easier to test Harder to share config Easier to share config Harder to share dev tooling Easier to share dev tooling Multiple CI pipelines Single CI pipeline Many places to report issues Single place to report issues Good for simple interdependencies Good for complex interdependencies Faster setup, builds and tests Slower setup, builds, and tests Discourages consistency Encourages consistency More duplication Less duplication Harder to coordinate changes Easier to coordinate changes Harder to browse full project Easier to browse full project Harder to sync project deps Easier to sync project deps Smaller repo Larger repo Less activity More activity
Who Uses MonoRepos? Google Facebook Air BnB Pinterest Angular React RxJS Vue
npm install -g lerna
npm lerna init
📁project/
npm lerna init 📄 lerna.json npx: installed 708 in 56.182s 📄 package.json lerna notice cli v3.20.2 📁 packages/ lerna info Creating package.json lerna info Creating lerna.json lerna info Creating packages directory lerna success Initialized Lerna files { “name”: “root”, “private”: true, “devDependencies”: { “lerna”: “^3.20.2” } }
📁project/
npm lerna init 📄 lerna.json npx: installed 708 in 56.182s 📄 package.json lerna notice cli v3.20.2 📁 packages/ lerna info Creating package.json lerna info Creating lerna.json lerna info Creating packages directory lerna success Initialized Lerna files { “packages”: [ “packages/*” ], “version”: “0.0.0” }
📁project/
npm lerna init 📄 lerna.json npx: installed 708 in 56.182s 📄 package.json lerna notice cli v3.20.2 📁 packages/ lerna info Creating package.json lerna info Creating lerna.json lerna info Creating packages directory lerna success Initialized Lerna files npm lerna init —independent { “packages”: [ “packages/*” } npx: installed 708 in 56.182s lerna notice cli v3.20.2 ], lerna info Creating package.json “version”: “independent”, lerna info Creating lerna.json lerna info Creating packages directory lerna success Initialized Lerna files
📁lerna-demo/
git clone git@github.com:siiron/lerna-demo.git 📄 lerna.json Cloning into ‘lerna-demo’… 📄 package.json Done. 📁 packages/ 📁 rs-btn/ 📁 src/ 📄 package.json 📁 rs-demo/ 📁 src/ 📄 package.json 📁 rs-styles/ 📁 src/ 📄 package.json
git clone git@github.com:siiron/lerna-demo.git 📁lerna-demo/ 📄 lerna.json Cloning into ‘lerna-demo’… 📄 package.json Done. 📁 packages/ 📁 rs-btn/ 📁 src/ .js 📄 package.json .scss 📁 rs-demo/ 📁 src/ 📄 package.json 📁 rs-styles/ 📁 src/ 📄 package.json .scss
📁lerna-demo/
git clone git@github.com:siiron/lerna-demo.git 📁 node_modules/ Cloning into ‘lerna-demo’… 📄 lerna.json Done. 📄 package.json 📁 packages/ 📁 rs-btn/ 📁 src/ 📄 package.json 📁 rs-demo/ 📁 src/ 📄 package.json 📁 rs-styles/ 📁 src/ 📄 package.json cd lerna-demo && npm install Installing… Done.
📁lerna-demo/
npm run bootstrap 📁 node_modules/ lerna bootstrap 📄 lerna.json Bootstrapping 3 packages 📄 package.json Installing external dependencies 📁 packages/ 📁 rs-btn/ 📁 node_modules/ 📁 src/ 📄 package.json 📁 rs-demo/ 📁 node_modules/ 📁 src/ 📄 package.json 📁 rs-styles/ 📁 node_modules/ 📁 src/ 📄 package.json Symlinking packages and binaries Bootstrapped 3 packages
📁lerna-demo/
npm run bootstrap 📁 node_modules/ lerna bootstrap 📄 lerna.json Bootstrapping 3 packages 📄 package.json Installing external dependencies 📁 packages/ 📁 rs-btn/ 📁 node_modules/ 📁 src/ 📄 package.json 📁 rs-demo/ 📁 node_modules/ 📁 src/ 📄 package.json 📁 rs-styles/ 📁 node_modules/ 📁 src/ 📄 package.json Symlinking packages and binaries Bootstrapped 3 packages
📁lerna-demo/
lerna bootstrap —hoist 📁 node_modules/ Bootstrapping 3 packages 📄 lerna.json Installing external dependencies 📄 package.json Installing hoisted dependencies into root 📁 packages/ 📁 rs-btn/ Pruning hoisted dependencies Finished pruning hoisted dependencies 📁 node_modules/ Finished installing in root 📁 src/ Symlinking packages and binaries 📄 package.json Bootstrapped 3 packages 📁 rs-demo/ 📁 node_modules/ 📁 src/ 📄 package.json 📁 rs-styles/ 📁 node_modules/ 📁 src/ 📄 package.json
📁lerna-demo/
lerna add lodash 📁 node_modules/ Installed lodash in rs-btn 📄 lerna.json Installed lodash in rs-demo 📄 package.json Installed lodash in rs-style 📁 packages/ 📁 rs-btn/ 📁 node_modules/ 📁 src/ 📄 package.json 📁 rs-demo/ 📁 node_modules/ 📁 src/ 📄 package.json 📁 rs-styles/ 📁 node_modules/ 📁 src/ 📄 package.json lerna add lodash -D
📁lerna-demo/ 📁 node_modules/ 📄 lerna.json 📄 package.json 📁 packages/ 📁 rs-btn/ 📁 node_modules/ 📁 src/ 📄 package.json 📁 rs-demo/ 📁 node_modules/ 📁 src/ 📄 package.json 📁 rs-styles/ 📁 node_modules/ 📁 src/ 📄 package.json
lerna add lodash –-scope @siiron/rs-btn Installed lodash in rs-btn
📁lerna-demo/
lerna add lodash —ignore @siiron/rs-btn 📁 node_modules/ Installed lodash in rs-demo 📄 lerna.json Installed lodash in rs-styles 📄 package.json 📁 packages/ 📁 rs-btn/ 📁 node_modules/ 📁 src/ 📄 package.json 📁 rs-demo/ 📁 node_modules/ 📁 src/ 📄 package.json 📁 rs-styles/ 📁 node_modules/ 📁 src/ 📄 package.json
📁lerna-demo/
lerna —scope @siiron/rs-styles run build 📁 node_modules/ Building rs-styles… 📄 lerna.json Done. 📄 package.json 📁 packages/ 📁 rs-btn/ 📁 node_modules/ 📁 src/ 📄 package.json 📁 rs-demo/ 📁 node_modules/ 📁 src/ 📄 package.json 📁 rs-styles/ 📁 node_modules/ 📁 dist/ 📁 src/ 📄 package.json
📁lerna-demo/
lerna run build 📁 node_modules/ Building rs-styles… 📄 lerna.json Building rs-btn… 📄 package.json Building rs-demo… 📁 packages/ 📁 rs-btn/ 📁 node_modules/ 📁 dist/ 📁 src/ 📄 package.json 📁 rs-demo/ 📁 node_modules/ 📁 build/ 📁 src/ 📄 package.json 📁 rs-styles/ 📁 node_modules/ 📁 dist/ 📁 src/ 📄 package.json Done.
📁lerna-demo/
lerna exec — rm –rf ./dist 📁 node_modules/ Removed ./dist in rs-styles… 📄 lerna.json Removed ./dist in rs-btn… 📄 package.json Done. 📁 packages/ 📁 rs-btn/ lerna —scope @siiron/rs-demo exec — rm –rf ./build 📁 node_modules/ Removed ./build in rs-demo… 📁 src/ Done. 📄 package.json 📁 rs-demo/ 📁 node_modules/ 📁 src/ 📄 package.json 📁 rs-styles/ 📁 node_modules/ 📁 src/ 📄 package.json
{
lerna publish “packages”: [ “packages/*” ? Select a new version (currently 0.1.0) (Use arrow keys) > Patch (0.1.1) ], Minor (0.2.0) “version”: “0.1.1” “0.1.0” Major (1.0.0) } Prepatch (0.1.1-alpha.0) Preminor (0.2.0-alpha.0) Premajor (1.0.0-alpha.0) Custom Prerelease Custom Version Changes: - @siiron/rs-btn: 0.1.0 => 0.1.1 - @siiron/rs-demo: 0.1.0 => 0.1.1 {
LERNA PUBLISH —allow-branch —no-commit-hooks —legacy-auth —amend —no-git-tag-version —no-git-reset —conventional-commits —no-push —no-verify-access —conventional-graduate —preid —otp —conventional-prerelease —sign-git-commit —preid —changelog-preset —sign-git-tag —pre-dist-tag <tag> —exact —yes —registry <url> —force-publish —tag-version-prefix —tag-version-prefix —git-remote —canary —temp-tag —create-release —contents <dir> —yes —ignore-changes —dist-tag <tag> —ignore-scripts —git-head <sha> —include-merged-tags —graph-type <all|dependencies> —message —ignore-scripts —no-changelog —ignore-prepublish
primer/primer babel/babel facebook/create-react-app ReactTraining/react-router pugjs/pug facebook/jest reactotron/reactotron ElemeFE/mint-ui devtools-html/debugger.html alibaba/rax Turfjs/turf babel/babili storybooks/storybook xmppjs/xmpp.js wooorm/retext jumpsuit/jumpsuit strapi/strapi cloudflare/cf-ui colmena/colmena angus-c/just cerebral/cerebral nteract/nteract wooorm/remark webpack-preset/webpack-preset vazco/uniforms metal/metal.js steelbrain/pundle neos/neos-ui mosjs/mos lore/lore act-framework/act yvele/poosh noderaider/gridiron superawesomelabs/leo tocco/tocco-client zalando-incubator/tessellate sencha/extjs-reactor DigitalRiver/react-atlas forivall/tacoscript Boxable/box-ui flux-capacitor/flux-capacitor trepo/trepo-js spacedoc/spacedoc oknosoft/metadata.js uber-web/uber-eslint brittanica/brittanica sterjakovigor/vqua wireapp/wire-web-packages klis87/redux-saga-requests WordPress/gutenberg react-cosmos/react-cosmos jsbites/jedifocus-monorepo FoalTS/foal emotion-js/emotion Bamieh/reflow transloadit/uppy olistic/warriorjs gatsbyjs/gatsby feathersjs/feathers vuejs/vue-cli dvajs/dva umijs/umi SBoudrias/Inquirer.js pedronauck/docz tasitlabs/tasitsdk zeit/next.js react-bootstrap-table/reactbootstrap-table2 webpack/webpack-cli
We have a bug 🐞! Use-case example #1
startlan-esoknad • Uses Husbanken Designsystem for css + images • Uses HB Angular Diverse for components • There is a bug that needs fixing 2 repos • Create Jira-ticket for startlan-esoknad #EST-666 • Create Jira-ticket for designsystem #STG-777 • Create Jira-ticket for hb-angular-diverse #FRED-888 • Link tickets together
📁husbanken-design/ 📄 lerna.json 📄 package.json 📁 packages/ 📁 design/ 📄 package.json 📁 docs/ 📄 package.json 📁 static/ 📄 package.json 📁 tokens/ 📄 package.json #STG-777
📁husbanken-design/
lerna version 📄 lerna.json ? Select a new version (currently 5.7.2) (Use arrow keys) 📄 package.json Patch (5.7.3) 📁 packages/ 📁 design/ 📄 package.json 📁 docs/ 📄 package.json 📁 static/ 📄 package.json Minor (5.8.0) Major (6.0.0) Prepatch (5.7.3-alpha.0) Preminor (5.8.0-alpha.0) Premajor (6.0.0-alpha.0) Custom Prerelease Custom Version 📁 tokens/ 📄 package.json Changes: - @husbanken/design: 5.7.2 => 5.7.3 - @husbanken/design-docs: 5.7.2 => 5.7.3 - @husbanken/design-static: 5.7.2 => 5.7.3 - @husbanken/design-tokens: 5.7.2 => 5.7.3 #STG-777
📁husbanken-design/ GIT TEKTON push npm build 📄 lerna.json 📄 package.json 📁 packages/ 📁 design/ 📄 package.json 📁 docs/ 📄 package.json 📁 static/ 📄 package.json 📁 tokens/ 📄 package.json #STG-777 pull request 🕓 approved ✔️
📁husbanken-design/ GIT TEKTON NEXUS 📄 lerna.json 📄 package.json 📁 packages/ 📁 design/ 📦 5.7.3 📦 5.7.3 📦 5.7.3 📦 5.7.3 📄 package.json 📁 docs/ 📄 package.json 📁 static/ 📄 package.json 📁 tokens/ 📄 package.json #STG-777 merge npm publish
📁hb-angular-diverse/ 📁 node_modules/ 📄 package.json 📁 src/ 📦 5.7.3 #FRED-888
npm install @husbanken/design@5.7.3
📁hb-angular-diverse/ GIT TEKTON push npm build 📁 node_modules/ 📄 package.json 📁 src/ pull request 🕓 approved ✔️ #FRED-888
📁 hb-angular-diverse/ GIT TEKTON NEXUS npm publish 📦 📁 node_modules/ 📄 package.json 📁 src/ merge #FRED-888 8.7.7
📁@husbanken/design MAKE GIT TEKTON 📁hb-angular-diverse/ NEXUS MAKE GIT TEKTON NEXUS 📁startlan-esoknad/ 📁 node_modules/ 📦 📦 5.7.3 8.7.7 📄 package.json 📁 src/
npm install @husbanken/design@5.7.3 > npm install hb-angular-diverse@8.7.7 #EST-666
We have a bug 🐞! Use-case example #2
startlan-esoknad • Uses Husbanken Designsystem for css + images + components • There is a bug that needs fixing in 1 repo • Create Jira-ticket for startlan-esoknad #EST-666 • Create Jira-ticket for designsystem #HDS-777 • Link tickets together
📁husbanken-design/ 📄 lerna.json 📄 package.json 📁 packages/ 📁 design/ 📄 package.json 📁 docs/ 📄 package.json 📁 diverse/ 📄 package.json 📁 static/ 📄 package.json 📁 tokens/ 📄 package.json #HDS-777
📁husbanken-design/
lerna version 📄 lerna.json ? Select a new version (currently 9.0.0) (Use arrow keys) 📄 package.json Patch (9.0.1) 📁 packages/ 📁 design/ 📄 package.json 📁 docs/ 📄 package.json 📁 diverse/ 📄 package.json Minor (9.1.0) Major (10.0.0) Prepatch (9.0.1-alpha.0) Preminor (9.1.0-alpha.0) Premajor (10.0.0-alpha.0) Custom Prerelease Custom Version 📁 static/ 📄 package.json 📁 tokens/ 📄 package.json Changes: - @husbanken/design: 9.0.0 => 9.0.1 - @husbanken/design-docs: 9.0.0 => 9.0.1 - @husbanken/design-diverse: 9.0.0 => 9.0.1 - @husbanken/design-static: 9.0.0 => 9.0.1 - @husbanken/design-tokens: 9.0.0 => 9.0.1 #HDS-777
📁husbanken-design/ GIT TEKTON push npm build 📄 lerna.json 📄 package.json 📁 packages/ 📁 design/ 📄 package.json 📁 docs/ 📄 package.json 📁 diverse/ 📄 package.json 📁 static/ 📄 package.json 📁 tokens/ 📄 package.json #STG-777 pull request 🕓 approved ✔️
📁husbanken-design/ GIT TEKTON NEXUS 📄 lerna.json 📄 package.json 📁 packages/ 📁 design/ 📄 package.json 📁 docs/ 📄 package.json 📁 diverse/ merge npm publish 📦 9.0.1 📦 9.0.1 📦 9.0.1 📦 9.0.1 📦 9.0.1 📄 package.json 📁 static/ 📄 package.json 📁 tokens/ 📄 package.json #STG-777
📁@husbanken/designsystem MAKE GIT TEKTON NEXUS 📁startlan-esoknad/ 📁 node_modules/ 📦 9.0.1 📄 package.json 📁 src/ 📦 9.0.1 > npm install @husbanken/design@9.0.1 > npm install @husbanken/diverse@9.0.1 #EST-666
Changelogs Can be automated
Better Changelogs • Lerna follows Semantic Versioning (SemVer)
9.5.0 Major Minor Patch
Better Changelogs • Lerna follows Semantic Versioning (SemVer) • Lerna supports Conventional Commits
<type>[optional scope]: <description> [optional body] [optional footer(s)] feat(components): change prop “icn” to “icon” in button component see HDS-007 for details BREAKING CHANGE: instances that uses “icn” will break.
📁project/ { 📄 lerna.json “packages”: [ 📄 package.json “packages/*” 📁 packages/ ], “command”: { “publish”: { “conventionalCommits”: true }, “version”: { “message”: “chore(release): release” } } }
Better Changelogs • Lerna follows Semantic Versioning (SemVer) • Lerna supports Conventional Commits • Commitizen – conventional commits utility
HOOS@HOOS-7510 MINGW64 /c/Projects/lerna-demo (develop) $ git cz cz-cli@4.2.2, cz-conventional-changelog@3.3.0 ? Select the type of change that you’re committing: (Use arrow keys) > feat: A new feature fix: A bug fix docs: Documentation only changes style: Changes that do not affect the meaning of the code (white-space, formatting, missing semi-colons, etc) refactor: A code change that neither fixes a bug nor adds a feature perf: A code change that improves performance test: Adding missing tests or correcting existing tests (Move up and down to reveal more choices)
? What is the scope of this change (e.g. component or file name): (press enter to skip) ? Write a short, imperative tense description of the change (max 94 chars): (20) add border to button ? Provide a longer description of the change: (press enter to skip) ? Are there any breaking changes? No ? Does this change affect any open issues? No
Better Changelogs • Lerna follows Semantic Versioning (SemVer) • Lerna supports Conventional Commits • Commitizen – conventional commits utility • Husky – git hook to the commit command • Commitlint
HOOS@HOOS-7510 MINGW64 /c/Projects/lerna-demo (develop) $ git commit -m “Add prop for shape + some button-styling” husky > commit-msg (node v12.18.3) ⧗ input: Add prop for shape + some button-styling ✖ subject may not be empty [subject-empty] ✖ type may not be empty [type-empty] ✖ found 2 problems, 0 warnings ⓘ Get help: https://github.com/conventional-changelog/commitlint/#what-is-commitlint husky > commit-msg hook failed (add —no-verify to bypass) HOOS@HOOS-7510 MINGW64 /c/Projects/lerna-demo (develop) $
Better Changelogs • Lerna follows Semantic Versioning (SemVer) • Lerna supports Conventional Commits • Commitizen – conventional commits utility • Husky – git hook that stops bad commits • Commitlint
Summary ✅ Supports both NPM and Yarn ✅ Framework agnostic ✅ Build, test, publish one, some, or all modules ✅ Run arbitrary commands for one, some, or all modules ✅ “Share” dependencies across modules safely ✅ “Link” module-to-module dependencies safely ✅ Install unique dependencies for individual modules ✅ Distribute to a pre-release channel ✅ Parallelization of tasks ✅ Support changelog generation
https://github.com/siiron/lerna-demo
Resourses 🔗 https://github.com/siiron/lerna-demo 🔗 https://github.com/lerna/lerna 🔗 https://semver.org/ 🔗 https://www.conventionalcommits.org/en/v1.0.0/ 🔗 https://github.com/commitizen/cz-cli 🔗 https://github.com/commitizen/cz-conventional-changelog 🔗 https://github.com/typicode/husky 🔗 https://github.com/conventional-changelog/commitlint 🔗 https://indepth.dev/posts/1040/release-management-in-angular-with-lerna