ClojureScript クイックスタート

最新の記事は[こちら](https://qiita.com/snufkon/items/0cc88e2a356127f848c2)

ClojureScript の[本家サイト](https://clojurescript.org/)にある [Quick Start](https://clojurescript.org/guides/quick-start) の記事をざっくりとまとめる。

丁寧でわかりやすいのだが、ちょっと長すぎるので...


**対象読者**

- Clojure[Script] のコードが読める人


## ClojureScript Compiler

Standalone の ClojureScript には Clojure 1.8.0 がバンドルされている。

[ダウンロード](https://github.com/clojure/clojurescript/releases/download/r1.9.473/cljs.jar)

以下、この JAR を利用しサンプルプログラムを動かしながら ClojureScript のビルド方法を学んでいく。


まず、ディレクトリを作成しダウンロードした JAR を配置。

```sh
$ mkdir hello_world
$ cp cljs.jar hello_world/
```

次に、ビルド対象のファイルを作成。

```sh
$ cd hello_world
$ mkdir -p src/hello_world
$ touch src/hello_world/core.cljs
```


`Hello World` を出力するためのコードを `core.cljs` に記述する。

```clj:core.cljs
(ns hello-world.core)

(enable-console-print!)

(println "Hello world!")
```

上記のコードをコンパイルするための build スクリプトを用意する。

```sh
$ cd hello_world
$ touch build.clj <-- 拡張子に注意
```

`build.clj`という名前にしているが任意の名前で良い。

中身には以下のコードを記述する。

```clj:build.clj
(require 'cljs.build.api)

(cljs.build.api/build "src" {:output-to "out/main.js"})
```

`cljs.build.api`に含まれる `build` 関数でビルドを行う。

第一引数には、コンパイル対象のディレクトリを指定。
第二引数には、コンパイル時のオプションを指定。


実際に、ビルドを行う。

```sh
$ 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` を作成する。

```sh
$ cd hello_world
$ touch index.html
```

```html: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 によって依存関係の順に正しくロードを行っていることが確認できる。

```js:main.js
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:index.html
<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:index.html
<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 を設定する。

```clj:build.clj
(require 'cljs.build.api)

(cljs.build.api/build "src"
  {:main 'hello-world.core
   :output-to "out/main.js"})
```

こうすることで `index.html` は以下となる。

```html:index.html
<html>
  <body>
    <script type="text/javascript" src="out/main.js"></script>
  </body>
</html>
```

リビルドし、動作確認。

```sh
$ 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 のコンパイラはインクリメンタルなコンパイルをサポートする。

ディレクトリを監視し、リコンパイルするスクリプトを作成する。

```sh
$ cd hello_world
$ touch watch.clj
```

```clj:watch.clj
(require 'cljs.build.api)

(cljs.build.api/watch "src"
  {:main 'hello-world.core
   :output-to "out/main.js"})
```

以下で、オートビルドを実行する。

```sh
$ java -cp cljs.jar:src clojure.main watch.clj
```

`core.cljs` を編集＆保存する毎にコンパイルが実行される。


**Note**

この Quick Start では不要だが、[GCL](https://developers.google.com/closure/library/) の基本を知っておくことをおすすめする。そうすれば、簡単なエラーの多くを避けることができる。

## Browser REPL

ClojureScript は Node.js, Rhino, Nashorn と ブラウザ上での REPL をサポートする。

ここでは、Browser REPL を試す。

まずは、REPL 用のスクリプトを作成する。

```sh
$ cd hello_world
$ touch repl.clj
```

```clj: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 を利用する場合も記述の流れは同じ。

1. REPL を起動する前にプロジェクトをビルドする
2. `cljs.repl/repl` に REPL 評価の環境(Node.js, Rhino, Nashorn, browser) を設定する

`cljs.repl/repl` のオプションに `:watch` を指定しておくことで、オートビルドを合わせて起動させておくことができる。ビルドのログは `out/watch.log` に出力される。

また、`output-dir` を指定しておくことで、REPL がビルドによりコンパイルされたファイルを再利用することができる。


browser REPL を利用するためには、source コード側に browser REPL をロードするための記述を追加する必要がある。

```clj:core.cljs
(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 を起動する。

```sh
$ rlwrap java -cp cljs.jar:src clojure.main repl.clj
```

ビルド完了後、REPL は接続待ち状態になりターミナルには

```
Waiting for browser to connect ...
```

と出力される。


ブラウザで `http://localhost:9000` にアクセスする。

ターミナルの待ち状態が解決し、REPL のプロンプトが表示される。

`(+ 1 2)`と打ち込むと、評価された結果が返ってくる。

`core.cljs` に以下の関数を追加し

```clj:core.cljs
(defn foo [a b]
  (+ a b))
```

REPL 上で namespace をリロードするために以下を評価する。

```
(require '[hello-world.core :as hello] :reload)
```

`(hello/foo 2 3)` を評価すると結果として`5`が得られる。

`foo` 関数を以下のように変更し、リロード後に同様の評価を行うと結果として`6`が得られる。

```clj:core.cljs
(defn foo [a b]
  (* a b))
```

## Production Builds

標準のビルドでは `out` ディレクトリに多くの JavaScript ファイルが出力されるが、GCL により minification や dead code 削除などの最適化を行うことができる。

リリース用のスクリプトを作成することにより確認する。

```sh
$ cd hello_world
$ touch release.clj
```

```clj: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](https://github.com/nodejs/node/blob/master/README.md) の沿ってインストールしておく。

`core.cljs` を以下のように修正。

```clj: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)
```

ビルド用のスクリプトを用意する。

```sh
$ cd hello_world
$ touch node.clj
```

```clj:node.clj
(require 'cljs.build.api)

(cljs.build.api/build "src"
  {:main 'hello-world.core
   :output-to "main.js"
   :target :nodejs})
```

基本のビルド用スクリプトとの違いは２点。

1. `:target` として `:nodejs` を指定。
2. `main.js` を `out` ディレクトリ外に出力。


Node.js が持つ source mapping を利用する場合は、以下をインストール。

```sh
$ npm install source-map-support
```

作成した node.clj で Node のプロジェクトをビルドする。

```sh
$ java -cp cljs.jar:src clojure.main node.clj
```

ビルドで作成された `main.js` を `node` で実行。

```sh
$ 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 を起動するより簡単。

以下のビルドスクリプトを作成する。

```sh
$ cd hello_world
$ touch node_repl.clj
```

```clj: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 を起動できる。

```sh
$ rlwrap java -cp cljs.jar:src clojure.main node_repl.clj
```

browser REPL の時と同様、関数を追加＆再リロードして利用することができる。


## Dependencies

ClojureScript は ClojureScript と JavaScript の dependencies を扱う様々な方法がある。
詳しくは [Dependencies](https://clojurescript.org/reference/dependencies)を参照。

その中で、一番簡単な方法は適切にパッケージされた JAR を利用する方法。
[CLJSJS](http://cljsjs.github.io/) を使うことでパッケージされた多くの JavaScript ライブラリを簡単に扱うことができる。

以下、CLJSJS が提供する [React](https://facebook.github.io/react/) パッケージを ClojureScript から利用する方法を説明する。

まず、CLJSJS が提供する JAR を [Clojars](https://clojars.org/) から取得する。

```sh
$ curl -O https://clojars.org/repo/cljsjs/react/0.12.2-8/react-0.12.2-8.jar
```

`core.cljs` を React を require するコードに変更。

```clj:core.cljs
(ns hello-world.core
  (:require cljsjs.react))

(enable-console-print!)

(println "Hello React!")
```

このコードをビルドするには、classpath にダウンロードした React の JAR を含めてビルドする必要がある。

```sh
$ java -cp cljs.jar:src:react-0.12.2-8.jar clojure.main build.clj
```

ビルド成功後、`index.html`をリロードすれば React が読み込まれ React によるログ出力が表示されることを確認できる。
(Chrome の場合、Console の出力を Verbose にすれば、React が出力したログを確認することができる)

利用するライブラリが複数ある場合、`lib`などのディレクトリにまとめて配置しビルドすると良い。

```sh
$ java -cp 'cljs.jar:lib/*:src' clojure.main build.clj
```

dependency グラフがより複雑になる場合、Maven や Leiningen を使うとよい。

詳細は、[Dependencies](https://clojurescript.org/reference/dependencies) を参照。