How to publish a library for Angular 2 on npm

Disclaimer: This article was last updated for Angular 2 beta 1.

Update 2017–03–20: this article is a bit outdated, it can still be used for some parts but you should probably read this article by Minko Gechev http://blog.mgechev.com/2017/01/21/distributing-an-angular-library-aot-ngc-types/
 And take a look at that starter:
https://github.com/preboot/angular-library-seed

Context

You can skip this if you just want the code!

I started a new project a few days ago, and quickly needed to add i18n support. When I was using Angular 1, I’ve been spoiled by the awesome Angular Translate written by the even more awesome Pascal Precht ʕ•̫͡•ʔ. I wanted my i18n translations to be easy and quick like I was used to, but since the i18n support for Angular 2 isn’t ready for now, I decided to write my own lib, and to share it with the world. That’s how ng2-translate was born !

And that’s where things started to get hairy.

If you’ve tried Angular 2, you might have noticed that the setup for a new project is still a bit complicated (but a lot better than it was at the beginning of the alpha). The Google team is working really hard right now to make this easier. Their ultimate goal is that you shouldn’t have to setup anything other than linking to the Angular 2 library and start coding your app.

But if you’re writing Angular 2 code then you’re probably using TypeScript to do so (if that’s not the case then I highly encourage you to). And TypeScript uses what they call “TypeScript Definitions” (also named typings). Those are TypeScript files that only contain the interfaces of your classes.

When you use a typing file (which is a file that ends by .d.ts) you will get auto-completion in your favorite IDE (let it be Webstorm, Visual Code studio, or any other IDE that supports TypeScript) and also errors when you’re using incompatibles types or trying to access properties/methods that don’t exist. The TypeScript compiler will also warn you when that’s the case.

For example, if you’re trying to check the equality of a string and a number, it will tell you that both types are incompatible and might not produce the result that you expect.

It also warns you when you are trying to access a property/method that isn’t defined in this object (for example trying to call .then on a variable that isn’t a Promise).

All of this is really awesome, but it only works if the lib that you’re using exports these TypeScript definitions in a way that your IDE/compiler can use.

You can use TSD to get TypeScript definitions for the most popular libraries, but I will explain here how to ship those typings with your library in a way that doesn’t involve publishing them to TSD (one less thing to do !).

This technique is similar to the one used by the Google team for Angular 2.

Let’s write our library

This is where we start the real code!

The first thing that you need to do to write your library is to init your npm module.

Go to your library folder and type:

npm init

This will generate the package.json file that you’ll need for your library.

Since we will be writing a library for Angular 2, let’s install it:

npm install --save angular2

Create your first .ts source file at the root of your folder and give it the name of your library. In my case, I named it ng2-translate.ts because I decided to call my library ng2-translate. This will be the name that people will use when they import your library in their own code:

import {TranslateService} from "ng2-translate/ng2-translate";

When they write “ng2-translate/ng2-translate”, the first “ng2-translate” is the name of your npm module (and folder inside the node_module directory), and the second one is the name of the main file that we just created (without the .ts extension because it is the default, so we don’t need to write it).

This main file will just be used to export all of the things that you want to export from your library. We will put the real source files in a separate folder that we’ll name src (but you can name it however you want).

Create a .ts source file in your src folder and write your code in it. Let’s take the example of my ng2-translate library here. I’ll make a file named translate.service.ts:

import {Injectable} from 'angular2/angular2';
import {Http} from 'angular2/http';
@Injectable()
export class TranslateService {

constructor(http: Http) {
// ...
}
}

Now let’s move back to our ng2-translate.ts file at the root of your project. We want it to export this TranslateService. Let’s write this:

export {TranslateService} from './src/translate.service';

Since we will probably put more than TranslateService in the translate.service.ts file (some interfaces, some other classes), we will change it to export everything that is publicly available:

export * from './src/translate.service';

If you add a new source file later on, just add a new line to your ng2-translate.ts file. For example if we make a translate.pipe.ts file, add it like this:

export * from './src/translate.pipe';
export * from './src/translate.service';

We’re done with the library, let’s make it ready for distribution.

Preparing your project for distribution

To use your library, people will need two things:

  • the .js files compiled to ES5 that will be loaded by the browser
  • the .d.ts TypeScript definition files (typings) that will be used for auto-completion and type checking when you’re coding.

Those files can be generated by the TypeScript compiler. Let’s install it locally:

npm install --save-dev typescript

Since we don’t want to forget to generate those files when we release a new version of our library, we need to define a script that npm will call when we publish the lib. To do that we will use the prepublish script which will be executed by npm just before you publish a new version of your lib.

The description of this script states:

prepublish: Run BEFORE the package is published. (Also run on local npm install without any arguments.)

When it says “local npm install”, it means that you’re doing npm install inside the folder of the module that you’re writing. It doesn’t mean that it is executed when someone does npm install inside their own project that uses your library.

Edit your package.json file and add this:

"scripts": {
"prepublish": "tsc"
}

The tsc command is what will compile your .js files and .d.ts typings, but we need to configure a few things for the compiler.

To do that we will create a tsconfig.json file at the root of our project that will be used by default by the TypeScript compiler. Put this inside your config file:

{
"compilerOptions": {
"noImplicitAny": true,
"module": "commonjs",
"target": "ES5",
"emitDecoratorMetadata": true,
"experimentalDecorators": true,
"sourceMap": true,
"declaration": true
},
"files": [
"ng2-translate.ts"
],
"exclude": [
"node_modules"
]
}

Let’s review those parameters together:

  • noImplicitAny: Raise error on expressions and declarations with an implied ‘any’ type.
    You can put it to false, but it isn’t recommended.
  • module: we choose “commonjs” here. It is the most commonly (!) used module system, it’s supported by all loaders (ES6 import, SystemJS, Webpack, RequireJS & Browserify).
    It also looks like it is the fastest module system right now (until the ES6 implementation is supported by all browsers). For the beta Google decided to add UMD modules which are AMD/CommonJS compatibles.
  • target: Specify ECMAScript target version: ‘ES3’ (default), ‘ES5’, or ‘ES6’.
  • emitDecoratorMetadata: Emit design-type metadata for decorated declarations in source.
    Required for Angular 2.
  • experimentalDecorators: Support ES7-proposed asynchronous functions using the async/await keywords.
    Required for Angular 2.
  • sourceMap: Generates corresponding ‘.map’ file.
  • declaration: Generates corresponding ‘.d.ts’ file.

In the “files” parameter just put your main .ts file because the compiler will follow your imports anyway. If you decide to make multiple separate modules (like Angular 2 with http, router, …) then you should add them here as well.

Exclude the “node_modules” folder because you don’t want to compile those files for nothing.

Now we can test our prepublish hook by doing:

npm run prepublish

It should generates the .js, .d.ts and .js.map files for your project.

Now, we don’t want to commit those files to github because they are unnecessary and will pollute your repository, so let’s make a .gitignore file and add this:

# Node generated files
node_modules
npm-debug.log
# OS generated files
Thumbs.db
.DS_Store
# Ignored files
*.js
*.map
*.d.ts

Since we added those files to the .gitignore file, you might be wondering how the user who installs your lib will get them. Well I discovered something here: when you do npm install, you don’t pull the module from github, you get it from the npm cache servers. Those servers will store what ever you pushed with npm publish, not what is available on github.

So when you’re doing npm publish, it will first call the prepublish script that we just wrote, generate the .js, .d.ts & .js.map files and push all of your files to the npm cache server.

But there is a trick here, npm will only publish the files that it doesn’t ignore and by default it will use the .gitignore file.

So we need to make a .npmignore file that is a copy of your .gitignore file with the only exception that we will remove *.js, *.map & *.d.ts from it. If a .npmignore file is present, npm won’t use your .gitignore file!

Your .npmignore file should look like this:

# Node generated files
node_modules
npm-debug.log
# OS generated files
Thumbs.db
.DS_Store
# Ignored files
*.ts
!*.d.ts

Wait, why are you ignoring `*.ts` files? You just said remove all the ignored files? Well it’s because we don’t want to publish those `.ts` source files to npm. The typescript compiler will load them by default (instead of your `.d.ts` files) and it will generate errors because those sources files are not in your root directory. The end-user doesn’t need the TypeScript source files, he will consume the ES5 js files anyway.

The last thing that we need to do is to tell your IDE/TypeScript compiler what your typing file is. To do that open package.json and add this:

"typings": "./ng2-translate.d.ts",

And we’re done !

Let’s test our setup

We want to make sure that all we did here works. To do that there is a “secret” technique called npm link. It will allow you to use the lib that you’re developing in your own project without having to publish it.

Go to the root folder of your library and type:

npm link

Now go to the root of your project and type:

npm link ng2-translate

Of course you should replace “ng2-translate” by the name of your library.

This will install the library in your project as if you had installed it with npm install.

You should be able to import your library and use it in your project without any other setup ! For example to use our TranslateService we would do:

import {TranslateService} from "ng2-translate/ng2-translate";

And if we use the SystemJS loader, we would have to add our library to the config.js file like this:

System.config({
packages: {
"/ng2-translate": {"defaultExtension": "js"}
}
});

This way when you import the TranslateService it will load the file node_modules/ng2-translate/src/translate.service.js because in your ng2-translate.ts file you have exported the TranslateService from ‘./src/translate.service’.

And we’re all done, now you just have to publish the library to npm with:

npm publish

Congratulations !

I hope that you will now be able to make your own Angular 2 libraries. If I missed anything here, don’t hesitate to ask for it in the comments and I’ll update my article.

You can find all that I explained here in my ng2-translate repository.

I would like to thank Rob Wormald & Dmitriy Shekhovstov who helped me alpha test my library and find the right setup.