How to build a group of interdependent publishable libraries all all namespaced under an NPM organization.
Find PRESENTATION_1.md at
Nx is a fantastic open-source tool for building monorepos built by the Nrwl team. Simplifies generation of Angular, React, and Nest apps and libraries. Find docs here and video tutorial here.
We can create an organization through npm and group all our libraries under this organization (e.g. @angular, @ngrx, @nrwl). This indicates to the consuming developer that the same organization created all your great libraries.
Publishing libraries like this allows you to:
- easily reuse features you’ve built for other apps
- share your code with anyone interested
- enables others to contribute back to the project
- show everyone (developers, companies, future employers) how great you are
Nx makes it really easy to create publishable interdependent libraries, this helps us to offer smaller libraries that focus on a specific set of solutions (form fields, state management, ui features, layout). This keeps our libraries lightweight and prevents end-developers from having to download unused dependencies.
If using as a monorepo, we can make changes to a library and easily run tests on all consuming apps. This helps ensure we don't introduce breaking changes or that we account for the changes everywhere that they break.
Additionally, we can even have internal apps that depend directly on the libraries while other apps depend on a package from a registry.
Whenever you want access to the code you're writing outside the context you're writing it in (i.e. CLI project or Nx workspace).
There are two main reasons to choose a workspace over an Angular CLI generated library system:
- You are using the monorepo to organize projects and libraries and would also like to publish some of those libraries
- You are building interdependent libraries and need to quickly how changes to one library effect the dependent libraries
If you are only wanting to publish a single library, or multiple stand-alone libraries you should check out @ngneat/lib. In addition to generating the publishable library, it generates templates for CODE_OF_CONDUCT.md, CONTRIBUTING.md, ISSUE_TEMPLATE.md, LICENSE.md, PULL_REQUEST_TEMPLATE.md, README.md. It also generates some scripts to help automate release.
For our purposes, we are deploying to NPM as a public package, but you can easily follow similar steps to deploy to other public or private registries. Consult their docs for information on setup and publishing.
-
Register for an NPM account if you don't already have one
-
Sign in through command line
npm login
Then follow prompts
-
Decide on a name for your workspace/organization. It's a good idea to make this short. It can be something non-descriptive (@mango) if you want to have disparate libraries, or something descriptive (@ngstate) if you want to have a group of related libraries.
-
Create an NPM Organization at www.npmjs.com/org/create. This will align with your Nx workspace name name allowing you to "scope" your libraries to one namespace. Do this before creating workspace to ensure that the organization name you want is available.
npm install -g @angular/cli
Use the npm organization from above as the namespace.
npx create-nx-workspace <your-namespace>
This command takes a little while (~3-5 min), so go get a cup of coffee.
Follow prompts:
- Pick the type of project (angular or angular-nest)
- Pick style extension (scss is great!)
- Name default app (
examples
is usually a good bet)
NOTE: Picking project type angular
or angular-nest
will create an 'Angular CLI Workspace' that create an angular.json
file to configure the workspace's libs
and apps
. Other project types (blank, react, etc..) create an nx.json
file to configure its libs
and apps
.
Each library should be focused on one feature (e.g. form-fields, state-management) and named appropriately.
-
Generate library
ng generate @nrwl/angular:lib <your-library-name> --publishable
Don't forget the
--publishable
flag! It tells nx to generate ang-package.json
andpackage.json
for the library and update the rootpackage.json
. This helps make publishing easy. -
Make public in root
package.json
"private": false
-
Disable Ivy in library's
tsconfig.lib.json
"angularCompilerOptions": { "enableIvy": false }
If you forget to disable Ivy you'll see this error:
node --eval "console.error('ERROR: Trying to publish a package that has been compiled by Ivy. This is not allowed.\nPlease delete and rebuild the package, without compiling with Ivy, before attempting to publish.\n')" && exit
-
Generate the library's first component
ng generate component --project=<your-library-name> --export=true
-
Export the component from the libraries
index.ts
export * from './input/input.component.ts
NOTE: This is not actually required, but it will be in the future after we enable Ivy for our library.
-
Make it do something!
-
commit changes
git commit -m 'Some changes on the master branch :)'
-
set initial version in package.json
-
build library
ng build <library-name> --prod
-
publish it!
cd dist/libs/<library-name> && npm pack && npm publish --access public && cd ../../../
You published the library, awesome! But running all those commands manually was kind of gross. There are tools that can help, but for now there's a release script, release.sh
, in the scripts
folder.
It automates release by:
- prompting user to input which package is being released
- ensuring that we're on the master branch
- prompting user to input version bump type (patch, minor, or major)
- building, packaging, and publishing the library
- committing a release commit after publishing
There are tons of other processes that could be added here (running tests), but it's a good start.
You can also add an npm run script to package.json
if you'd an alias.
"scripts": {
"release": "bash ./scripts/release.sh",
...
}
There are a lot of ways to do this, and I am going to cover fancier options and important consideration when releasing interdependent packages in an upcoming talk.
You can also checkout implementing a CI/CD pipeline with Travis here.
One of the most important pieces of a good library, is really good documentation. If you plan the build and keep organized examples of the features as you build them out, then it's relatively easy to post your testbed as interactive documentation on StackBlitz.
StackBlitz can serve any Angular CLI generated project that is on a public github repo, pretty easily. Unfortunately, our documentation app was generated by Nx and StackBlitz can't serve it at all. So we have to do a little trickery to get it to work, but it's actually a good exercise as it forces us to use the library like our users are using it. Take advantage of this by taking notes on any trouble you have setting it up.
-
Organize the development testbed. I recommend each library as a lazy-loaded module with each feature as a routed component under that module. This will give you a place to test each feature, and will translate well as an example when it gets turned into docs.
-
Generate a Angular CLI project, named the same as your documentation app, next to your nx workspace.
cd ../ && ng new <-docs-app-name>
-
Copy app from Nx workspace over to CLI project. Change directory back to Nx workspace and run:
cp -rf ./apps/<docs-app-name>/src/app ./../<docs-app-name>/src
-
Create new repository in Github, name it after the
docs-app-name
-
From the new cli projects root directory, connect the project to the repo
git remote add origin https://github.com/<github-username>/<docs-app-name>.git
git push -u origin master
-
Nx uses a different naming convention for the
<app-root>
component in theindex.html
file, using the the convention<namespace>-root
. This will need to be updated in the CLI generated app'sindex.html
. So if your namespace wascool-ng
<app-root></app-root> becomes <cool-ng-root></cool-ng-root>
This will probably be your first time using your package outside the context of the Nx workspace, and is a good opportunity to see what any consuming user will have to do to use your library. Make sure you carefully outline what it takes to get started with your library. If you have trouble getting it setup, imagine how hard it will be for anyone else to get started.
-
Add your package as well as any
dependencies
andpeerDependencies
to thepackage.json
of the CLI generated app, thennpm install
. -
Perform any other required setup (e.g. if you need Angular Material run
ng add @angular/material
). -
Run documentation project
ng serve
-
Push changes
git push
-
View it on StackBlitz
https://stackblitz.com/github/<your-github-username>/<your-project-name
-
Add above link to README.md file, now users can see exactly how to implement each feature of your library.
-
Don't enable Ivy in libraries yet. View Engine (pre-Ivy) libraries are forwards compatible with Ivy apps but the reverse isn't true. Recommendation is to wait until Angular 10 before publishing Ivy libraries. The folks at Angular In Depth have a lot more advice here.
-
Update your library's
README.md
file, this will be displayed in on it's npm page. It should help users consuming your package to get started. Here's a good article on Medium help make it fancy, but at a minimum you'll want to have installation instructions and basic usage. -
If your library consumes any packages (e.g. @angular/material, @angular/forms), be sure to add those packages to the
peerDependencies
in itspackage.json
file, this will warn users if they are missing its dependencies.
-
Alfredo Perez has a great series on publishing libraries with Nx. He even goes into implementing a CI/CD pipeline with Travis here.
-
If you'd like some help with your README, here is a good article on Medium by Adnan Rahić. Checkout shields.io if you'd like to add some badges to your README.
-
Learn more on declaring
dependencies
andpeerDependencies
in yourpackage.json
here -
If you'd like to really dive deep, here is an article from the Angular Blog on building an
update
schematic to automatically fix breaking changes when you make them.