Publish Angular libraries like a Pro

Robert Hjalmers
11 min readOct 18, 2018

--

Maintaining, publishing and keeping packages up-to-date is often a bit cumbersome, but I doesn’t have to be and I’m going to show you just how easy it can be.

In this particular case I’m going show you how to create an angular library using Angular CLI and automatically publish it to npm using semantic-release together with travis. We’ll also configure travis to deploy an updated demo of our library to github pages. Note that most of the concepts we’ll cover aren’t just for angular and most of the tools we’ll use are framework agnostic so use whatever parts you like from this guide.

Prerequisites:

  • node and npm installed on your computer
  • Github account and a repository for our library
  • npm user to publish the page

Tools and frameworks that we’ll cover:

  • Angular CLI to create a new angular library
  • Travis for continuous integration
  • Github for code and demo site
  • Commitizen for commit message format
  • Husky to enforce commit format using git hooks
  • Semantic release to automatically publish and release new versions and generate a changelog
  • Renovate to update library dependencies automatically

Create an angular library

Before we start we need to make sure we have the latest version of angular-cli installed.

Install the latest version of angular-cli globally using:

npm install -g @angular/cli

Once angular-cli is installed we can continue and create our app and library. For the purpose of this tutorial I’m going to show you how I created angular-exemplify, an angular library for creating code snippets and examples from real code and markup (note that the code in the repo might differ a bit from this guide).

First we’re going to create the app and I’m going to call it “angular-exemplify-demo”, the reason I’ve added demo to the name is because this app will just be used as a demo for the library.

Create a new app:

ng new angular-exemplify-demo

The second step is to create the library itself, the name we choose here is the name of the package that will be published to npm later so choose a fitting name that describes your library.

Create library:

ng generate library angular-exemplify --prefix ex

Side note: don’t forget to replace “angular-exemplify” and “exemplify” with the name of your library as well as the prefix “ex”. If omitted the prefix will be set to “lib”.

The above command will create a new folder called “projects” containing our newly created library. Let’s try it out by adding the library module to our demo app:

app.module.ts

import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';

import { AppComponent } from './app.component';
import { AngularExemplifyModule } from '../../projects/angular-exemplify/src/lib/angular-exemplify.module';

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

Next step is to add the first component from our library to our demo app, let’s update app.component.html.

<ex-angular-exemplify></ex-angular-exemplify>

Give it a try and fire up the app by running:

npm start

We’ve now hooked up our library to our demo app, and this is what we’ve got so far.

very basic demo application showing or first library component

How to import the library?

You might have noticed that we imported our module directly from the project folder like this:

import { AngularExemplifyModule } from '../../projects/angular-exemplify/src/lib/angular-exemplify.module';

This is good when developing as changes to our library will be reflected instantly in our demo app, however keep in mind that consumers of our library will consume the compiled version that we get by building our library. Let’s go ahead and create an npm script in package.json that will compile our library, we also need to set the base-href for github pages for our demo build.

package.json

{
"name": "angular-exemplify-demo",
"version": "0.0.0",
"scripts": {
"ng": "ng",
"start": "ng serve",
"build": "ng build --base-href /angular-exemplify-demo/ --prod",
"build-lib": "ng build angular-exemplify",
"test": "ng test",
"lint": "ng lint",
"e2e": "ng e2e"
},
...
}

Build the library by running:

npm run build-lib

Angular-cli will now compile our library using ng-packgr, and place the library bundles in the default output folder “dist/angular-exemplify”. This is the package that we want to publish to npm and if we want to try it our ourselves before doing so we can update or import reference in app.module.ts to:

import {AngularExemplifyModule} from 'angular-exemplify';

Great, now we’ve covered the basic setup for a library built using angular-cli. I’m not going to go through the code or the process of creating angular-exemplify as I’m going to assume that you’ve already got something that you want to publish, therefor I’m going to jump right into how we’d like to setup it up to automate the whole process.

Setup and automate the release workflow

Before we continue we need to install a few more things.

Commitizen

Let’s start with commitizen that we’ll use to create git commit messages that can be analyzed by semantic-release inorder to determine the next version following the semantic versioning specification. This is essential to automating the release as it removes the obstacle of determining the next version number which is often is up for debate when humans are involved.

Install Commitizen cli tools globally with:

npm install commitizen -g

We also need to to let commitizen now which adapter to use i.e which template we want our contributors to follow. I’m going to use the conventional change log adapter for this tutorial.

Initialize the cz-conventional-changelog adapter by running:

commitizen init cz-conventional-changelog --save-dev --save-exact

Add npm script for easy commits:

{
"name": "angular-exemplify-demo",
"version": "0.0.0",
"scripts": {
"ng": "ng",
"start": "ng serve",
"build": "ng build --base-href /angular-exemplify-demo/ --prod",
"build-lib": "ng build angular-exemplify",
"test": "ng test",
"lint": "ng lint",
"e2e": "ng e2e",
"commit": "git-cz"
},
...
config": {
"commitizen": {
"path": "./node_modules/cz-conventional-changelog"
}
}

}

Side note: config for commitizen is added automatically by “commitizen init”.

All we need to do now to start the commit “wizard” is to type:

npm run commit
Commit “wizard” — commitizen with cz-conventional-changelog

Husky and commitlint

This is all fine and dandy and commitizen helps us create commit messages for our changes, but what happens if someone runs “git commit” or some third-party client and adds a custom message? That’s right, they’ll add a commit that we won’t be able to analyze correctly. Husky and git hooks to the rescue!

Install husky:

npm install husky --save-dev

Husky makes adding git hooks to our repository a breeze and what we want to do is to make sure that all git commits follows our format no matter where or how they’re created. So to achieve that we also need to lint our commit messages and we’ll do that using commitlint:

Install commit lint and config for conventional format:

npm install @commitlint/cli --save-dev
npm install @commitlint/config-conventional --save-dev

Next we’ll configure husky and commit lint, easiest is to add the configuration directly in our package.json.

{
"name": "angular-exemplify-demo",
"version": "0.0.0",
"scripts": {
"ng": "ng",
"start": "ng serve",
"build": "ng build --base-href /angular-exemplify-demo/ --prod",
"build-lib": "ng build angular-exemplify",
"test": "ng test",
"lint": "ng lint",
"lint-lib": "ng lint angular-exemplify",
"e2e": "ng e2e",
"commit": "git-cz"
},
"commitlint": {
"extends": [
"@commitlint/config-conventional"
]
},

"husky": {
"hooks": {
"pre-commit": "npm run lint && npm run lint-lib",
"commit-msg": "commitlint --edit"
}
},

...
}

Side note: I’ve added “lint-lib” to scripts as well and assigned it to the pre-commit hook to lint the demo app and library before committing code, in the future I’ll probably replace it with prettier, but feel free to add other hooks/scripts here too.

We’ve now enforced our commit format so if we try committing with a message that violates our format the commit will be rejected.

Semantic release

Let’s continue and add semantic release that will do all the heavy lifting for us.

npm install semantic-release --save-dev

There’s also a nifty cli for semantic-release that will help us set it up, however I’m going to show you how to set it up manually as we want to do some adjustments.

Travis

Semantic release is intended to run in a ci environment so before we continue with the configuration we need to install travis from githubs marketplace and give it access to our repository. The next step is to add a .travis.yml file to the root folder of our project.

.travis.yml

dist: trusty
sudo: false
language: node_js
node_js: node

install:
- npm ci
cache:
directories:
- $HOME/.npm
script:
- npm run lint-lib
- npm run build-lib && npm run build

deploy:
provider: pages
skip-cleanup: true
github-token: $GITHUB_TOKEN
keep-history: true
local-dir: dist/angular-exemplify-demo
on:
branch: master

git:
depth: 3

after_success:
- npm run travis-deploy-once "npm run semantic-release"

branches:
except:
- /^v\d+\.\d+\.\d+$/

Just to give a short explanation to what the above does.

  1. Install dependencies with “npm ci” similar to npm install, read more here
  2. Lint library “npm run lint-lib”
  3. Build library and demo app “npm run build-lib && npm run build”
  4. After success run semantic release
  5. Deploy demo app to github pages using pages provider (only on master branch)

We also need to add some more things to our package.json to make everything work.

{
"name": "angular-exemplify-demo",
"version": "0.0.0",
"scripts": {
"ng": "ng",
"start": "ng serve",
"build": "ng build --base-href /angular-exemplify-demo/ --prod",
"build-lib": "ng build angular-exemplify",
"test": "ng test",
"lint": "ng lint",
"lint-lib": "ng lint angular-exemplify",
"e2e": "ng e2e",
"commit": "git-cz",
"travis-deploy-once": "travis-deploy-once --pro",
"semantic-release": "semantic-release"

},
"release": {
"pkgRoot": "dist/angular-exemplify"
},

...
}

Install travis-deploy-once
To run the first script “travis-deploy-once” we need to install and add it as dev dependency:

npm install travis-deploy-once --save-dev

The script “travis-deploy-once --pro” will make sure the deploy script is only executed once in Travis and the pro option is for Travis Pro or Travis Enterprise (travis-ci.com) which if I’ve understood Travis correctly will replace travis-ci.org so we’re going to use it. The second script is not really necessary, but we’ll use it later to replace environmental variables in our app before it’s deployed.

We also need to add a release configuration to let semantic release know which package we’re releasing so lets point it to “dist/angular-exemplify” (the output folder for our compiled library).

Another thing we want to do to make it easier for contributors and others to know that our library is semantically released is to change the version in package.json for our library, from 0.0.1 to 0.0.0-semantically-released like this:

{
"name": "angular-exemplify",
"version": "0.0.0-semantically-released",
"peerDependencies": {
"@angular/common": "^6.0.0-rc.0 || ^6.0.0",
"@angular/core": "^6.0.0-rc.0 || ^6.0.0"
}
}

Create access tokens for github and npm

Create github token
Travis and semantic-release needs access to github to tag releases and deploy our demo to github pages so let’s head over to developer settings in github and generate an access token.

Create personal access token in Github

Once we’ve got the token, go to your app in travis (https://travis-ci.com/) and add it to environmental variables under the settings tab using the name GITHUB_TOKEN (see screenshot from settings in Travis below).

Create npm token
For semantic release to be able to publish our npm package we need to do the same with npm at npmjs.com (you have to login with your npm user).

Create access token at npmjs.com

Add the token from npm using the name NPM_TOKEN.

Settings in travis at travis-ci.com

Add environmental variables in Travis

Time to give it a try

Now that we’ve set everything up it’s time to push our changes to the repository. I recommend protecting the master branch in github and commit to develop or some feature branch before merging to master via a pull request.

Pull requests to master will automatically trigger a build in travis and if all goes well, publish a new version to npm and create release notes in github as well as updating our demo hosted on GitHub pages. Travis will also trigger builds on other branches and pull requests by default but semantic-release is configured to only release from the master branch.

Semantic release in action in Travis
Change log in github
Issue tracking by semantic-release bot

Update dependencies automatically with renovate

This is also optional but really handy when you want to keep your library up-to-date. Renovate will go through all the dependencies and create pull requests (one pull request per dependency) when new versions are available. Travis will then run tests (if you’ve added any) for each pull request and let you know if the pull request can be merged without breaking your app or library. If you’re confident with the tests you’ve written, pull requests done by renovate could be automatically merged to master and triggering a new release without human intervention!

  1. Begin by installing renovate from github marketplace.
  2. Add renovate.json to project root.

renovate.json

{
"extends": [
"config:base"
],
"baseBranch": "develop",
"packageRules": [
{
"packagePatterns": [
"angular-exemplify"
],
"rangeStrategy": "replace"
},
{
"packagePatterns": [
"angular-exemplify"
],
"depTypeList": [
"devDependencies"
],
"rangeStrategy": "pin"
},
{
"packagePatterns": [
"angular-exemplify"
],
"depTypeList": [
"peerDependencies"
],
"rangeStrategy": "widen"
},
{
"packagePatterns": [
"angular-exemplify-demo"
],
"rangeStrategy": "replace",
"semanticCommitType": "chore"
},
{
"packagePatterns": [
"angular-exemplify-demo"
],
"depTypeList": [
"devDependencies"
],
"rangeStrategy": "pin",
"semanticCommitType": "chore"
},
{
"packagePatterns": [
"angular-exemplify-demo"
],
"depTypeList": [
"peerDependencies"
],
"rangeStrategy": "widen",
"semanticCommitType": "chore"
}
]
}

You can read more about how to configure renovate here, the above configuration will create pull requests for our demo app using the chore commit type (to avoid creating new releases just because the dependencies for the demo app has changed). We separate the demo app from the library using packagePatterns “angular-exemplify-demo” and “angular-exemplify”. Our library on the other hand might get pull requests with the typ set to fix if included dependency receives a fix.

By default pull requests by renovate won’t be merged, but like I said before, if you’re confident with your tests there’s nothing stopping renovate from automatically merging them. Note however that renovate currently won’t be able to automatically merge into protected branches, more info and possible workarounds can be found here.

Add badges

Make sure to let your users know that your library is updated automatically with renovate, semantically released and commitizen by adding the following badges to the README.md file in your repository.

[![Commitizen friendly](https://img.shields.io/badge/commitizen-friendly-brightgreen.svg)](http://commitizen.github.io/cz-cli/)
[![semantic-release](https://img.shields.io/badge/%20%20%F0%9F%93%A6%F0%9F%9A%80-semantic--release-e10079.svg)](https://github.com/semantic-release/semantic-release)
[![Renovate enabled](https://img.shields.io/badge/renovate-enabled-brightgreen.svg)](https://renovatebot.com/)

The current build status is also available from travis (don’t forget to update the path).

[![Build Status](https://travis-ci.com/hjalmers/angular-exemplify.svg?branch=master)](https://travis-ci.com/hjalmers/angular-exemplify)

That’s it, now we’re all set and ready to publish packages like pros! Please comment if you found this little guide useful and want more articles like this:)

BTW The source code for angular exemplify with all the configuration files and scripts can be found here on github, note however that the real code differs a bit from the example above but the concepts are the same.

--

--

Robert Hjalmers

An enthusiastic problem solver with a genuine interest in front-end development, new technology, UX, design and branding.