In this post, we are going to see how an Angular 2 component library can be built and then consumed. Check this repository for a minimal sample library and examples on how to consume it in both SystemJs and Webpack. Let's go over the following topics:

Choosing a format to publish an Angular 2 library - CommonJS

Setting up the Typescript compiler

Publish typings and source maps

Exposing the library public API

How to publish component CSS

Isolating CSS via component encapsulation

Overriding CSS if needed

Publishing a library to npm

Consuming a library using SystemJS

Consuming a library using Webpack

Choosing a Format to Publish an Angular 2 Library

During the alpha stage of Angular, the Angular bundles were initially published in SystemJS format, but now they are published in CommonJS (see here.) This happened for a couple of good reasons:

CommonJS is easily consumable by existing popular tools such as Browserify or Webpack.

CommonJS can also be easily consumed by SystemJS.

CommonJS is the node.js module format, which allows for components to be more simply used in server-side rendering.

the size of CommonJS bundles is smaller (in the case of Angular the gain was around 20%.)

Due to the above-mentioned reasons, the current most convenient format to publish an Angular 2 library is a set of JavaScript-only files in the CommonJS module format.

What Does a Published Library Look Like?

Let's take a look at what the sample library (named angular2-library-example) looks like when published to npm:

. |____components.d.ts |____components.js |____lib | |____HelloWorld.d.ts | |____HelloWorld.js | |____HelloWorld.js.map |____package.json

The following are the different components of the library:

components.js and components.d.ts define the public API of the library.

and define the public API of the library. the lib folder contains the bulk of the library, in this case, the Angular 2 component named HelloWorld

each JavaScript file is published together with the TypeScript type definitions and source maps

each JavaScript file follows the CommonJS module format of node, exporting its dependencies via module.exports

Why Publish Unbundled Files?

Most applications consuming Angular 2 libraries will probably have a frontend build based on some combination of Gulp, jspm, SystemJS, or Webpack.

For those cases, it's best if the consumer of the library itself does the bundling. This ensures that bundlers like Webpack will give optimal results for the end user and that they won't throw warnings.

Should Bundled and Minified Files Also be Published?

Its a good idea to also publish the library as a single concatenated file, together with a minified version of it. This will allow library consumers that don't use a frontend build to still be able to use the library.

Setting Up the TypeScript Compiler

Since version 1.5, TypesSript now allows to compile the ES6 module syntax into any module format. For example, this is the standard way of importing a component using ES6 syntax:

import {HelloWorld} from 'angular-library-example/components';

This can then be transformed by the compiler into different module syntaxes depending on the target module format. For example, this is the output for CommonJS:

var HelloWorld = require('angular2-library-example/components').HelloWorld

We just need to configure the TypeScript compiler to output CommonJS modules. Let's go over the whole TypeScript configuration of the sample library:

{ "compilerOptions": { "emitDecoratorMetadata": true, "experimentalDecorators": true, "target": "es5", "module": "commonjs", "moduleResolution": "node", "removeComments": true, "sourceMap": true, "outDir": "../lib", "declaration": true } }

This config ensures the following:

emitDecoratorMetadata and experimentalDecorators enable the use of annotations such as @Component

and enable the use of annotations such as target: 'es5' means that the output of the compilation is just plain JavaScript, instead of for example, ES6

means that the output of the compilation is just plain JavaScript, instead of for example, ES6 module: 'commonjs' means that each file will be exported as a separate CommonJS module. For example, HelloWorld.ts will be converted into an isolated module

means that each file will be exported as a separate CommonJS module. For example, will be converted into an isolated module moduleResolution: 'node' ensure that the import paths will work exactly the same way in the browser as in a node runtime

ensure that the import paths will work exactly the same way in the browser as in a node runtime outDir is where the output of the TypeScript compiler is saved

is where the output of the TypeScript compiler is saved sourcemap: true triggers the generation of source maps

triggers the generation of source maps declaration: true triggers the generation of the typings *.d.ts files, that enable IDE Intellisense

Creating the Public API of a Library

The sample library only contains the HelloWorld component, importable from the components barrel. Each library should provide at least one import barrel in order to:

simplify the import of library components via IDE Intellisense

Make it clear which components of the library are meant to be used publicly, and which are just internal implementation details

Publish Components via CommonJS

As we saw all components of the library will be inside the lib folder. To make them publicly visible in the angular2-library-example/components barrel, we need to create a components.js in the root of the module:

exports.HelloWorld = require('./lib/HelloWorld').HelloWorld;

Inside the components.js barrel file, we export using the CommonJS

exports syntax any component that we want to make public. In this case, only the HelloWorld component is published.

Informing TypeScript of Library Content

The file components.js would be sufficient to use the HelloWorld component, but to enable IDE Intellisense we also need to publish

components.d.ts :

export * from './lib/HelloWorld';

This will allow auto-completion when importing HelloWorld . Next, let's see how we can publish the CSS of a component.

How to Publish CSS of an Angular 2 Component

Angular 2 components work by default in emulated view encapsulation mode. This means that by default, the styles of Angular 2 components are isolated and don't affect the rest of the page. Instead those styles will (in almost all cases) affect only the component itself.

How Does Emulated CSS Encapsulation Work?

For example, take the HelloWorld component:

@Component({ selector: 'hello-world', styles: [` h1 { color: blue; } `], template: ` <div> <h1 (click)="onClick()"> {{message}} </h1> </div>` }) export class HelloWorld { ... }

If we run one of the examples of the sample library, we can see that the

color:blue inline style is being applied in the following way:

h1[_ngcontent-rgt-2] { color: blue; }

The value _ngcontent-rgt-2 is an automatically generated attribute that Angular assigned to the outer most element of the template:

<h1 _ngcontent-rgt-2="">Hello World!</h1>

So, this is how emulated encapsulation works! It's a simple trick applied at runtime to ensure that the component styles all have at least one attribute selector. This greatly increases the specificity of the component styles, making it unlikely that they ever get overridden.

How to Override Component CSS if Needed

Although the style of a component is well isolated, it can still be easily overridden if necessary. For that, we just need to add an attribute to the body of the page:

<body override> <app></app> </body>

The name of the attribute can be anything. No value is needed and the name override makes it apparent what it's being used for. To override component styles, we can then do the following:

[override] hello-world h1 { color:red; }

As we can see, although the component styles have a high specificity, they can still be easily overridden if it's necessary—and there is always some corner case for that.

How to Publish Component CSS Then?

The current easiest way to write component styles is inline as shown above. Here are several reasons for this:

writing the styles and templates inline encourages the components not to grow too large

if the CSS (or the template) starts to grow big, it's a sign that the component should be split in several components

it's very convenient to have everything related to the component in a single file (like in React)

the Angular 2 template transforms mechanism is not yet ready (it's scheduled for final). But when this is ready, templates and CSS will likely be inlined everywhere via plugins, like the case of templates and the various template cache plugins in Angular 1

The convenience of distribution, combined with the good isolation and the ease of override, make the combination of inline styles and emulated encapsulation a good solution for publishing component CSS.

Publishing a Library to npm

Once the library is built in the lib folder, it's time to distribute it. You probably have configured .gitignore to exclude the lib folder. But, npm uses .gitignore by default, so the lib folder won't be published!

We need to start by creating an .npmignore file that says what should be ignored when publishing to npm:

examples node_modules src

In this case, all folders were ignored except the lib folder.

Creating a Build Script

In the case of a component library, we can probably keep the build very simple. Using npm scripts is likely sufficient:

scripts: { build: "rm -rf lib && tsc -p src" }

This script can be run using npm run script , and it creates a lib folder ready to publish. Tools like Webpack can also be used effectively via npm scripts.

If the approach becomes cumbersome, then it's better to introduce Gulp. But for most components, probably npm scripts will be sufficient.

npm Semantic Versioning

When publishing new versions of the library to npm, its important to use the built-in npm version command line tool according to semantic versioning:

npm version [<newversion> | major | minor | patch]

This tool will update the package.json version, and commit the change to git. The convention for versioning is the following:

major: breaking changes

minor: new features, backwards compatible to current major

patch: no new features, only bug fixes (and still backwards compatible)

Publishing to npm

In order to publish to npm, first create an account in npmjs.com.

Then set up your username and password in your local npm:

npm adduser

When this is done, you can simply do:

npm publish

This is the essential of how to publish component published to npm. It's important to follow semantic versioning to avoid breaking the builds of library users.

How to Consume an Angular 2 Library

Many library consumers will probably be using a module loader and a package manager.

Let's see how the sample library can be consumed using the two most commonly used tools: SystemJS and Webpack. In both cases, the first step is to install the library using npm:

npm install angular2-library-example

Consuming a Library Using SystemJS

See here for an example of how to load the sample library using SystemJS

SystemJS is the most used module loader in many of the Angular 2 examples currently available. See this post for an introduction to SystemJS and the associated Jspm package manager.

Ideally we would install Angular 2 with one command via jspm:

jspm install angular2 .

But this is currently not working, see for example, this issue. What currently works is to load Angular 2 with some script tags:

<script src="/node_modules/angular2/bundles/angular2-polyfills.js"></script> <script src="/node_modules/es6-shim/es6-shim.min.js"></script> <script src="/node_modules/systemjs/dist/system.src.js"></script> <script src="/node_modules/angular2/bundles/http.min.js">

Then, import any libraries like the sample library via System.config :

</script> <script> System.config({ defaultJSExtensions: true, packages: { "/angular2": {"defaultExtension": false} }, map: { 'angular2-library-example': 'node_modules/angular2-library-example' } }); </script>

And then, bootstrap the app:

System.import('build/App')

A good place to keep track of what is currently possible with SystemJS is to check the scaffolding of angular-cli here over time. The issue with Jspm will probably be fixed soon.

Consuming a library using Webpack

Here is an example of how to consume the sample library using Webpack

The HTML needed for bootstrapping an Angular 2 app is very simple:

<script src="bundle.js"></script>

In the case of the sample library example, the bundle file does not exist in the file system, it's in memory in the webpack-dev-server tool.

This bundle is created according to the following Webpack configuration:

module.exports = { entry: "./src/app.ts", output: { filename: "bundle.js" }, devtool: 'source-map', resolve: { extensions: ['', '.webpack.js', '.web.js', '.ts', '.js'] }, module: { loaders: [ { test: /\.ts$/, loader: 'ts-loader' } ] } };

This configuration does the following:

sets up a TypeScript loader named ts-loader , that allows for Webpack to compile TypeScript files while loading them

, that allows for Webpack to compile TypeScript files while loading them activates source maps

defines the entry point of the application ( app.ts )

One note on using Webpack to import a local version of a library using npm link: You will likely run into cannot find module issues. Check here for a solution.

Conclusions

With the set of practices presented above, it's definitely possible to publish an Angular 2 library today. Check some early examples of libraries that were the basis for this post:

ng2-pagination by @michlbrmly - an Angular 2 port of the Angular 1 paginate directive

ng2-translate by @OCombe - An implementation of Angular 1 ng-translate, for internationalizing Angular 2 applications

The best practices for publishing libraries will probably evolve over time, its best to check angular-cli from time to time, to keep track of best practices and see what scaffolding is available.

References

How to publish a library for Angular 2 on npm from @OCombe