GradleからCoffeeScript、LESS、Bowerを使うプラグインgradle-web-resource-plugin

前のエントリで少し触れてしまったけど、少し前に作り、最近大幅に改造してひとまず仕上げたGradleプラグインgradle-web-resource-pluginの紹介です。
というか、開発の経緯の説明という感じですが・・

このプラグインで何ができるのかというと、GradleのタスクでCoffeeScriptやLESSのソースをコンパイルでき、Bowerで利用できるライブラリを組み込むことができる。
しかもNode.js、npmなどのインストールは不要。

これを作ったきっかけは、このブログでも何度か触れているSpring Bootでのフロントエンドの開発をどうするか?というテーマ。
CoffeeScriptやLESSを使って実際に開発した経験はなかったのだけど、今回久々にWebの開発をやることになり、Spring Bootを採用することになった。当然フロントエンド周りも検討したのだが、RailsやPlayだとCoffeeScriptなどが扱えるのに対して、Springはnpmなどを使ってねという感じ。かといって、JavaScriptやCSSを直接コーディングしたり、JavaScriptライブラリをダウンロードしてきて手動で組み込むなんて古すぎるよね?と自問したのが始まり。Mavenならwro4jなどがあるようだが、Gradleだと難しい。

そして行き着いたのが止むを得ずNode、npmを直接使うことだったのだが、Gradleに慣れている自分としては、Nodeやnpmをインストールするという手順が邪魔だし、トラブルが多すぎる。
せっかくGradle (Gradle Wrapper)を使って開発のスタートを楽にしているのに、実際試してみると、Node、npmについてはWindows/Macでインストールの仕方が違うとか、プロキシ設定が...とか問題が多いことに気づいた。

解決策を探していると、srs/gradle-node-pluginという素晴らしいプラグインを発見。このプラグインを使うと、Gradleのタスクを実行するだけで、環境に合わせたNodeディストリビューションをダウンロードし、npmも実行できる。これをベースにCoffeeScriptやLESSのビルドをGulpを使って組み立ててみた。
それでもすべて解決するわけではなかったが、まあ大半のメンバーはちゃんとセットアップしてくれた。

しかし、次に速度の問題にぶつかった。
Gradleだけで諸々がインストールできるようになったのはいいが、プロジェクト外部に何かをインストールしたりするのは避けたかった。
これを許容すると、環境依存の問題が発生しやすくなる。
前述のgradle-node-pluginには、ダウンロードするか、ローカルインストール済みのものを使うかのオプションがあり、これをダウンロード強制にすればいいのだが、問題はその先。
npmのdependencyやBowerのdependencyなど、JavaScript系モジュールもJavaと同様にcleanタスクで綺麗さっぱり消したい。
そうでないと、ビルドの完全性が保てない。
それらのファイルをbuildディレクトリに置くことで、cleanタスクの実行時に勝手に消えてくれるようにはなるのだが、今度は毎回npmによる長いnode_modulesのビルドやBowerによるネットワークアクセスが発生してしまう。
冒頭で紹介したgradle-web-resource-pluginは、最初はこの状態で作っていた。

もう一つの問題が、Windowsで扱うにはパスが長すぎるという問題。
npmでライブラリをインストールして、Gradleのサブプロジェクト配下のbuildディレクトリに入れて・・・としていると物凄くパスが長くなってしまい、cleanタスクが失敗したりする。

こうしたビルドの速度や面倒臭さの問題を放置していると、どうなるか?
まあ予想はついていたが、皆、cleanを避けるようになった。
GitLabを使ってソースコードレビューの管理などをしているのだが、マージリクエスト(GitHubでいうプルリクエスト)を出す前にcleanしてcheckタスク叩いてね、と言っていてもやってくれなかったりする。
自分もできれば避けたいくらいだから、まあ当然といえば当然。

これはまずい・・・ということであれこれ検討し、いくつか見直した。

  • GulpでLESSなどのビルドを組み立てていたが、直接触るわけではないのであまり使うメリットがない。Gradle側でコントロールすることで、Gulpをインストールしないようにする。
  • タスクの実行時にnpmのdependencyをインストールするのをやめて、プラグインにbundleしてしまう。これによってネットワークアクセスもなくなり、jarからのコピーだけで済ませる。

これはそれなりに効果はあった。

さらに、といろいろ探して、JavaのJavaScriptエンジンであるRhinoに行き着いた。
そもそもNodeやnpmを外からインストールするのをやめたほうがいいだろうという感じ。
しかしNode用のライブラリを扱うことを考えるとRhinoでは簡単には行かなそう。
これ使いたいんだけどなぁ・・と二の足を踏んでいると、apigee/triremeを発見。

Triremeで動かすようにGradleタスクをコンバートしかけたのだが、BowerをTriremeで動かすとどうしても落ちてしまう。
このときは、CoffeeScript、LESS、Bowerいずれもコマンドで実行するイメージで考えていたのだが、これを小さなJavaScriptを組んでAPIとして呼び出すことにした。

特に、クラッシュを引き起こしていたBowerに関しては、依存関係の定義ファイルであるbower.jsonを使うのではなく、APIのパラメータに1つのライブラリだけを指定することで正常に動かすことができるようになった。
これができると割とスイスイと進むようになって、以下のように改善していった。

  • Bowerはローカルにキャッシュを作るが、キャッシュがあってもリモートにアクセスして変更をチェックしに行ってしまう。これが非常に遅い。これは、Bowerが特定のバージョンではなくてバージョンxx以上、のような指定ができる仕様上仕方ないのだろうとは思ったものの、Gradle同様、キャッシュしたのならネットワークアクセスしないようにしたい。なので、まずキャッシュをチェックして、キャッシュがあればofflineオプションを指定してインストールすることで、ネットワークアクセスを発生させないようにした。
  • npmを3系にバージョンアップして、node_modulesをフラットにインストールできるようにした。何が問題だったかというと、それまでは各ライブラリがそれぞれ依存する別のライブラリを持っていて、ディレクトリ内に node_modules フォルダを作ってどんどん階層を深くしてしまうこと、そして重複するライブラリがいくつもインストールされることだった。しかもWindowsだと削除できなくなったりする。これがフラットにインストールされることによってだいぶ改善された。
  • Gulpで並列化されていたCoffeeScriptやLESSのコンパイルが直列になってしまい遅くなったため、GroovyのライブラリGParsを使ってGradleプラグイン側からコンパイルを並列化させた。

こんな大転換をして、だいぶ高速化されたのでバージョン1.0としてリリースしたのがつい先日10/12のこと。

ビルド全体で見るとまだ遅いタスクの部類ではあるものの、まあ許容できる範囲になったかなと思う。

もう少しやれることはありそうなので、開発は引き続き進めていきます。

© 2010 ksoichiro