Tokyo.clj#22
lispが大好きです。
その割には、全然書けるようになりません。
なので、勇気を出してTokyo.cljの門戸を叩いてみました。
https://atnd.org/events/56473
個人的には、2012年のKyoto.lisp以来2年ぶりのlispイベント参加でしたが、どんどん参加していかないと駄目だなと痛感しました(もちろん自分でコード書きまくった上で参加しないと意味ないですが…)。
当日ペアプロの時間があったのですが、お相手の方にほぼ一方的にClojureScriptについて教えていただいてました(ありがとうございましたm(_ _)m)。
その方は画像認識の機械学習を行うアプリを作っていらしゃってすごーく興味あったのですが、ひとまずペアプロの時間はフロントで採用されているClojureScriptについて教えて頂いて、自分のring/compojure/d3.jsで作ったアプリでClojureScriptを試してみたところで、時間切れでした。
本稿には、その時に学んだTipsをメモ書き程度に記します。
ClojureScript
Clojureのシンタックスで書いてコンパイルしたらjsになるっていう…まああれ、あれですよね、CoffeeScript的なあれですよね。。
正直、あまり需要ないのかなって思ってたのですが(実際Clojureで業務されてる方も使ったこと無いって言われてたし)、やっぱりClojureでWebアプリ作るなら、なかなかメリットあるんじゃないかと認識を改めましたm(_ _)m
server/clientでのシンタックスの統合はもちろんですが、一番のメリットはClojureのデータ構造をそのまま受け渡しすることで、client側での処理にスムーズに移行できること。clientで複雑な処理をするシステムが楽しく書けそう!
ということで、ペアプロでは、いったんjsonに変換してjsに渡していたデータを、そのままClojureのマップでclientで受け取ることを目標にしました(最終的にちょっと変なことになってますが…orz)
ちなみに「om(オーム)」というライブラリ( https://github.com/swannodette/om )を使えば、immutableが基本のClojureでもUIの書き換えが簡単にできるそうで、このomの存在もClojureScriptを採用する大きなメリットになりそうです。
ClojureScript入門
プロジェクト(スケルトン)の作成
$ lein new cljs-kickoff hoge-app
leiningenの"cljs-kickoff"というテンプレートを使用すれば簡単です。
(今回のサンプルでは使っていません)
https://github.com/konrad-garus/cljs-kickoff
プロジェクト構成
注:以下はcljs-kickoffで生成したスケルトン構成とは若干異なります。
hoge-app
├── project.clj #1 依存ライブラリ
├── resources
└── public
└── assets
├── demo
│ ├── cljs_test.html #5 表示用画面
│ └── cljs.js #6 生成されたjs
└── index.html
└── src
└── hoge_app
├── core.clj #2 ルーティングとか
└── hogehoge.clj #3 マップを返すクラス
└── cljs
└── client.cljs #4 clojurescriptファイル
1,依存ライブラリなど
:dependencies [[org.clojure/clojure "1.6.0"]
[ring/ring-core "1.3.0"]
[ring/ring-jetty-adapter "1.3.0"]
[compojure "1.1.8"]
[cheshire "5.3.1"]
[org.clojure/clojurescript "0.0-2156"]] ;<=追加
;; clojurescriptのビルド関連の指定を追加
:hooks [leiningen.cljsbuild]
:cljsbuild {
:builds {
:main {
:source-paths ["src/cljs"]
:compiler {:output-to "resources/public/demo/cljs.js"
:optimizations :simple
:pretty-print true}
:jar true}}}
:source-paths でcljsファイルの置き場所を指定。
:output-to でコンパイルされたjsファイルの出力場所を指定。
2,ルーティング
3,マップを返すクラス
4,ClojureScript
もともとは下記のように、d3.jsonメソッドに、"/demo/d3/sentou/all"からjsonに変換したマップ配列を渡していた。
d3.json("/demo/d3/hogehoge", function(error, data) {
// 以下でdataを処理
}
これをClojureScriptで書くと以下のようになる。
(ns d3-clojurescript-test)
(defn d3-callback [error data]
(def app (.getElementById js/document "app"))
(set! (.-innerHTML app) (pr-str (js->clj data))))
(.json js/d3 "/demo/d3/sentou/all" d3-callback)
ここがちゃんと説明するべきところなんだけど…
ClojureScriptちゃんと勉強してからまた追記します。。
わかる部分だけ書くと、d3-callback関数で、innerHTMLを呼び出して、HTMLの要素("app")の内容をサーバーから渡ってきたdataで書き換えている。
重要なのは、"(pr-str (js->clj data)"という部分で、Clojureのマップ配列の内容をちゃんと画面に表示できるようにしてるところで、これをやらないと”Object”みたいな表示になってしまう。
(追記1:pr-strとread-stringについて
http://tnoda-clojure.tumblr.com/post/28499910150/collection-literals-instead-of-json )
(追記2:"js->clj"は、jsオブジェクトをclojureオブジェクトに変換するメソッド。逆なら"clj->js"。今回はdataにjsonが入ってるので前者。)
で、最後の行なんだけど、"/demo/d3/sentou/all"が返すのは現状jsonなので、それを"js/d3"クラスの".json"メソッドで受け取ってClojureScriptで処理するというトリッキーというか、あほなことになっている。。
学んだことのメモなのでご勘弁ください。
5,表示用画面
<body>
<div id='app'>
</div>
<script src='http://d3js.org/d3.v3.min.js' charset='utf-8'></script>
<script src='cljs.js'></script>
</body>
divの中身にClojureScriptのデータが書き込まれる。
6,生成されたjsファイル
コンパイル後、project.cljの":output-to"で指定した場所に、jsファイルが生成する。
自動コンパイルの設定
lein runでアプリを実行してもClojureScriptはコンパイルされるが、開発時はもちろん自動コンパイルも設定できる。
$ cd path-to-project-root
$ lein cljsbuild auto
以降、ファイルの変更のたびに、バックグラウンドでコンパイル〜ビルドをやってくれる。