It took a while since my last touch of AngularJS and I decided to play around with the latest version of Angular again. Although I have been following the Angular community and I know Angular feels more like a completely new framework rather than a continuation of AngularJS, I encountered some challenges when trying to create a simple distributable (via NPM) library myself. Since there are (almost) no official guidelines how to do it, I decided to write this post and to summarize my challenges and findings.
Angular CLI
One of the new things in Angular is the introduction of a command-line interface (CLI) to support the creation of apps that follow the community best-practices. The simplest way to create your first Angular app is by running the following:
1 2 3 |
ng new hello-world cd hello-world ng serve |
The output of this is an app that is bootstrapped in a simple HTML file. If you want to create a component in the app, you could do the following:
1 |
ng genrate component hello |
This command will create a set of templates (ts, html, css) for our component, which we can use in the app. The problem is, you cannot distribute this component independently as a library. You could, of course, expose the source files directly, meaning other developers will compile them into their build process. We can do better.
Compilation & packaging
Many people have created distributable libraries for Angular, so I decided to research how they did it. I went through a couple of such libraries and realized that everyone had done it differently. Furthermore, things varied with the different versions of Angular. At the point of writing this post, the current version is Angular 5, so I wanted to concentrate on it.
Using the Ahead-of-Time (AOT) compiler
Angular comes with two modes of compiling your source to JavaScript:
- Ahead-of-Time (AOT) compilation allows your app metadata to be compiled at build time
- Just-in-Time (JIT) compilation extracts and evaluates the metadata at runtime (in your browser)
AOT compilation is the preferred way in Angular, though JIT should be supported by your library as well. We can use Angular compiler directly.
1 |
npm install @angular/compiler-cli @angular/compiler --save-dev |
This will create a tiny shortcut in node_modules/.bin/ngc to call the compiler. You can also attach different settings in tsconfig.json to instruct the compiler. The AOT compiler is a drop-in replacement for the TypeScript compiler, tsc, with some more settings provided. In your package.json you can then add the following script:
1 2 3 4 5 6 7 |
{ ... "scripts": { "build": "./node_modules/.bin/ngc -p tsconfig.json" }, ... } |
To build your library, you can then run:
1 |
npm run build |
It will then create a dist folder (or as indicated in your tsconfig.json) with the compiled files. You can then pack your library by running npm pack or publish it on NPM with npm publish.
This solution is very simple, yet it covers only a simple usage scenario of your library. Developers use 3rd-party libraries in different ways: some may use SystemJS, others could use Webpack, some might consume packages in Node, others might do it in the browser as a UMD bundle or through global variable access. If you want to distribute your library to a broader audience, you need to do better.
Angular package format
Why not look at how the Angular team builds libraries? They have defined a common package format for the libraries they produce. Although, the document seems very informative, the community-built libraries don’t follow this format (at least many don’t). Angular libraries should work with any package usage scenario, so they include a version of the sources built for each such scenario. Whether you want to support all of these scenarios, depends on your vision for your library, but if you want to, you have to do some more work.
Filipe Silva has created a quick-start library seed project that aims at helping you build libraries against the Angular package format. Looking at the build.js script, you may get scared at first, but this is what it is necessary in order to cover all the different package usage scenarios. It uses the Angular compiler too, but then it compiles the sources multiple times plus it puts other relevant files (like package.json) in the dist folder. Then you could do npm pack in the dist folder directly.
ng-packagr
Filipe Silva’s quick-start seed project shows what you need in order to build a distributable Angular library, but it requires a lot of configuration (unless you decide to blindly copy the build scripts). David Herges, on the other hand, has created a tool, called ng-packagr, that compiles your source into a ready-to-distribute package, following the Angular package format. You need to install the tool first
1 |
npm install ng-packagr --save-dev |
and then you can create ng-package.json with some instructions:
1 2 3 4 5 6 |
{ "$schema": "./node_modules/ng-packagr/ng-package.schema.json", "lib": { "entryFile": "public_api.ts" } } |
Note that public_api.ts is the way the Angular team calls their entry file for their packages, but whatever name you choose, this file should contain references to your code that should go in the distributable package. Then in your package.json file, you could define the following script:
1 2 3 4 5 6 7 |
{ ... "scripts": { "build": "ng-packagr -p ng-package.json" } ... } |
Running npm run build will produce a folder containing your library: entirely self-sufficient, packaged according to Angular’s practices. You can then run npm pack to create a npm package or npm publish to publish it on NPM.
Conclusion
Creating a distributable Angular library may not be the easiest thing, as it requires you to understand well the different concepts of including your library in other apps by other developers (AOT vs. JIT, modules). If you know (or want to restrict) how your library is going to be used, the easiest way is to use the Angular compiler directly. If you want to create a full-feature library that can be used in any scenarios, ng-packagr is so far the easiest way. If you have any thoughts on that, I would be happy to hear them in the comments below. 🙂