clojurescriptのshadow-cljsについて

# shadow-cljsとは
shadow-cljsはclojurescriptのコードをコンパイルするなど、ビルドするためのツールです。clojurescriptのビルドツールはfigwheel-mainなど他にもいくつかありますが、npmやyarnなど標準のjavascriptの環境と親和性がよいことが特徴で、npmのパッケージが直接利用できることはとても便利です。

shadow-cljsはThomas Hellerによって開発されています。

shadow-cljsに必要な環境は以下の通りです。

- node.js (v6.0.0+, most recent version preferred)
- npm (comes bundled with node.js) or yarn
- Java SDK (Version 8+, Hotspot) 

# shadow-cljsのプロジェクト作成
npxをインストールします。npxを用いるとプロジェクト、node_modulesフォルダ内のバイナリをパス指定無しで直接実行出来るようになります。

```bash
npm install npx -g
```
最新のnpmはnpxが付属しているため、インストールの必要はないかも知れません。
このnpxを利用して新規プロジェクトを作成します。

```bash
$ npx create-cljs-project my-app
npx: 1個のパッケージを2.051秒でインストールしました。
(中略)

Done. Actual project initialization will follow soon.
----

```
さて、新しいプロジェクトができたので、その構成をみてみます。

```bash
$ cd my-app
$ ls
node_modules/  package-lock.json  package.json	shadow-cljs.edn  src/

```
shadow-cljs.edn以外はjavascriptではお馴染のプロジェクトの構成はpackage.jsonとshadow-cljs.ednを用いて行います。

package.jsonはnodeの依存ファイルや実行コマンドを記述します。生成された内容は以下の通りです。

```json

{
  "name": "my-app",
  "version": "0.0.1",
  "private": true,
  "devDependencies": {
    "shadow-cljs": "2.8.83"
  },
  "dependencies": {}
}
```
新たにライブラリを追加したい場合、npmもしくはyarnコマンドで追加すると、package.jsonにも依存関係が追加されます。

shadow-cljs.ednはclojurescriptに関する構成を記述します。デフォルトでのソースコード構成はsource-pathsに書かれていますが、変更したい場合はここを修正します。

```cljs
;; shadow-cljs configuration
{:source-paths
 ["src/dev"
  "src/main"
  "src/test"]
 :dependencies
 []
 :builds
 {}}
```
# browser replの立ち上げ
npxを用いてbrowser replを起動することにより、ブラウザのjavascriptにてclojurescriptのreplを利用することができます。

```bash
$ npx shadow-cljs browser-repl
shadow-cljs - config: /export/clojure/advanced-clojure/7.advanced-cljs/my-app/shadow-cljs.edn  cli version: 2.8.83  node: v8.10.0
shadow-cljs - server version: 2.8.83 running at http://localhost:9630
shadow-cljs - nREPL server started on port 36415
[:browser-repl] Configuring build.
[:browser-repl] Compiling ...
[:browser-repl] Build completed. (135 files, 1 compiled, 0 warnings, 3.59s)
cljs.user=> 

```

次のようにブラウザが立ち上がります。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/373940/bb48bb57-3d89-64f1-35fa-ec90d2fbedda.png)

replのプロンプトが出たら、clojurescriptのプログラムを打ち込んで、結果が出ることを確認します。ブラウザにアラート表示を行うこともできます。

```bash
cljs.user=> (+ 1 1)
2
cljs.user=>  (js/alert "Hello world!")
nil
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/373940/67d96ab1-bd85-3895-ca1f-650f29e76f8c.png)

# 簡単なclojuescriptのコード作成と実行

今度はソースコードを作成して実行してみましょう。

src/mainにmy_appというディレクトリを作成して、そこにhelloworld.cljsという名前のファイルを作成して以下の内容にします。

```text
src/main/my_app/
└── helloworld.cljs
```
コードはコンソールにHello Worldを表示するものです。

```cljs
(ns my-app.helloworld)

(defn init []
  (println "Hello World!"))
```

publicという名前のディレクトリを作成して、その下にindex.htmlを作成し、以下の通りとします。

```text
public/
└── index.html
```
HTMLのコードは以下の通りとなります。

```html
<!doctype html>
<html>
  <head>
    <meta charset="utf-8" />
    <title>helloworld example</title>
  </head>
  <body>
    <div id="app"></div>
    <script src="/js/main.js" type="text/javascript"></script>
  </body>
</html>
```

書かれたソースコードを実行するために:dev-httpと:buildsのエントリーを追加します。
起動したときに動く関数を:modules->:mainのエントリーで記述します。:dev-httpエントリーではブラウザーにてアクセスするポートを指定します。

```cljs
;; shadow-cljs configuration
{:source-paths
 ["src/dev"
  "src/main"
  "src/test"]

 :dependencies
 []
 :dev-http {8080 "public"}
 :builds
 {:hello
  {:target :browser
   :modules {:main {:init-fn my-app.helloworld/init}}
   }}
 }

```
npxを用いて実行します。

```bash
$ npx shadow-cljs watch hello
shadow-cljs - config: /export/clojure/advanced-clojure/7.advanced-cljs/my-app/shadow-cljs.edn  cli version: 2.8.83  node: v8.10.0
shadow-cljs - HTTP server available at http://localhost:8080
shadow-cljs - server version: 2.8.83 running at http://localhost:9630
shadow-cljs - nREPL server started on port 35655
shadow-cljs - watching build :hello
[:hello] Configuring build.
[:hello] Compiling ...
[:hello] Build completed. (135 files, 1 compiled, 0 warnings, 3.29s)
```
ブラウザを起動して、localhost:8080にアクセスしてみます。
そしてブラウザーのデベロッパーツールにてコンソールをみてみます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/373940/e14a127c-3396-1597-7190-c70e31fb33bf.png)

そうするとHello World!の文字列が表示されていることが確認できます。

コンソールに出力するメッセージを変更してみましょう。

```cljs
(ns my-app.helloworld)

(defn init []
  (js/alert "test")
  (println "こんにちは!"))
```

Browser replはソースコードの変更を検知して、自動的にコンパイルしてくれます。

```text
[:hello] Compiling ...
[:hello] Build completed. (135 files, 1 compiled, 0 warnings, 0.17s)
```
ブラウザーをリロードするとメッセージが変更されていることが分ります。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/373940/b8c90cfa-d55d-f958-2620-fd900e10945b.png)

# reagentを使う
今度はブラウザーにHello World!の文字列を表示してみましょう。
package.jsonにreact関係のライブラリーを記述します。

```json
{
  "name": "my-app",
  "version": "0.0.1",
  "private": true,
  "devDependencies": {
    "shadow-cljs": "2.8.83"
  },
  "dependencies": {
    "all": "^0.0.0",
    "create-react-class": "15.6.3",
    "react": "16.3.2",
    "react-dom": "16.3.2"
  }
}
```
shadow-cljs.ednにはreagentとdevtoolsの依存関係を記述します。また、新しく作成するreagentのためのコードの記述をします。新しくmy-appにcore.cljsを作成して、init関数を起動します。

```cljs
;; shadow-cljs configuration
{:source-paths
 ["src/dev"
  "src/main"
  "src/test"]

 :dependencies
 [
  [binaryage/devtools "0.9.10"]
  [reagent "0.8.0"]
  ]
 
 :dev-http {8080 "public"}
 :builds
 {:hello
  {:target :browser
   :modules {:main {:init-fn my-app.helloworld/init}}
   }
  :app {:target :browser
        :output-dir "public/js"
        :asset-path "/js"

        :modules
        {:main
         {:entries [my-app.core]}}

        :devtools
        {:after-load  my-app.core/init
         :http-root   "public"
         :http-port   3000}}
  }
 }

```
core.cljsの中身です。ブラウザーにHello Worldを表示するためのreagentのコードです。

```cljs
(ns my-app.core
  (:require [reagent.core :as r])
  )

(def greeting "Hello World!")

(defn app []
  [:div greeting])

(defn stop []
  (js/console.log "Stopping..."))

(defn start []
  (js/console.log "Starting...")
  (r/render [app]
            (.getElementById js/document "app")))

(defn ^:export init []
  (start))

```
npxにて起動します。

```bash
$ npx shadow-cljs watch app
```
今度はブラウザ上にHello Worldが表示されます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/373940/e6efe2d8-7704-db91-a680-210e5d36dc89.png)