最新の記事はこちら
ClojureScript の本家サイトにある Quick Start の記事をざっくりとまとめる。
丁寧でわかりやすいのだが、ちょっと長すぎるので...
対象読者
- Clojure[Script] のコードが読める人
ClojureScript Compiler
Standalone の ClojureScript には Clojure 1.8.0 がバンドルされている。
以下、この JAR を利用しサンプルプログラムを動かしながら ClojureScript のビルド方法を学んでいく。
まず、ディレクトリを作成しダウンロードした JAR を配置。
$ mkdir hello_world
$ cp cljs.jar hello_world/
次に、ビルド対象のファイルを作成。
$ cd hello_world
$ mkdir -p src/hello_world
$ touch src/hello_world/core.cljs
Hello World
を出力するためのコードを core.cljs
に記述する。
(ns hello-world.core)
(enable-console-print!)
(println "Hello world!")
上記のコードをコンパイルするための build スクリプトを用意する。
$ cd hello_world
$ touch build.clj <-- 拡張子に注意
build.clj
という名前にしているが任意の名前で良い。
中身には以下のコードを記述する。
(require 'cljs.build.api)
(cljs.build.api/build "src" {:output-to "out/main.js"})
cljs.build.api
に含まれる build
関数でビルドを行う。
第一引数には、コンパイル対象のディレクトリを指定。
第二引数には、コンパイル時のオプションを指定。
実際に、ビルドを行う。
$ cd hello_world
$ java -cp cljs.jar:src clojure.main build.clj
java のクラスパスには cljs.jar と 対象の ClojureScript のコードが含まれるディレクトリを指定する。
clojure.main の引数には実行する Clojure ファイルを指定する。
ビルドが完了すると、 out
ディレクトリにコンパイルされた JavaScript、自分で記述した ClojureScript が出力される。
出力されるファイル群の詳細は後述。まずは、出力された JavaScript をブラウザ上で確認する。
Using ClojureScript on a Web Page
以下を記述した index.html
を作成する。
$ cd hello_world
$ touch index.html
<html>
<body>
<script type="text/javascript" src="out/main.js"></script>
</body>
</html>
この index.html
をブラウザで開き、デベロッパツールでコンソール出力を確認する。
期待した Hello world!
は表示されず
Uncaught ReferenceError: goog is not defined
と表示される。
このエラーを理解するためには、 Google Closure Library についてちょっとした基本を知っておく必要がある。
Google Closure Library
JavaScript の環境の違いを抽象化するため、ClojureScript は Google Closure Library(GCL) を利用する。
この GCL は、JavaScript に欠けている namespace と namespace の依存関係を宣言する方法を提供する。
また、GCL が依存関係のあるライブラリのロード問題(正しい順番でロードする必要がある)を解決してくれる。
ビルドで出力された、out/main.js
を確認すると GCL によって依存関係の順に正しくロードを行っていることが確認できる。
goog.addDependency("base.js", ['goog'], []);
goog.addDependency("../cljs/core.js", ['cljs.core'], ...);
goog.addDependency("../hello_world/core.js", ['hello_world.core'], ...);
上記のように、依存関係の順に正しくロードは行っているが、ここで使われている goog
オブジェクトがどこにも定義されていない。さきほどのエラーはこれが原因。
GCL を正しくブートストラップするために goog/base.js
を自分でロードする必要がある。
out/goog/base.js
があるのでこれを main.js
の実行前にロードしておく
<html>
<body>
<script type="text/javascript" src="out/goog/base.js"></script>
<script type="text/javascript" src="out/main.js"></script>
</body>
</html>
これで index.html
をリロードするとエラーメッセージが表示されなくなる。
しかし、out/main.js
はロードの依存関係の記述のみで実際に require
していないので Hello world!
は出力されない。
以下のように index.html
で作成した namespace を require
することにより期待した通りに Hello world!
が出力される。
<html>
<body>
<script type="text/javascript" src="out/goog/base.js"></script>
<script type="text/javascript" src="out/main.js"></script>
<script type="text/javascript">
goog.require("hello_world.core");
// Note the underscore "_"!
</script>
</body>
</html>
Less Boilerplate
ビルドのオプションで :main
を使うことにより、GCL 利用時の定型文を減らすことができる。
:main
にはエントリーポイントとなるnamespece を設定する。
(require 'cljs.build.api)
(cljs.build.api/build "src"
{:main 'hello-world.core
:output-to "out/main.js"})
こうすることで index.html
は以下となる。
<html>
<body>
<script type="text/javascript" src="out/main.js"></script>
</body>
</html>
リビルドし、動作確認。
$ java -cp cljs.jar:src clojure.main build.clj
今までと同様、 Hello world!
が出力されていることが確認できる。
また、out/main.js
を確認すると定型のスクリプトタグの記述が追加されていることがわかる。
:main
を指定する前に out/main.js
に出力されていた dependency の記述は out/cljs_deps.js
に記載されている。
Auto-building
ClojureScript のコンパイラはインクリメンタルなコンパイルをサポートする。
ディレクトリを監視し、リコンパイルするスクリプトを作成する。
$ cd hello_world
$ touch watch.clj
(require 'cljs.build.api)
(cljs.build.api/watch "src"
{:main 'hello-world.core
:output-to "out/main.js"})
以下で、オートビルドを実行する。
$ java -cp cljs.jar:src clojure.main watch.clj
core.cljs
を編集&保存する毎にコンパイルが実行される。
Note
この Quick Start では不要だが、GCL の基本を知っておくことをおすすめする。そうすれば、簡単なエラーの多くを避けることができる。
Browser REPL
ClojureScript は Node.js, Rhino, Nashorn と ブラウザ上での REPL をサポートする。
ここでは、Browser REPL を試す。
まずは、REPL 用のスクリプトを作成する。
$ cd hello_world
$ touch repl.clj
(require 'cljs.repl)
(require 'cljs.build.api)
(require 'cljs.repl.browser)
(cljs.build.api/build "src"
{:main 'hello-world.core
:output-to "out/main.js"
:verbose true})
(cljs.repl/repl (cljs.repl.browser/repl-env)
:watch "src"
:output-dir "out")
上記は Browser REPL 用のスクリプトだが他の REPL を利用する場合も記述の流れは同じ。
- REPL を起動する前にプロジェクトをビルドする
-
cljs.repl/repl
に REPL 評価の環境(Node.js, Rhino, Nashorn, browser) を設定する
cljs.repl/repl
のオプションに :watch
を指定しておくことで、オートビルドを合わせて起動させておくことができる。ビルドのログは out/watch.log
に出力される。
また、output-dir
を指定しておくことで、REPL がビルドによりコンパイルされたファイルを再利用することができる。
browser REPL を利用するためには、source コード側に browser REPL をロードするための記述を追加する必要がある。
(ns hello-world.core
(:require [clojure.browser.repl :as repl]))
(defonce conn
(repl/connect "http://localhost:9000/repl"))
(enable-console-print!)
(println "Hello world!")
以下で ビルド & REPL を起動する。
$ rlwrap java -cp cljs.jar:src clojure.main repl.clj
ビルド完了後、REPL は接続待ち状態になりターミナルには
Waiting for browser to connect ...
と出力される。
ブラウザで http://localhost:9000
にアクセスする。
ターミナルの待ち状態が解決し、REPL のプロンプトが表示される。
(+ 1 2)
と打ち込むと、評価された結果が返ってくる。
core.cljs
に以下の関数を追加し
(defn foo [a b]
(+ a b))
REPL 上で namespace をリロードするために以下を評価する。
(require '[hello-world.core :as hello] :reload)
(hello/foo 2 3)
を評価すると結果として5
が得られる。
foo
関数を以下のように変更し、リロード後に同様の評価を行うと結果として6
が得られる。
(defn foo [a b]
(* a b))
Production Builds
標準のビルドでは out
ディレクトリに多くの JavaScript ファイルが出力されるが、GCL により minification や dead code 削除などの最適化を行うことができる。
リリース用のスクリプトを作成することにより確認する。
$ cd hello_world
$ touch release.clj
(require 'cljs.build.api)
(cljs.build.api/build "src"
{:output-to "out/main.js"
:optimizations :advanced})
(System/exit 0)
最適化オプションとして :advanced
を指定した場合、:main
の記述は不要。
また、GCL が作成したスレッドプールを終了させるため、(System/exit 0)
を実行する。
core.cljs
から REPL を設定を削除し元に戻しておく。
```clj:core.cljs`
(ns hello-world.core)
(enable-console-print!)
(println "Hello world!")
`release.clj` を実行しリリース用のビルドを作成。
```sh
$ java -cp cljs.jar:src clojure.main release.clj
index.html
をブラウザで開き、Hello world!
と表示されることを確認。
out/main.js
のファイルサイズが 80K 程度であることが確認できる。
Running ClojureScript on Node.js
はじめに Node.js を Node.js wiki の沿ってインストールしておく。
core.cljs
を以下のように修正。
(ns hello-world.core
(:require [cljs.nodejs :as nodejs]))
(nodejs/enable-util-print!)
(defn -main [& args]
(println "Hello world!"))
(set! *main-cli-fn* -main)
ビルド用のスクリプトを用意する。
$ cd hello_world
$ touch node.clj
(require 'cljs.build.api)
(cljs.build.api/build "src"
{:main 'hello-world.core
:output-to "main.js"
:target :nodejs})
基本のビルド用スクリプトとの違いは2点。
-
:target
として:nodejs
を指定。 -
main.js
をout
ディレクトリ外に出力。
Node.js が持つ source mapping を利用する場合は、以下をインストール。
$ npm install source-map-support
作成した node.clj で Node のプロジェクトをビルドする。
$ java -cp cljs.jar:src clojure.main node.clj
ビルドで作成された main.js
を node
で実行。
$ node main.js
Hello world!
と出力される。
Note
最新の JavaScript エンジン (V8, SpiderMonkey and JavaScriptCore) の最適化が優れているため、Node.js では :advanced
で最適化を行う理由はほとんどない。 :simple
or :none
の最適化で十分。
Node.js REPL
Node.js REPL を起動させることは、 browser REPL を起動するより簡単。
以下のビルドスクリプトを作成する。
$ cd hello_world
$ touch node_repl.clj
(require 'cljs.repl)
(require 'cljs.build.api)
(require 'cljs.repl.node)
(cljs.build.api/build "src"
{:main 'hello-world.core
:output-to "out/main.js"
:verbose true})
(cljs.repl/repl (cljs.repl.node/repl-env)
:watch "src"
:output-dir "out")
browser REPL の時に必要であった core.cljs
側への変更は不要。
以下で REPL を起動できる。
$ rlwrap java -cp cljs.jar:src clojure.main node_repl.clj
browser REPL の時と同様、関数を追加&再リロードして利用することができる。
Dependencies
ClojureScript は ClojureScript と JavaScript の dependencies を扱う様々な方法がある。
詳しくは Dependenciesを参照。
その中で、一番簡単な方法は適切にパッケージされた JAR を利用する方法。
CLJSJS を使うことでパッケージされた多くの JavaScript ライブラリを簡単に扱うことができる。
以下、CLJSJS が提供する React パッケージを ClojureScript から利用する方法を説明する。
まず、CLJSJS が提供する JAR を Clojars から取得する。
$ curl -O https://clojars.org/repo/cljsjs/react/0.12.2-8/react-0.12.2-8.jar
core.cljs
を React を require するコードに変更。
(ns hello-world.core
(:require cljsjs.react))
(enable-console-print!)
(println "Hello React!")
このコードをビルドするには、classpath にダウンロードした React の JAR を含めてビルドする必要がある。
$ java -cp cljs.jar:src:react-0.12.2-8.jar clojure.main build.clj
ビルド成功後、index.html
をリロードすれば React が読み込まれ React によるログ出力が表示されることを確認できる。
(Chrome の場合、Console の出力を Verbose にすれば、React が出力したログを確認することができる)
利用するライブラリが複数ある場合、lib
などのディレクトリにまとめて配置しビルドすると良い。
$ java -cp 'cljs.jar:lib/*:src' clojure.main build.clj
dependency グラフがより複雑になる場合、Maven や Leiningen を使うとよい。
詳細は、Dependencies を参照。