このエントリは(Qiitaじゃない方の) Angular JS Advent Calendar 14日目の投稿です.
やりたかったこと
AngularJSでDirectiveやService等の部品をガリガリと作成したら, 「みんな、俺のイカしたAPI見てくれよ!」とかやりたくなる筈。やりたくなれ。
丁度、AngularJS本家APIリファレンスのイメージ.
しかも、.htmlと.js, .cssさえあれば、ブラウザで作成した部品達の動作確認自体まで行えるのがフロント系のフレームワークのよいところ.
下記のようなAngularJSのソース + ngdoc書式のコメントを書いたら, こんな感じのAPIページ を表示しましょう、というお話.
'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, gulp-ngdocs 等がある.
今回は, ngdocコメントの処理にAngularJS本家が利用しているという理由で, Dgeni を用いてみることにした.
Dgeniは、拡張性が高く、ServiceやFactory, Filterと言った概念から構成されているため、AngularJS使いにとって取っつきやすそう、と思ったのがきっかけ.
なお, Dgeniについては、@kinzai 氏がとても分かり易いエントリを記載されているので、是非一読されたし.
さて、Dgeniをngdoc生成器として利用するためには、Dgeni本体とdgeni-packagesというnode.jsのパッケージを取り込む必要がある.
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が生成されると思い込んでいたのだが、これは結構な間違いであった.
- dgeni-packagesに含まれているngdoc生成器は、API用HTMLの一部しか吐いてくれない
- 部品の動作サンプルを作るために、bowerの依存関係やら自作モジュールのビルドを考慮する必要があり、ドキュメント生成以外にもgulp(またはgrunt)のタスクを色々用意しないといけない
Dgeniが出力するのは、下記のような断片的な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><sample-elem> ... </sample-elem></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レポジトリ を見てくださいな。
面倒なのでYeomanのgenerator化.
とまぁ、Dgeniを使ってみたものの、AngularっぽいAPIページを作るのには随分骨が折れる。正直、ゼロから自分のプロジェクトに組み込むのなんて、何回もやりたい作業ではない. このままでは、grunt-ngdocsやgulp-ngdocsを素直に使っときゃよかった、という状態だ.
そうだ! Yeomanのgeneratorにしちゃえばいいじゃん! ということで、Yeomanの勉強がてら、generator作ってみました。
使い方はとっても簡単(まぁgeneratorは全部使うの簡単だけどさ...). コマンドプロンプトで下記の通りに入力していけばよい。
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に情報が載っているものの、outdatedって書いてあるため、鵜呑みにしてよいものやら...
結局、AngularJS本体のコメントを漁っていくのが一番確実っぽい.
(どなたか、ちゃんとしたngdocリファレンスの在り処を知っていたら教えてください)
網羅性は一切保証できないが、ngdocの記載方法について、備忘を残しておく.
@ngdoc
@ngdoc
は、一つのコメントブロックに付与する.
続けて、「何を表すか」を記載する(module/directive/filter/service/provider/type/event/function/method/property/overview)
例えば、myFilterという名前で、filterを作成したら、下記のようになる:
/**
*
* @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
の場合等、「誰の持ち物か」の情報が必要である場合は、下記のように #
で結合する.
/**
* :
* @name 持ち主#関数名
* :
**/
@description
説明文. Markdownが使える.
@param
, @return
関数のパラメータ、戻り値の説明に用いる.
-
{...}
に型名を記載(独自定義でも可. - 複数の型が利用可能な場合、
|
で区切る. - 省略可能である場合,
{...=}
とする.
/**
*
* @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の文中で、他のドキュメントへのリンクを作成してくれる.
/**
*
* @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させる際のパッケージ名.
/**
*
* @ngdoc module
* @name testModule01
* @module testModule01
* @packageName test-module01
* @description
* This is a sample module.
*
**/
@ngdoc type
上述で少し触れたが、Method, propertyを所有したオブジェクトのAPIを記載したい時に便利.
あるServiceを実行すると、オブジェクトが返ってくる時とか.
-
@property
で、このオブジェクトがもつPropertyの説明を記載する.
/**
*
* @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の説明となる.
/**
*
* @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 を記載する.
/**
*
* @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
の例は下記:
/**
*
* @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を晒してみる.
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
は、ここを参考に作成している.
リンク先に詳細が記載されているが, github.ioへpushするために、下記を実行する必要がある.
- gh-pagesブランチを
git branch --orphan gh-pages
で作成 - Githubのセキュアトークンを取得
-
gem install travis
でtravisコマンドをインストール - レポジトリルートで下記コマンドを実行し、.travis.ymlの
secure:〜
の部分を生成する.
travis encrypt -r (Github ID)/(レポジトリ名) "GH_TOKEN=(2.で取得しているトークン)" --add
まとめ
- みんなngdocでAngularJSモジュールのドキュメント書こうぜ!
- Dgeni + dgeni-packageだけだと、完品のAPIページは作れないよ!
- generator-ngdoc使ってね!
- こまったら、最終的にはAngularJS本家のコード読むといいよ!