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!