AngularJSのコード書いたらDgeniでAPIページ作って公開したれ

このエントリは(Qiitaじゃない方の) [Angular JS Advent Calendar](http://www.adventar.org/calendars/350) 14日目の投稿です.

## やりたかったこと

AngularJSでDirectiveやService等の部品をガリガリと作成したら, 「みんな、俺のイカしたAPI見てくれよ！」とかやりたくなる筈。やりたくなれ。
丁度、[AngularJS本家APIリファレンス](https://docs.angularjs.org/api)のイメージ.

しかも、.htmlと.js, .cssさえあれば、ブラウザで作成した部品達の動作確認自体まで行えるのがフロント系のフレームワークのよいところ. 

下記のようなAngularJSのソース + ngdoc書式のコメントを書いたら, [こんな感じのAPIページ](https://quramy.github.io/example-of-generator-ngdoc/index.html#/api/exampleOfGeneratorNgdoc/directive/sampleElem) を表示しましょう、というお話.

```js
'use strict';

/**
 *
 * @ngdoc directive
 * @module exampleOfGeneratorNgdoc
 * @name sampleElem
 * @restrict E
 * @description 
 * This is a sample directive.
 *
 * @example
    <example module="sampleElemExample" deps="" animate="false">
      <file name="index.html">
        <sample-elem></sample-elem>
      </file>
      <file name="main.js">
        angular.module('sampleElemExample', ['exampleOfGeneratorNgdoc']);
      </file>
    </example>
 *
 **/
angular.module('exampleOfGeneratorNgdoc').directive('sampleElem', function () {
  return {
    restrict: 'E',
    template: '<div class="sample-awesome">Hello, AngularJS directive!</div>'
  };
});
```

## Dgeni

上記のサンプルコードのように, ngdocコメントからHTMLを生成する手段としては[grunt-ngdocs](https://www.npmjs.com/package/grunt-ngdocs), [gulp-ngdocs](https://www.npmjs.com/package/gulp-ngdocs) 等がある.
今回は, ngdocコメントの処理にAngularJS本家が利用しているという理由で, [Dgeni](https://github.com/angular/dgeni) を用いてみることにした.

Dgeniは、拡張性が高く、ServiceやFactory, Filterと言った概念から構成されているため、AngularJS使いにとって取っつきやすそう、と思ったのがきっかけ.

なお, Dgeniについては、@kinzai 氏がとても分かり易いエントリを記載されているので、是非一読されたし.

+ [AngularJSアプリケーションのドキュメントを生成するDgeni簡易リファレンス](http://qiita.com/kinzal/items/13be4f0c1b9e1c730f2d)
+ [Dgeniで簡単に始めるngdocドキュメント生成](http://qiita.com/kinzal/items/4580abe9e116dfecc85c)

さて、Dgeniをngdoc生成器として利用するためには、Dgeni本体とdgeni-packagesというnode.jsのパッケージを取り込む必要がある.

```sh
npm install dgeni dgeni-packages --save-dev
```

Dgeni本体は、processorと呼ばれるタスク実行器を順次実行するランナーのようなものと考えるのがよい. 各processorには下記のようなものがある:

+ `readFileProcessor`: ソースファイルを読み込むprocessor
+ `extractJSDocCommentsProcessor`: アノテーションを解析するprocessor
+ `writeFilesProcessor`: ドキュメントを吐き出すprocessor

Dgeni単体では、ngdocやjsdocのコメントを解釈するprocessorを持っていないため、これらのprocessorが登録されているdgeni-packageを併用することが多い.
(gulp本体と各種gulpプラグインの関係を思い浮かべてもらえばよいと思う)

当初、dgeni-packagesに含まれるngdocパッケージを読み込み、Grunt or Gulpから呼び出すだけでいい感じにHTMLが生成されると思い込んでいたのだが、これは結構な間違いであった.

1. dgeni-packagesに含まれているngdoc生成器は、API用HTMLの一部しか吐いてくれない
1. 部品の動作サンプルを作るために、bowerの依存関係やら自作モジュールのビルドを考慮する必要があり、ドキュメント生成以外にもgulp(またはgrunt)のタスクを色々用意しないといけない

Dgeniが出力するのは、下記のような断片的なHTMLのみである.

```html:partials/api/exampleOfGeneratorNgdoc/directive/sampleElem.html
<header class="api-profile-header">
  <h1 class="api-profile-header-heading">sampleElem</h1>
  <ol class="api-profile-header-structure naked-list step-list">
    <li>
      - directive in module <a href="api/exampleOfGeneratorNgdoc">exampleOfGeneratorNgdoc</a>
    </li>
  </ol>
</header>
<div>
  <h2>Directive Info</h2>
  <ul>
    <li>This directive executes at priority level 0.</li>
  </ul>
  <h2 id="usage">Usage</h2>
  <div class="usage">
    <ul>
      <li>as element:
      <pre><code>&lt;sample-elem&gt;&#10;...&#10;&lt;/sample-elem&gt;</code></pre>
      </li>
  </div>
</div>
```

出力先のpathに'partial'とあることからも推測がつくと思うが、完全なHTMLではなく、飽くまで部分的な断片でしかないのだ.

上記のHTMLをブラウザできちんと表示するためには、別途、AngularJSのアプリを作り、`ng-include` や angular-routeモジュールの`ng-view` と `routeProvider`を組み合わせて読み込んでやる必要がある.

また、本家のAPIレファレンスのようなサイドバーメニューを表示したければ、自分でメニュー情報を作成するよう、Dgeniのprocessorから用意しなくてはならないのだ。

このエントリを書こうと思った当初は、一通りドキュメントアプリを生成出来るようになるまでに作成した:

 + Dgeniの設定
 + 追加で作成したDgeniのprocessor
 + Gulpのタスク定義
 + ドキュメントアプリのAngularJSコード

を、ここで解説していこうかと思ったのだけれど、ファイル数が30を越えており、とてもじゃないけど解説仕切れないので割愛.

興味がある人は [冒頭サンプルのGitHubレポジトリ](https://github.com/Quramy/example-of-generator-ngdoc/tree/master/docs) を見てくださいな。

## 面倒なのでYeomanのgenerator化.

とまぁ、Dgeniを使ってみたものの、AngularっぽいAPIページを作るのには随分骨が折れる。正直、ゼロから自分のプロジェクトに組み込むのなんて、何回もやりたい作業ではない. このままでは、grunt-ngdocsやgulp-ngdocsを素直に使っときゃよかった、という状態だ.

そうだ！ Yeomanのgeneratorにしちゃえばいいじゃん！ ということで、Yeomanの勉強がてら、generator作ってみました。

https://www.npmjs.org/package/generator-ngdoc

使い方はとっても簡単(まぁgeneratorは全部使うの簡単だけどさ...). コマンドプロンプトで下記の通りに入力していけばよい。

```sh
npm yo generator-ngdoc
mkdir my-angular-module
cd my-angular-module
yo ngdoc
(質問は適当に答えてください)
cd docs
gulp docs:serve
```

問題なく動作していれば、ブラウザ上でドキュメントアプリが表示されている筈。

'API'→'sampleElem'と辿っていけば、example付きで冒頭に例示した `sample-elem`ディレクティブのドキュメントが表示されます.

注意は、`yo ngdoc` での雛形生成が終わった後に、カレントディレクトリを`docs`に変更しているところ.

generator-gulp-angularやgenerator-angularで既に作成したAngularJSのモジュールプロジェクトでも、後付けで組み込めるよう、generator-ngdocの生成物は、極力、`docs`ディレクトリ配下に留めておきたかったのだ.

### gulp tasks
以下は、generator-ngdocが生成するgulpタスクのリファレンス.
いずれも、(プロジェクトルート)/docs ディレクトリにカレントディレクトリを移動してから実行されたし.

```
cd docs
```

#### `docs:serve`

```
gulp docs:serve
```

ブラウザ上でドキュメントアプリを動作させる(browserSync).

`src/**/*.js`をwatchしており、ソースコードに修正が加わった場合は、Dgeniを再実行した上でブラウザがライブリロードされるため、その場で編集結果が確認可能.

#### `docs:build`

```
gulp docs:build
```

docs/dist配下にドキュメントアプリをパッケージングする. Webサーバに配布する際に用いることを想定.

#### `docs:serve:dist`

```
gulp docs:serve:dist
```

`docs:build`タスクの結果をWebサーバ(browserSync)で確認するためのタスク.

#### `docs:clean`

```
gulp docs:clean
```

お掃除タスク. `docs/.tmp`と`docs/dist`を削除.

## ngdoc記法の例

さて、ようやっとngdocコメントからAPIページを簡単に吐き出すための仕組みが整った.

...ところまではよいものの、ngdoc記法のリファレンスが以外に見当たらない.
[本家のWiki](https://github.com/angular/angular.js/wiki/Writing-AngularJS-Documentation)に情報が載っているものの、outdatedって書いてあるため、鵜呑みにしてよいものやら...

結局、AngularJS本体のコメントを漁っていくのが一番確実っぽい.
(どなたか、ちゃんとしたngdocリファレンスの在り処を知っていたら教えてください)

網羅性は一切保証できないが、ngdocの記載方法について、備忘を残しておく.

### `@ngdoc`
`@ngdoc`は、一つのコメントブロックに付与する.
続けて、「何を表すか」を記載する(module/directive/filter/service/provider/type/event/function/method/property/overview)

例えば、myFilterという名前で、filterを作成したら、下記のようになる:

```js
/**
 *
 * @ngdoc filter
 * @module testModule01
 * @name myFilter
 * @description
 * A filter sample.
 *
 **/
```

なお、`@ngdoc controller`は存在しないので注意.
一般的に、モジュールを外部公開する際に、Controllerを利用させる、というシーンがあまり無いからだろうか.
「Directiveに紐付くController」(例: `ng-model`と`ngModelController`)のケースがあるが、これは`@ngdoc type` で対応できる.
`@ngdoc type`は、「メソッド, プロパティを持つオブジェクトの仕様」を記載できるため.

### `@module`
このコンポーネントが属するmodlue名を記載する.

### `@name`
DirectiveやFilter, Serviceの場合は, コンポーネントの名前自身を記載すればよい.
`@ngdoc method` の場合等、「誰の持ち物か」の情報が必要である場合は、下記のように `#` で結合する.

```js
/**
 * :
 * @name 持ち主#関数名
 * :
**/ 
```

### `@description`
説明文. Markdownが使える.

### `@param`, `@return`
関数のパラメータ、戻り値の説明に用いる.

+ `{...}`に型名を記載(独自定義でも可.
+ 複数の型が利用可能な場合、`|` で区切る.
+ 省略可能である場合, `{...=}` とする.

```js
/**
 *
 * @ngdoc method
 * @module testModule01
 * @name myService.myType#foo
 * @param {Object|String|Boolean|Date|Function|Number|CustomType} param A parameter.
 * @param {Array.<String|Object>} list A list.
 * @param {Object=} opt Optional parameter.
 * @return {String} Result
 * @description
 * A method sample.
 *
 **/
```

### `{@link }`
ngdocの文中で、他のドキュメントへのリンクを作成してくれる.

```js
/**
 *
 * @ngdoc provider
 * @module testModule01
 * @name myServiceProvider
 * @property {String} prop A property of this provider.
 * @description
 * A provider of {@link myService}
 *
 **/
```

### `@ngdoc module`

モジュール全体の定義に使う.

+ `@packageName` Bowerでinstallさせる際のパッケージ名.

```js
/**
 *
 * @ngdoc module
 * @name testModule01
 * @module testModule01
 * @packageName test-module01
 * @description
 * This is a sample module.
 *
 **/
```

### `@ngdoc type`
上述で少し触れたが、Method, propertyを所有したオブジェクトのAPIを記載したい時に便利.
あるServiceを実行すると、オブジェクトが返ってくる時とか.

+ `@property` で、このオブジェクトがもつPropertyの説明を記載する.

```js
/**
 *
 * @ngdoc type
 * @module testModule01
 * @name myService.myType
 * @property {String} myProp
 * @description
 * A child type.
 *
 **/
```

### `@ngdoc directive`

Directive専用で使えるアノテーションが幾つかある.

+ `@restrict` `E`とか`AC`とか.
+ `@scope` スコープを作成する場合、`true` とする.
+ `@priority` 動作順序のアレ. 多分、あまり使わないけど...
+ `@param` Directiveで`@param`を使った場合, HTMLテンプレートで利用した場合のattributeの説明となる. 

```js
/**
 *
 * @ngdoc directive
 * @module testModule01
 * @name myDirective
 * @restrict AC
 * @scope true
 * @priority 100
 * @param {String} myDirective
 * @param {String=} foofoo
 * @description
 * A directive.
 *
 **/
```

### `@ngdoc event`
Service等が、的等な条件下で `Scope.$emit` や `$rootScope.$broadcast` を発火させる場合に記載するやつ.

+ `@eventType` に 種別(emit or broadcast) on 対象scope を記載する.

```js
/**
 *
 * @ngdoc event
 * @module testModule01
 * @name myService#myEvent
 * @eventType broadcast on rootScope
 * @param {Object} angularEvent Synthetic event object.
 * @param {String=} param Optional parameter.
 * @description
 * A event.
 *
 **/
```

### `@example`
部品の動作サンプルを生成する際に利用する.

ここで記載したアノテーション情報から、APIページから`<iframe>`で取り込まれるHTMLが生成される.

+ `<example>` タグ1つに対して、`<iframe>`で取り込まれるページが一つ生成される.
+ `module`属性には、サンプルアプリとしてのModule名を記載する. `ng-app="..."`の中身となる.
+ `<file>`タグでサンプルで動作させたいファイル(html, JavaScript, CSS)の中身を記載していく.

エントリ冒頭のサンプルと同一だが、`@example` の例は下記：

```js
/**
 *
 * @ngdoc directive
 * @module testModule01
 * @name sampleElem
 * @restrict E
 * @description
 * This is a sample directive.
 *
 * @example
    <example module="sampleElemExample" deps="" animate="false">
      <file name="index.html">
        <sample-elem></sample-elem>
      </file>
      <file name="main.js">
        angular.module('sampleElemExample', ['testModule01']);
      </file>
    </example>
 *
 **/
```

## 作成したドキュメントアプリをTravis CIで公開

ここまで来たら、「ソースコードをgithubへcommit & push→ドキュメントサイトをgithub.ioへdeploy」というCIのフローを作り込んでみよう.

実際に作ってみた[travis.yml](https://github.com/Quramy/example-of-generator-ngdoc/blob/master/.travis.yml)を晒してみる.

```yaml:.travis.yml
env:
  global:
  - GIT_COMMITTER_NAME=y-kurami
  - GIT_COMMITTER_EMAIL=yosuke.kurami@gmail.com
  - GIT_AUTHOR_NAME=Quramy
  - GIT_AUTHOR_EMAIL=yosuke.kurami@gmail.com
  - secure: MeJhx5HscQf8Ra9LMCafHqBNmO3AegY5ZLRAhhin9V6s1Q+clSNylB8E0TRqw5zkRvAOwynRxHtOLZOU6MJNwWNO3LIvYYS1o08K7YPWUQL7WCrl/DsXPCJ2xrEdV69YkG0aVmBrpvcnMBsXe78ggUAsB6ZaSL8eEos5ckYtzmU=
language: node_js
node_js:
- '0.10'
before_script:
- npm install -g bower
- npm install -g gulp
- bower install
- cd docs
- npm install
- bower install
- git clone --quiet -b gh-pages https://github.com/Quramy/example-of-generator-ngdoc.git
  dist
- cd ..
script:
- cd docs
- gulp docs:build
- cd ..
after_script:
- cd docs/dist
- git add -A
- git commit -m "UPDATE DOCS."
- '[ "$TRAVIS_BRANCH" == "master" ] && [ "$GH_TOKEN" ] && git push --quiet https://$GH_TOKEN@github.com/Quramy/example-of-generator-ngdoc.git
  gh-pages'
notifications:
  emails:
  - yosuke.kurami@gmail.com
```

上記の`.travis.yml`は、[ここ](http://tricknotes.hateblo.jp/entry/2013/06/17/020229)を参考に作成している.
リンク先に詳細が記載されているが, github.ioへpushするために、下記を実行する必要がある.

1. gh-pagesブランチを`git branch --orphan gh-pages` で作成
2. Githubのセキュアトークンを取得
3. `gem install travis`でtravisコマンドをインストール
4. レポジトリルートで下記コマンドを実行し、.travis.ymlの`secure:〜`の部分を生成する.

```sh
travis encrypt -r (Github ID)/(レポジトリ名) "GH_TOKEN=(2.で取得しているトークン)" --add
```


## まとめ

+ みんなngdocでAngularJSモジュールのドキュメント書こうぜ！
+ Dgeni + dgeni-packageだけだと、完品のAPIページは作れないよ！
+ [generator-ngdoc](https://www.npmjs.org/package/generator-ngdoc)使ってね!
+ こまったら、最終的にはAngularJS本家のコード読むといいよ!