Fabrizio Fortunato

Multiple packages repository with Angular

January 18, 2018

Managing modules across different applications can be challenging for Angular applications. There are different approaches that you can take that will impact your code and i’ve discussed some in my previous post the implications as well Managing Modules with Angular.

Update 16/09/19: I’ve published a new article with an updated view on monorepositories https://izifortune.com/share-angular-libraries-with-lerna/.

In here we are going to explore how to manage in monorepository different modules with the ability also to publish them as separate modules.

I will leverage NRWL/Nx an open source toolkit for enterprise Angular applications and ng-packagr a library for compiling your modules following angular format.

Why

Nx really shines for managing monorepositories and the philosophy behind it is to keep all your code in a single place.

One big problem that you can have with this setup is that different teams may work on different applications and have different releases and timelines. A team should always reduce the impact of their changes to another and if you are sharing libraries with Nx and doing breaking changes on them you will need to take care of refactoring other applications that you hardly have work on.

That’s why we can use semantic versioning, even if a breaking change its released another app can still use the previous version. Giving time to the team to adapt those changes.

Setup

Start by installing the latest Nx cli, and the latest @angular/cli globally

npm install -g @nrwl/schematics @angular/cli

This will install the create-nx-workspace binary. The next step is to create a workspace:

create-nx-workspace common

The command will create all the necessary files and folders that we will need for our repository.

If you want to read more about how workspaces works you can read their documentation at nx-workspaces.

Now that we have our workspace we can already start adding libraries or applications to our workspace.

Since we want to use versioning for our libraries that’s where ng-packagr comes into play. ng-packagr its a small library that transpile your library using the Angular library format.

Using what David Herges demonstrated on the following repository https://github.com/dherges/nx-packaged we want to combine Nx and ng-packagr together.

npm i --save-dev ng-packagr

Let’s generate two libraries in our repository:

ng generate lib mylib --nomodule
ng generate lib mylib-two

Nx extends the @angular/cli schematics making possible to create applications templates while still using the cli.

The option --nomodule will generate a general library not wrapping it in an angular module.

This will generate our libraries inside the libs folder, what we should have is two folders:

libs/
  mylib/
  mylib-two/

What we need to add is a package.json inside each library folder:

{
  "$schema": "../../node_modules/ng-packagr/package.schema.json",
  "name": "@common/mylib",
  "version": "0.0.0",
  "ngPackage": {
    "lib": {
      "entryFile": "index.ts"
    },
    "dest": "@common/mylib"
  }
}
{
  "$schema": "../../node_modules/ng-packagr/package.schema.json",
  "name": "@common/mylib-two",
  "version": "0.0.0",
  "ngPackage": {
    "lib": {
      "entryFile": "index.ts"
    },
    "dest": "@common/mylib-two"
  }
}

This way ng-packagr can start packaging our libraries for us. In fact if we run ng-packagr -p libs/mylib/package.json && ng-packagr -p libs/mylib-two/package.json after the command run you will notice both the libraries compiled under @common folder.

Tools

Now we need to take care of building our libraries. In a recent post in the NRWL/blog Victor Savkin introduced a new feature on Nx for smarter and faster buils using the toolkit.

This enhance the Nx toolkit giving you the ability to build only the applications that actually have changed instead of rebuilding the whole repository.

If you inspect your package.json at this point you will notice that there are a series of scripts

"apps:affected": "node ./node_modules/@nrwl/schematics/src/command-line/affected.js apps",
"build:affected": "node ./node_modules/@nrwl/schematics/src/command-line/affected.js build",
"e2e:affected": "node ./node_modules/@nrwl/schematics/src/command-line/affected.js e2e",
"affected:apps": "node ./node_modules/@nrwl/schematics/src/command-line/affected.js apps",
"affected:build": "node ./node_modules/@nrwl/schematics/src/command-line/affected.js build",
"affected:e2e": "node ./node_modules/@nrwl/schematics/src/command-line/affected.js e2e",

We will leverage that but we need to customize it for libraries since those scripts are only available for applications in nx.

Add these scripts inside the scripts folder.

The file utils.js contains just a set of utilities that we will use into our different scripts.

libs-affected.js instead just print out the libraries that are affected from our changes.

Before testing it out we need to add fs-extra in our devDependencies.

npm i --save-dev fs-extra

And also let’s create a script inside our package.json to call libs-affected.js script.

"libs:affected": "node ./scripts/libs-affected.js libs",

Let’s start by committing our changes that we have so far.

git commit -am 'Initial commmit'

Now generate a new component for mylib-two:

ng generate component myButton --app=mylib-two

Once again let’s commit our changes:

git commit -am 'Add myButton'

If we run now npm run libs:affected -- HEAD HEAD~1 the output should be: mylib-two. The script its returning the library affected since the last commit. Notice that i’m passing the option HEAD HEAD~1 which compare the current commit with our previous. libs:affected supports the same arguments as apps:affected from Nx you can invoke the commands either passing the list of the changed files or two commit SHAs.

To test it even further, let’s import mylib into mylib-two, commit our changes and then modify only mylib, the script should print out to console mylib mylib-two.

Build

Once the number of library will start to growth we definitely don’t want to add each library into our build script to be invoked. Using libs-affected we can automate this whole process and build only the changed/affected libraries.

We will need another scripts to be added:

And add this to our npm scripts:

"build:libs:affected": "node ./scripts/libs-build.js build",

Running npm run build:libs:affected -- HEAD HEAD~1 will trigger ng-packagr build only on those libraries.

Test

Another important step that we want to guarantee in our repository its testing. In one of my previous Unit testing angular applications with jest i’ve discussed how to start unit testing with jest. Let’s assume that we have already setup jest as our test framework what we need a command that will invoke only the tests of our changed libraries.

And as always another npm script:

"test:libs:affected": "node ./scripts/libs-test.js libs",

This is especially good if you are covering unit tests. On the other hand if you have integrations tests than it will be better to run all your tests suit all the time.

Bonus: versioning & release automation

The last step that we want to automate in our repository will be related to versioning and releases.

Leveraging lerna and standard commit we don’t need to manage ourselves all that.

npm i --save-dev lerna
./node_modules/.bin/lerna init

This will install lerna in our repository and initialise it. In order for lerna to work we have to modify the file lerna.json that was just created:

{
  "lerna": "2.7.0",
  "packages": [
    "libs/*"
  ],
  "version": "0.0.0"
}

Add another extra script:

And the last npm script:

"prerelease": "lerna publish --skip-npm",
"release": "node ./scripts/libs-publish.js",

The prerelease script will take care of increasing the version of our libraries, commit our changes and tag also the commit. Notice that we are passing the flag --skip-npm because the actual publishing of our libraries will be done by our script.

Using a tool like commitizen you can also add the flag --conventional-commits which will calculate automatically your next version based upon your commit message.

If you are dying to start using it for your libraries here’s a link to a repository with all the steps already in place.

https://github.com/izifortune/nrwl-packagr

Enjoy!


Head of Frontend at RyanairLabs @izifortune
Fabrizio Fortunato © 2021, Built with Gatsby