Setting up Mono Repo using Lerna for JS Project

Setting up Mono Repo using Lerna for JS Project

Lets first understand the word mono-repo

  • A monorepo (mono repository) is a single repository that stores all of your code and assets for every project.
  • Using a monorepo is important for many reasons. It creates a single source of truth. It makes it easier to share code. It even makes it easier to refactor code.

Monorepo Is Usually Best For…

  • Visibility : Using a single repository gives you visibility into your code and assets for every project. This helps you manage dependencies.
  • Collaboration: A single repository makes it easier to collaborate. That’s because everyone can access the code, files, and assets. So, developers can share and reuse assets.
  • Speed: Using a single repository can help you accelerate development. For instance, you can make atomic changes (one action to make a change across multiple projects).

Lets see How lerna can help us in Building mono repo

Lerna is a popular and widely used tool written in JavaScript for setting and managing multi-package repositories for Node.js projects with npm and Git.

Lerna has two modes: fixed and independent. Fixed mode keeps all versions of packages at the same level. This approach is quite popular these days. You may have seen it in Angular.

install Lerna as a global dependency:

npm install -g lerna

Lerna is a tool that optimizes the workflow around managing multi-package repositories with git and npm.

Lerna can also reduce the time and space requirements for numerous copies of packages in development and build environments - normally a downside of dividing a project into many separate NPM packages. See the hoist documentation for details.

➜  tkssharma lerna init && npm install
lerna notice cli v4.0.0
lerna info Initializing Git repository
lerna info Creating package.json
lerna info Creating lerna.json
lerna info Creating packages directory
lerna success Initialized Lerna files

├── lerna.json
├── package.json
└── packages

/packages is a placeholder for our shared packages, and lerna.json is a Lerna configuration file. package.json is our usual manifest file.

lerna init --independent/-i – Use independent versioning mode.

you can choose shared or independent versioning mode for packages

Lerna doesn’t create a .gitignore file, so we will create one with this content:

node_modules/
lerna-debug.log
npm-debug.log
packages/*/lib
.idea

Lets add typescript for our root project

npm install typescript @types/node — save-dev

and we can have global tsconfig file

{
 "compilerOptions": {
   "module": "commonjs",
   "declaration": true,
   "noImplicitAny": false,
   "removeComments": true,
   "noLib": false,
   "emitDecoratorMetadata": true,
   "experimentalDecorators": true,
   "target": "es6",
   "sourceMap": true,
   "lib": [
     "es6"
   ]
 },
 "exclude": [
   "node_modules",
   "**/*.spec.ts"
 ]
}

Building our packages with lerna create

Now we can create sub projects here using learn create command something like this

lerna create sdk
learn create cdk
lerna create cli-tools

we can repeat same process for other packages

➜  lerna-poc git:(master) ✗ lerna create sdk
lerna notice cli v4.0.0
lerna WARN ENOREMOTE No git remote found, skipping repository property
package name: (sdk) aws-sdk
version: (0.0.0) 
description: 
keywords: 
homepage: 
license: (ISC) 
entry point: (lib/sdk.js) 
git repository: 
About to write to /Users/tkssharma/tkssharma/lerna-poc/packages/sdk/package.json:
{
  "name": "aws-sdk",
  "version": "0.0.0",
  "description": "> TODO: description",
  "author": "tkssharma <tarun.softengg@gmail.com>",
  "homepage": "",
  "license": "ISC",
  "main": "lib/sdk.js",
  "directories": {
    "lib": "lib",
    "test": "__tests__"
  },
  "files": [
    "lib"
  ],
  "scripts": {
    "test": "echo \"Error: run tests from root\" && exit 1"
  }
}
Is this OK? (yes) yes
lerna success create New package aws-sdk created at ./packages/sdk

Now we have three different packages

├── lerna.json
├── package.json
└── packages
    ├── cdk
    │   ├── README.md
    │   ├── __tests__
    │   │   └── cdk.test.js
    │   ├── lib
    │   │   └── cdk.js
    │   └── package.json
    └── sdk
        ├── README.md
        ├── __tests__
        │   └── sdk.test.js
        ├── lib
        │   └── sdk.js
        └── package.json

7 directories, 10 files

For all packages we have create compiler config known as tsconfig we can extend what we have at root level

{
 "extends": "../../tsconfig.json",
 "compilerOptions": {
   "outDir": "./lib"
 },
 "include": [
   "./src"
 ]
}

We can add tsc npm scripts in all packages to play with lena commands

  "scripts": {
    "tsc": "tsc",
    "test": "echo \"Error: run tests from root\" && exit 1"
  }

Now when we execute lerna run tsc it will execute tsc command in all packages one by one

Linking packages

With TypeScript compiled, let’s create a test integration package to see how Lerna handles linking packages.

cd packages
mkdir integration
cd integration
npm init -y
cd ../..

To install necessary dependencies from npm, or link ones from the monorepo, use the lerna add command from the root of the project. In case of any naming conflicts, local packages will always take precedence over remote ones.

➜  lerna-poc git:(master) ✗ lerna add cdk --scope=integration
lerna notice cli v4.0.0
lerna notice filter including "integration"
lerna info filter [ 'integration' ]
lerna info Adding cdk in 1 package
lerna info Bootstrapping 3 packages
lerna info Symlinking packages and binaries
lerna success Bootstrapped 3 packages


➜  lerna-poc git:(master) ✗ lerna add sdk --scope=integration
lerna notice cli v4.0.0
lerna notice filter including "integration"
lerna info filter [ 'integration' ]
lerna info Adding sdk in 1 package
lerna info Bootstrapping 3 packages
lerna info Installing external dependencies
lerna info Symlinking packages and binaries
lerna success Bootstrapped 3 packages

In our root package json we can have npm publish script to publish this package

{
 "scripts": {
   "publish": "lerna run tsc && lerna publish"
 }
}

Now i can see linking dependencies in integration project

{
  "name": "integration",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "dependencies": {
    "cdk": "^0.0.0",
    "sdk": "^0.5.0"
  }
}

Now we can publish using lerna publish

lerna publish

Create a new release of the packages that have been updated. Prompts for a new version and updates all the packages on git and npm.

➜  lerna-poc git:(master) ✗ npm run publish

> root@ publish /Users/tkssharma/tkssharma/lerna-poc
> lerna run tsc && lerna publish

lerna notice cli v4.0.0
lerna info Executing command in 3 packages: "npm run tsc"
lerna info run Ran npm script 'tsc' in 'cdk' in 0.5s:

> cdk@0.0.0 tsc /Users/tkssharma/tkssharma/lerna-poc/packages/cdk
> echo 'tsc'

tsc
lerna info run Ran npm script 'tsc' in 'aws-sdk' in 0.5s:

> aws-sdk@0.0.0 tsc /Users/tkssharma/tkssharma/lerna-poc/packages/sdk
> echo 'tsc'

tsc
lerna info run Ran npm script 'tsc' in 'integration' in 0.4s:

> integration@1.0.0 tsc /Users/tkssharma/tkssharma/lerna-poc/packages/integration
> echo 'tsc'

tsc
lerna success run Ran npm script 'tsc' in 3 packages in 0.9s:
lerna success - cdk
lerna success - integration
lerna success - aws-sdk
lerna notice cli v4.0.0
lerna info current version 0.0.0

This is How we can manage multiple Projects and can have their own local dependencies and run command using lerna which will run over all the projects

Its good practice that we should give proper names to the packages if we want to publish them like while adding a package put the name of packages prefix with Org name

lerna create @Corps/aws-sdk
learn create @Corps/aws-cdk
lerna create @Corps/cli-tools

Conclusion

  • lerna is easy to get started and help is setting up mono-repo and we already aware about the advantages of mono-repo in today's world
  • With this help you can setup different package as application like React and another can be nestjs, design system or shared utilities in between projects.

References

Comments