JavaScript Advent Calendar 2014
Mocha などでクライアントサイドの JavaScript をテストするときに、 忌まわしき test_runner.html を自動生成できるツールをつくった。
URL: https://www.npmjs.org/package/phantomochajs
gulp.src() などを使ってテストコードを指定すると、 テストフレームワークとかテストコード自身を読み込む HTML ファイルを自動生成し、 テストを実行できる状態のウェブサーバーを立ち上げて PhantomJS 上でテストを実行する。
gulp = require "gulp"
phantomochajs = require "phantomochajs"
gulp.task "test/client", ->
gulp.src ["spec/spec_helper.coffee", "spec/**/*_spec.coffee", "spec/**/*_spec.js"]
.pipe phantomochajs()
ほかには、以下のような感じで依存ライブラリを指定できるようにした。
gulp.task "test", ->
gulp.src ["spec/spec_config.coffee", "spec/spec_helper.coffee", "spec/**/*_spec.coffee"]
.pipe phantomochajs(
dependencies: [
"./bower_components/requirejs/require.js"
"./tmp/js/app/utils.js"
]
test_dependencies: [
"./bower_components/mocha/mocha.js"
"./bower_components/chai/chai.js"
"./bower_components/sinon/pkg/sinon.js"
]
)
話は変わって、RequireJS で glob ができると個人的に都合が良い感じがあり、 RequireJS のプラグイン指定みたいに HTTP リクエストで演算的な操作ができると良さそうだなと思っていた。 例えば、以下のような構成で AMD なモジュールを置いているときに、
(root)
|-my_module_a.js
\-sub_modules/
|-my_module_1.js
\-my_module_2.js
GET /sub_modules/*.js
という感じの HTTP リクエストを受け取ったら、
ファイル名などをベースにモジュールの名前を決めて、
下記のような JavaScript コードを生成する。
define(
["sub_modules/my_module_1", "sub_modules/my_module_2"],
function(MyModule1, MyModule2) {
function SubModules() {}
SubModules.prototype.MyModule1 = MyModule1;
SubModules.prototype.MyModule2 = MyModule2;
return SubModules;
}
)
これで以下のような形で名前空間的なものを導入できるはず。
# coffeescript
requirejs ["sub_modules/*"], (SubModules)->
mod_1 = new SubModules::MyModule1()
mod_2 = new SubModules::MyModule2()
ディレクトリがネストしているときも、さらに glob をするように依存関係を指定することで対応できる。
(define(["{dirname}/*", ...])
みたいな感じで)
実際に phantomochajs の隠し機能として追加してみたところ(amd_glob: true
で有効にできる)、
少なくとも普通の単純なケースに関しては動作が確認できて、
これでテスト時については名前空間的なものを利用することができるようになった。
あとはビルドができると良いが、こちらはいまひとつ手が進まなかった。 AMD の最適化系ツールで scalableminds/amd-optimize という、 gulp.src などから流れてくるファイルを依存関係順で並べ替え、適切な ID を割り振った状態で書き出してくれるものがあって、 この出力を gulp-concat などを使って結合すると良い感じに固まる。
これを使って、
gulp = require "gulp"
concat = require "gulp-concat"
amdSource = require "amd-source" # TODO?: create
amdOptimize = require "amd-optimize"
http = require "http"
connect = require "connect"
connectMincer = require "connect-mincer"
connectAmdGlob = require "connect-amd-glob" # TODO?: create
gulp.task "build", ->
app = connect()
app.use connectMincer(...) # ビルド対象
app.use connectAmdGlob(...) # glob なリクエストに対して AMD かつ名前空間的なモジュールを返す
server = http.createServer(app).listen(...)
# entry_point.js から辿れる全ての依存先モジュールを取得し、
# vinyl-fs に落とし込んで適切な ID を割り振りストリームに流しこむ
amdSource ["entry_point"], {baseUrl: "http://url/to/glob-able"}
.pipe amdOptimize("entry_point")
.pipe concat("main.js")
.pipe gulp.dest("dist/")
.on "end", -> server.close()
みたいな形でビルドできると良さそう。