Undefined Title

Undefined Title

モダンなJavaScriptライブラリ開発、テスト環境への移行 -- Karma + webapck + jasmine

リリース当初には見つけられなかったツールを使って、自分のJavaScriptライブラリの開発環境を改善できたので久しぶりにエントリー書いた。 最近なんかGithubでstarをつけてくれる人が増えてきて100を超えたのと、 俺のJSライブラリの世界観(2014末版)に触発され、 環境改善のモチベーションがあがったので頑張ってみた。

TL;DR

  • 自作のライブラリJUMLYをCommonJS styleで書きなおした
  • ビルドはwebpack、テストはKarmaで実行できるようにした
  • コマンドラインツールとしても使える。es5-shimありがとう
  • Karma on Travis CIでレンダリング後のサイズや位置のテストができた
  • モダンになった気がする

要求や想定

とりあえず自分がしたいことや現状をまとめる。

  • ライブラリ自体はDSLを書くとシーケンス図を描くというもの
  • 開発にはCoffeeScript, stylusを使用
  • テストF/WはJasmine。描画後の座標や位置関係のテストがメイン。「このdivの幅は〜以上であること」とかそんな感じ。
  • 実際のファイルの配布は、JSとCSSの2ファイルを<script>、<link>で使用してもらう想定
  • 自サイト(express3で構築したWebアプリ)で使用
  • CLIツールをサポート。PNG, JPEG, HTMLで保存が可能
  • Travis CIでテストを実行

つまり、配布用のJS, CSSファイルの生成できて、自サイトのWebアプリで利用でき、 CLIからも参照できるようにしたい、と。できるだけスマートに。

移行前

  • jumly.min.js, jumly.min.cssの2ファイルをGruntで生成
    • coffee pluginでcompile (simpleなconcat), uglify pluginでminify
    • stylus pluginでcompile (simpleなconcat), cssmin pluginでminify
  • Webアプリからは*.coffee, *.stylをconnect-assets (sprocketsのnode版)やnibを使ってロード
  • テストの実行は自分でstaticなindex.htmlをブラウザを開く
  • CLIでは配布用のファイルをPhantomJSからロードして利用
  • Travis CIではjasmine-nodeで実行

これで大体自分のやりたいことはできていたが、まだ足りない点や不満な点もあった。

  • node.jsとブラウザで動作を変えるためのオレオレrequireを定義
    • 実装側の各ファイルではnode.js、ブラウザの場合分けのコードを入れていた
    • specの各ファイルでオレオレrequireを使うようにコードを入れていた
  • Travis CIではjasmine-nodeを使い描画後のテストはできないので、描画関連のテストを外したtest suiteを定義して回避していた

この他にもjQuery pluginのnamespaceを汚染してたり、Gruntfileでのprocess起動がイマイチスマートじゃなかったりとか あるけどそれは局所的な実装の問題で、一番問題だったのは上記の2つ。 新規開発や改善のモチベーションを削ぐものだったのでずっと改善したいと思っていた。

移行後

  • 各ファイルはCommonJSスタイル(require, exports)で標準化
  • ビルドにwebpackを使用。CSSも含められるため配布用ファイルが1ファイル(jumly.min.js)になった
  • Test runnerにKarmaを使用。Chrome launcherを使用したautoWatchができるようになった。変更したらテストが走るのやっぱ便利
  • TravisCI上でChromiumを使用したテストが実行できる。http://blog.h13i32maru.jp/entry/2014/12/29/163209を参考させてもらった
  • CLIツールをnpmへuploadできるようになった
  • ビルドはwebpackにまかせたのでGruntfileがシンプルになった。代わりにビルドにはMakefile使うようにした

やっぱりwebpackとKarmaの導入が大きい。

各種設定について

webpack, Karma, Travis CI等の各種設定について書く。

webpack

webpack用の設定はこちら。webpack.config.js

CoffeeScriptとjQueryに依存していて、ビルドに含めたくないのでexternalsセクションに指定している点と、 css, coffee, stylusのloaderを指定している点がカスタマイズしている点。

externals: {
    // require("jquery") & require("coffee-script") are external and available on the global
    "jquery": "jQuery",
    "coffee-script": "CoffeeScript"
},
module: {
    loaders: [
        { test: /\.css$/, loader: "style!css" },
        { test: /\.styl$/, loader: "style-loader!css-loader!stylus-loader" },
        { test: /\.coffee$/, loader: "coffee-loader" },
    ]
},

この設定でwebpackコマンドを実行するとdist/bundle.lib.jsdist/bundle.spec.jsを生成する。 で、Gruntでminifyするとpublic/jumly.min.jsを生成して、これが最終成果物。 ファイル1つでJavaScriptもCSSも含んでいる優れモノだ。このファイルをリポジトリにコミットしてしまって、 Webアプリからはこれを参照するようにしている。

dist/bundle.spec.jsはKarmaを使わずにブラウザでテストを実行したいとき用に生成している。 Karmaを使うと実際の描画がブラウザに一瞬しか表示されないため、今までどおり自分でブラウザを開いてテストを実行する手段を残してある。

Karma

Karma用の設定はこちら。karma.conf.js

karma-webpackプラグインを使ってwebpackでビルドするようにしている。 その際、そのままwebpack.config.jsを使用するとsepcファイルが重複してしまうため、下記のようにspec部分のビルドを削除するようにしている。

webpack: function() {
    var conf = require("./webpack.config.js")
    delete conf.entry.spec;
    return conf;
}(),

JavaScriptで設定ファイルが書けて、かつ、関数を評価してくれる場合、こういうとき助かる。

karma.conf.jsでもうひとつカスタマイズしている点としては以下。 Travis CI上ではautoWathなし、一回走ったら終わり、という設定。

var travis_enabled = process.env["KARMA_OPTIONS"] ? true : false;
...
autoWatch: !travis_enabled,
...
singleRun: travis_enabled,

.travis.ymlで下記のように環境変数を設定しておいてTravis CIでの動作を切り替えている。

- export KARMA_OPTIONS="--browsers ChromeTravis"

ただ、このエントリー書いてる最中にkarma-webpack、1.4.0にアップデートされたみたいなんだけど、 その1.4.0がどうもおかしく、karmaの実行時に以下のようなエラーが出る。

You need to include some adapter that implements __karma__.start method!

とりあえず動いている1.3.1を使うようにして様子見。時間がとれたらバグレポートしたいところ。

Travis CI

Travis CI用の設定はこちら。.travis.yml
実際のビルドはこちら。https://travis-ci.org/tmtk75/jumly/builds/45836086

ChromiumをTravis CIで使用する設定は、モダンぽいJavaScriptテスト環境の構築メモを参考にさせてもらった。 非常に助かりました。まさかTravis CIでChromium使ってテストできるとは思ってなかった。

before_install:
  - export CHROME_BIN=chromium-browser
  - export DISPLAY=:99.0
  - sh -e /etc/init.d/xvfb start

  - export KARMA_OPTIONS="--browsers ChromeTravis"

実はこのエントリー書いてる最中に、上記記事がTLに流れてきて「あー、もう今書いてる俺の記事要らないんじゃないかなー」と思った。 まあ、webpackとか被ってない部分もあったので書き進めて今に至る。

あとは前述したようにjQueryとCoffeeScriptとCSSファイルを外部ファイルとして参照するので、npm, make叩いて自前でインストールしている。

install:
  - npm install
  - make dist/jumly.css vendor/coffee-script.js

CLI(PhantomJS)

改善前はJS, CSSの2ファイルを読み込んでいた。

改善前
<link rel="stylesheet" href="#{rootdir}/views/static/release/jumly.min.css" />
<script src='#{rootdir}/views/static/release/jumly.min.js'></script>

改善後
<script src='#{rootdir}/public/js/es5-shim.min.js'></script>
<script src='#{rootdir}/public/jumly.min.js'></script>

改善後はwebpackのビルドのおかげでJSひとつだけ読めばいいようになったのだが、 副作用として今まで使っていなかったFunction#bindメソッドを使用するようになってしまった。 PhantomJS(現時点で1.9.13)はbindをサポートしていないようでエラーになる。困った。 が、幸いPolyfillのひとつes5-shimを読むようにしたらうまく動いてくれた。

ハマった点

files: [
    'node_modules/jquery/dist/jquery.js',
    'vendor/coffee-script.js',
    'dist/jumly.css',
    'spec/ClassDiagramSpec.coffee',
    ...

webpackはCSSもひとまとめにしてくれるが、なぜかKarma上でwebpackを使用したビルドではCSSが適用されないみたい。 そのために位置関係やサイズに関するテストが失敗してしまっていた。CSSを別で生成し(今までどおりGruntでconcat)、 それをjQueryやCoffeeScriptと同様にKarmaの設定で外部ファイルとして読ませたらCSSが期待通りに適用され、無事テストが通った。

まとめ&これから

  • JavaScriptはCommonJSスタイルで書こう
  • ビルドはwebpack。ひとつのファイルにJavaScriptとCSSを含められる
  • Test runnerとしてKarmaを使用。Chrome上でdivのサイズのテストとかできてる。Travis CIでも動いてる
  • PhantomJS使って画像生成できる。es5-shimでPhantomJSに足りない機能が補えた
  • Grunt, gulpとかあるけど、makeでできることはmakeがいいなあ
  • Karma + webpackの構成だとCSSが適用されないみたいなので気をつけよう
  • モダンになった気がする

あともっとモダンにするのにはこんなとこかな。

  • bowerに対応させたい
  • Mocha + power-assert試したい

せっかく環境を整えたので、今年は中身の改善もしたいところだ。