Angular2 用の npm パッケージを作成する (TypeScript)

## はじめに

現在ベータ版の [**Angular2**](https://angular.io/) を少し触ってみたい、ということで公式サイトにのっている [QuickStart](https://angular.io/docs/ts/latest/quickstart.html) を読み進めてみました。

**Angular1.x** の経験があったので理解にはあまり困らずに進めることができたのと、**TypeScript** が思ったより書いていて楽しい言語だったので、QuickStart から一歩足をだして、**Angular2** のライブラリ作成に挑戦してみました。あまりフロントエンドすることなかったので **npm** パッケージの作り方を調べながら、なんとなくそれっぽくなったのでまとめてみようと思います。

記事の最後に作ったものと、参考にしたサイトを書いておくので、そちらも参考にしていただければと思います。

## ゴール

**Angular2 のライブラリを作って公開できる**

## 3行まとめ

- Git では **.ts** のみ管理 (**.js** や **.d.ts** ファイルは管理しない)
- **TypeScript** のコンパイルは **es5**、 `moduleResolution` を `node` に指定する
- 作ったライブラリをインポートする際に **SystemJS** の **map** でパスの指定が必要

*備考1: 現在の自分の理解ではこうなっていますが、ベストプラクティス的なものではないです。(Angular2 の QuickStart がターゲットになってます)*

*備考2: SystemJS でインポートする際に map の指定を不要にするための方法がありそうな気がしてます。気がしているだけかもしれませんが。*

## 方針

### ディレクトリ構成

- `lib` ディレクトリを作成してそこにコードを書く
- `lib/components` など、機能ごとにディレクトリを作成してまとめる
- `lib/components.ts` で必要に応じて `export` する
 - (もしかしたら `/` においた方がいいのかも)

*`src` ディレクトリと `dest` ディレクトリを作成して、`ts` ファイルを `src` において、コンパイル結果を `dest` においたりする例もあり、このへんは好みなのかなと。*

```txt:ディレクトリ構成
angular2-example-library
├── lib
│   ├── components
│   │   ├── example.component.ts
│   │   └── sample.component.ts
│   ...
│   └── components.ts
...
├── .gitignore
├── .npmignore
├── package.json
├── tsconfig.json
├── tslint.json
└── typings.json
```

```ts:lib/components.ts
// 公開したいものを export
export * from './components/example.component';
export * from './components/sample.component';
```

```ts:lib/components/example.component.ts
import {Component} from 'angular2/core';

@Component({
  selector: 'example',
  template: '<p>example!</p>'
})
export class ExampleComponent {}
```

```tcsh:.gitignore
# TypeScript
*.js
*.map
*.d.ts
```

```tcsh:.npmignore
# DO NOT IGNORE TYPESCRIPT FILES FOR NPM
# TypeScript
# *.js
# *.map
# *.d.ts
```
>
`.gitignore` で `.js` ファイルなどを指定しているのですが、パッケージ公開の時に **TypeScript** のコンパイルを行って `js` ファイルなどを生成する 設定を　**package.json** に記述するので、実際の npm パッケージには `.js` ファイルも含まれます。

### 設定ファイル

```json:package.json
{
  "name": "angular2-example-library",
  "version": "0.1.0",
  "description": "Angular 2 Example Library",
  "homepage": "https://github.com/example/example.git",
  "author": "Mr. Example <example@example.com>",
  "dependencies": {
    "angular2": "^2.0.0-beta.9",
    "es6-promise": "^3.0.2", 
    "es6-shim": "^0.33.3",
    "reflect-metadata": "0.1.2",
    "rxjs": "5.0.0-beta.2",
    "zone.js": "0.5.15"
  },
  "scripts": {
    "test": "tslint ./lib/*.ts ./lib/**/*.ts",
    "prepublish": "tsc",
    "typings": "typings"
  },
  "typings": "./lib/components.d.ts",
  "repository": {
    "type": "git",
    "url": "git://github.com/example/example.git"
  },
  "devDependencies": {
    "tslint": ">=3.5.0",
    "typings": ">=0.6.8",
    "typescript": "^1.8.7"
  }
}
```
> 
直接依存しているのが `angular2` のみの場合は, `dependencies` は `angular2` ののみを記述し、それ以外は `angular2` が依存している(孫依存)ものなので、記述せずに利用者に委ねる方がいいのかもしれません。
`script` の `prepublish` で npm パッケージ公開時に TypeScript のコンパイルをするように指定しています。 TypeScript のコンパイル設定は下記の `tsconfig` に記述。

```json:tsconfig
{
  "compilerOptions": {
    "module": "commonjs",
    "target": "es5",
    "sourceMap": true,
    "emitDecoratorMetadata": true,
    "experimentalDecorators": true,
    "removeComments": true,
    "noImplicitAny": false,
    "declaration": true,
    "moduleResolution": "node",
    "rootDir": "lib"
  },
  "files": [
    "node_modules/angular2/typings/browser.d.ts",
    "lib/components.ts",
    "lib/components/fa-stack.component.ts",
    "lib/components/fa.component.ts"
  ],
  "exclude": [
    "node_modules"
  ]
}

```
>
Angualr2 の Quickstart の例でインポートできるようにしてたらこうなりました。もっといい書き方があるのかもしれません。

### パッケージを公開する

**省略**。公開前にローカルで試すのに `npm link` というコマンドが便利でした。

- [**3分でできるnpmモジュール**](http://qiita.com/fnobi/items/f6b1574fb9f4518ed520)
- [**npm linkが意外と便利だった**](http://www.tejitak.com/blog/?p=1535)

### 作ったものを使ってみる

- **angular2-example-library**  をインポートするときのパスを指定する(**SystemJS**)
- **angular2-example-library** インポート時に拡張子(js)を補完するようにする (**SystemJS**)
- 実際に使うファイルで **import** する

*サンプルコードの例は Angular2 の QuickStart のものです。*

```html:index.html
<script>
  System.config({
    defaultExtension: 'js',
    packages: {
      app: {
        format: 'register',
        defaultExtension: 'js'
      },
      // これを書かないとロードするときに .js 拡張子が補完されなかった
      "angular2-example-library": {"defaultExtension": 'js'}
    },
    map: {
      // 実際のファイルのパスを指定する。
      'angular2-example-library': 'node_modules/angular2-example-library/lib'
    }
  });
  System.import('app/main').then(null, console.error.bind(console));
</script>
```

```ts:app.component.ts
// インポートする
import {ExampleComponent} from 'angular2-example-library/components';

@Component({
  selector: 'my-app',
  template: '<example></example>',
  directives: [ExampleComponent], // 利用可能
})
export class AppComponent {}
```


ブラウザを開いたときに、`<example></example>` が変換されていれば成功！
## おわりに
以上で **Angular2** のライブラリを作成してみました。
*npm パッケージ作成のベストプラクティス* 的なものを調べている最中であり、ディレクトリ構造や設定ファイルなどは改善の余地がありそうでした。

ちなみに自分が作っていて出来上がったものは [**angular2-fontawesome**](https://github.com/travelist/angular2-fontawesome) (作成途中) で、**Angular1.x** の **directive** には存在する `replace: true` でタグを書き換える挙動がないっぽい( [議論中?](https://github.com/angular/angular/issues/6710) )ので実装を待つため一時終了となっています。

現在 **Angular2** は **ベータ版** なので、挙動が今後変わる可能性がある点に注意です。

最後まで読んでいただきありがとうございました。

## 参考
- [**How to create an Angular 2 component library, and how to consume it using SystemJs or Webpack**](http://blog.jhades.org/how-to-create-an-angular-2-library-and-how-to-consume-it-jspm-vs-webpack/)
- [**3分でできるnpmモジュール**](http://qiita.com/fnobi/items/f6b1574fb9f4518ed520)
- [**npm linkが意外と便利だった**](http://www.tejitak.com/blog/?p=1535)
- [**npm-scripts**](https://docs.npmjs.com/misc/scripts)
- [**angular2-fontawesome**](https://github.com/travelist/angular2-fontawesome)