Angular 14 monorepo with lerna, npm workspaces and Git

Angular 14 monorepo with lerna, npm workspaces and Git

In this tutorial with the latest version, we'll see by example how to create an angular 14 monorepo project containing two packages: a shared library and a demo application using tools like lerna, git and npm workspaces.

Lerna and npm workspaces are tools that help developers develop libraries and applications in a single repo (also known as a monorepo) without having to publish to npm or other registries in the development phase of a project. By accessing shared components and libraries locally, we can complete the development (such as coding, testing and debuging) cycles considerably faster.

Setting up a monorepo for angular 14 with lerna and npm workspaces

If this is your first time using Angular 14, you'll need to install it globally. Open a command-line interface and run the following command:

npm install --global @angular/cli

Next, let’s create an empty Angular workspace as follows:

ng new angularmonorepo --create-application=false

I'm not interested in creating an initial application and so we used the —create-application option with the ng new command. When set to false, an empty workspace with no first app is created.

Next, install lerna as a development dependency in your Angular 14 project using the following commands:

cd angularmonorepo/
npm install lerna --save-dev

The command added lerna to devDependenciesin package.json

For incorporating lerna into our project to assist us with library management, we simply need to run lerna init which generates a new lerna repository. We’ll be using independent mode with the --independent option to allow package versions to be incremented regardless of one another.

Then, proceed to set up a monorepo for your Angular workspace with lerna as follows:

lerna init --independent // initialize lerna monorepo 

We set the version to independent so we can manage the packages’ versions and publish them independently.

Once the startup process has been completed, you will be presented with the following output:

info cli using local version of lerna
lerna notice cli v4.0.0
lerna info Updating package.json
lerna info Creating lerna.json
lerna info Creating packages directory
lerna success Initialized Lerna files

Notice that lerna has created a lerna.json file and a packages/ folder and only update the existing package.json rather than creating a new one.

By default, lerna points its packages to the packagesfolder.

We’ll not be using the default lerna’ packages/ folder since Angular uses the projects directory for applications and libraries, therefore, we need to tell lerna about that. Open the lerna.json file and update it as follows:

{
  "packages": [
    "projects/*"
  ],
  "version": "independent"
}

Then you can simply delete the packages/ folder from your Angular 14 workspace:

rm -r packages/

Next, open the package.json file and add the following key to use npm workspaces:

"workspaces":["projects/*"]

Next, generate a library using Angular CLI v14 as follows:

ng g lib shared

This library will be generated inside the projects/ folder by Angular CLI which we configured as a workspace for both lerna and npm.

Then, create an application using Angular 14 CLI as follows:

ng g app demoapp

You’ll be prompted as follows:

  • ? Would you like to add Angular routing? Yes
  • ? Which stylesheet format would you like to use? SCSS

Following that, create a package.json file inside the application’s root folder and add the following contents:

{
    "name": "demoapp",
    "version": "0.0.1",
    "scripts": {
        "start": "ng serve demoapp",
        "build": "ng build demoapp",
        "test": "ng test demoapp",
        "lint": "ng lint demoapp"
    }
}

Next, open the project’s level package.json file and update the scripts to use lerna:

"scripts": {
    "ng": "ng",
    "start": "lerna run start --scope=demoapp --stream",
    "build": "lerna run build --scope=demoapp,shared --stream",
    "buildlib": "lerna run build --scope=shared --stream",
    "watch": "ng build --watch --configuration development",
    "test": "lerna run test --scope=demoapp,shared --stream"
  }

Save the root package.json file and build the shared library by running the following command:

npm run buildlib

This will display the following output on the screen:

> [email protected] buildlib
> lerna run build --scope=shared --stream

lerna notice cli v4.0.0
lerna info versioning independent
notice filter including "shared"
info filter [ 'shared' ]
lerna success run No packages found with the lifecycle script 'build'

Let’s solve this by adding a build script to the package.json file of the shared library as follows:

"scripts":{
    "build": "ng-packagr"
}

Save the file and re-run the npm run buildlib command. You should get a similar truncated output:

shared: Built Angular Package
shared:  - from: ..../tutorials/angularmonorepo/projects/shared
shared:  - to:   ..../tutorials/angularmonorepo/dist/shared
shared: -------------------------------------------------------------------
shared: Build at: 2022-04-11T03:43:30.517Z - Time: 3114ms
lerna success run Ran npm script 'build' in 1 package in 4.1s:
lerna success - shared

Next, let’s add the built shared library as a dependency in our app by running the following lerna command:

lerna add shared

You should get a similar output:

info cli using local version of lerna
lerna notice cli v4.0.0
lerna info versioning independent
lerna info Adding shared in 1 package
lerna info Bootstrapping 2 packages
lerna info Symlinking packages and binaries
lerna success Bootstrapped 2 packages

These are the contents of the demoapp package.json file after running the previous command:

{
    "name": "demoapp",
    "version": "0.0.1",
    "scripts": {
        "start": "ng serve demoapp",
        "build": "ng build demoapp",
        "test": "ng test demoapp",
        "lint": "ng lint demoapp"
    },
    "dependencies": {
        "shared": "^0.0.1"
    }
}

Notice that shared has been added to the application dependencies.

You can test if everything works by going to the demoapp/src/app/app.module.ts file and import symbols from the shared module as follows:

// [...]
import { SharedModule, SharedService } from 'shared';

@NgModule({
  declarations: [
    AppComponent
  ],
  imports: [
    BrowserModule,
    AppRoutingModule,
    SharedModule
  ],
  providers: [SharedService],
  bootstrap: [AppComponent]
})
export class AppModule { }

Next, run your angular 14 demo application using the following command:

npm start 

It should show the following truncated output and build then serve the app:

> lerna run start --scope=demoapp --stream

lerna notice cli v4.0.0
lerna info versioning independent
notice filter including "demoapp"
info filter [ 'demoapp' ]
lerna info Executing command in 1 package: "npm run start"
[...]
demoapp: ** Angular Live Development Server is listening on localhost:4200, 
open your browser on http://localhost:4200/ **
demoapp: ✔ Compiled successfully.

Conclusion

You could ask why using both lerna and NPM Workspaces. What functions do each of them play in our development process? In a nutshell, the features provided by the two utilities are quite similar. Both lerna and npm workspaces are capable of reading package.json files and configuring the required symlinks but lerna add a few commands that make things a lot easier.

Both lerna and npm workspaces use a packages folder in your repository. A package may be independently initialized, versioned, and published. The example 14 monorepo shown above contains two packages:

  • A shared library: This may contain code that designed be reused.
  • A demo app: This is an angular 14 application that may import and use the code exported from the shared library.

In the next tutorial, we’ll learn how to streamline our angular library release with commit conventions and commitizen.