Publish your Angular library

Friday, Oct 26, 2018 - Posted by Amphinicy Blogger
Publish Angular library to NPM with ng-packagr

You want to have an application that can be run as a standalone and you can pack it as a library that can be published to NPM and then reused (installed) inside other applications? You can use Angular CLI to generate your application, and ng-packagr to pack it and publish it.

You can also use this tutorial just to see how you can pack and publish your application as a library.

Using ng-packagr to build a library

Maybe this will look like a long and boring setup, but follow it through and in the second chapter (Improving build) we will automate it more. Here you can see what will happen under the hood and after you got everything set up, you will not have to worry about it again.

Create a project

Use Angular CLI to set up a project:

ng new great-app

After that, you have a fully working application. Here I will not go into details about Angular CLI. Instead, I will show you how you can publish your application as a library.

Idea

You have your AppModule that runs your standalone application. We will not be packing that into our library. Therefore, create another module inside your application that will be then imported by the AppModule.

cd src/app
ng g module reusable

and import it inside your AppModule.

Now, do all development inside the ReusableModule and specific stuff (like navigation for example) inside AppModule which you can't reuse in other application.

What we will be publishing to NPM is ReusableModule.

If you are just writing some kind of library (let's say a spinner), you can use AppModule for testing it in a browser and publish only SpinnerModule.

Setting up ng-packagr

NPM module ng-packagr is used to "Transpile your libraries to Angular Package Format".

Install it via NPM:

npm install ng-packagr --save-dev

Create ng-package.json inside your root folder which is used for configuring ng-packagr by telling it where to start from:

{
  "$schema": "./node_modules/ng-packagr/ng-package.schema.json",
  "lib": {
    "entryFile": "public_api.ts"
  }
}

Now, file public_api.ts will contain everything you want to make accessible to other application through the code (e.g. importing some service to other application).

For now, just create public_api.ts with the export of your module.

export * from './src/app/reusable/reusable.module';

As you create other services that you want to make accessible through another app, add those in here. If you don't add them, don't worry, other application can still use your module and functionalities. It can still use components you have created, but only through HTML. It can not reference them in their TypeScript code. Everything you want that it can be accessible via TypeScript in another app, export it here.

Now, another app can just do the import of the module:

import { ReusableModule } from 'great-app'

Unfortunately, ng-packagr does not package assets so if you need to package them, you can use cpx module to copy them manually to dist folder. So install that:

npm install cpx -g --save-dev

To make your life easier, add ng-packagr task as a script to package.json.

"packagr": "ng-packagr -p ng-package.json && cpx "./src/assets/**/*" ./dist/assets",

You are now completely ready to build your library (output will be in dist folder).

Building with ng-packagr

Two important things before running build.

Since NPM 3+, it does not allow you to publish your library with dependencies listed without whitelisting them first. Here, we will not be doing that. We will be publishing our library with only its own code. Dependencies needed for the library to be working we will put to peerDependencies. 

When your library will be installed via npm install, another app will get notification/warning that great-app has some peer dependencies and that it should install them manually.

Also, you have to change your package.json and set private to false so it can be published.

Once you have changed dependencies to peerDependencies in your package.json you can run build and increased the version (you can't publish two same versions of the library to NPM):

npm run packagr

And there you go, you have built your library. Now you have to push it to NPM. Do that by doing (assuming you have NPM account):

npm publish dist

Revert peerDependencies to dependencies back.

Improving build

We will improve this process of building by

  • having the script for updating our package.json (no need to manually update peerDependencies)
  • using npm version command to build and publish automatically

Preparing scripts

We first need to install one dependency used for parsing JSON.

npm install jsonfile --save-dev

We will have two scripts (one for renaming dependencies to peerDependencies and other vice versa). Put them inside /scripts folder

prepare-package-to-peer.js

const jsonfile = require('jsonfile'),
  path = require('path'),
  packageJsonLocation = path.join(__dirname, '..', 'package.json'),
  packageJson = jsonfile.readFileSync(packageJsonLocation);
packageJson['peerDependencies'] = Object.assign({}, packageJson['dependencies']);
delete packageJson['dependencies'];
jsonfile.writeFileSync(packageJsonLocation, packageJson, {spaces: 4})

revert-package-from-peer.js

const jsonfile = require('jsonfile'),
  path = require('path'),
  packageJsonLocation = path.join(__dirname, '..', 'package.json'),
  packageJson = jsonfile.readFileSync(packageJsonLocation);
packageJson['dependencies'] = Object.assign({}, packageJson['peerDependencies']);
delete packageJson['peerDependencies'];
jsonfile.writeFileSync(packageJsonLocation, packageJson, {spaces: 4})

This scripts will be run in npm version steps which we will define in package.json

Preparing NPM version tasks

NPM version command is used to bump the version in package.json. Read in the official docs which parameters it gets.

Also, besides doing that, it allows us to define the pre-version and post-version steps where we will do our peerDependency renaming (pre-version) and publishing (post-version). 

Define those steps in package.json under scripts:

"preversion": "node scripts/prepare-package-to-peer.js",
"version": "npm run packagr && npm publish dist && node scripts/revert-package-from-peer.js",
"postversion": "git push origin master && git push origin --tags"

What are we doing here:

1) [preversion] Renaming dependencies to peerDependencies in package.json via script

2) [version - run by NPM] Bump version in package.json

3) [version] Run packagr task to build the library inside dist folder

4) [version] Rename peerDependencies to dependencies

5) [postversion] Commits and pushes the latest code changes (change in package.json) together with tags

Now you can run NPM version command (read about it's parameters on official docs of the NPM), e.g.

npm version patch

and have your library built and published.