Arantium Maestum

プログラミング、囲碁、読書の話題

Clojure Web Development勉強 - ClojureScript(その2)cljsbuildでコンパイル

Clojureビルドツールあれこれ

Clojureビルドツール界隈ではleiningenが圧倒的シェアを誇っておりそこにbootが食らいついている、というような状況になっている。

私はbootは使ったことはないが、leiningenにはいつも非常にお世話になっている。project.cljに依存ライブラリなども含めたビルドオプションを記述して、あとはライブラリのダウンロードからコンパイルから実行までleinコマンドでできてしまうのは本当に便利だ。

前回のClojureScriptのコンパイルではleiningenもbootも出る幕がなく、自分でClojureScriptコンパイラのjarを落としてきてjavaコマンドで実行していた。しかし、他のライブラリへの依存性管理や、より便利なワークフローツール(特にfigwheel!)の利用などのためにも、ビルドツールとしてleinを使っていきたい。

普段はプロジェクトの雛形すらlein new <project-name>あるいはlein new reagent <project-name>といったコマンドでleiningenに用意してもらうのだが、今回はそこは手動でやって最低限のプロジェクトの条件を見ていく。実は似たようなことをQiitaでもやったが、その後いろいろと試していくうちに知見も溜まったので・・・

ClojureScript/cljsbuild用のproject.clj

用意するディレクトリとファイルは以下の通り:

.
├── index.html
├── project.clj
└── src
    └── min_cljsbuild
        └── core.cljs

src/min_cljsbuild/core.cljsとindex.htmlは前回でもおなじみ。内容も前回と全く同じ。

ClojureScriptコンパイラcljs.jarとビルドのために実行するスクリプトであるbuild.cljが消え、そのかわりleiningenがビルドに必要な情報を記述したproject.cljが加わっている。

本当の最低限

いろいろと試した結果、cljsbuildがエラーを吐かずに「動作」するproject.cljは以下の通り:

(defproject min-cljsbuild "0.0.1"
  :dependencies [[org.clojure/clojure "1.8.0"]
                 [org.clojure/clojurescript "1.9.293"]]

  :plugins [[lein-cljsbuild "1.1.4"]]
  
  :cljsbuild {:builds []})

上記のようなproject.cljを置いて、bashlein cljsbuild onceとコマンドを走らせると

Compiling ClojureScript...

とメッセージが表示される。その後ディレクトリを調べると、いろんなファイルが出力されているのがわかる。

.
├── dev-resources
├── index.html
├── project.clj
├── resources
├── src
│   └── min_cljsbuild
│       └── core.cljs
├── target
│   ├── classes
│   │   └── META-INF
│   │       └── maven
│   │           └── min-cljsbuild
│   │               └── min-cljsbuild
│   │                   └── pom.properties
│   ├── cljsbuild-crossover
│   └── stale
│       └── extract-native.dependencies
└── test

しかし肝心のJavaScriptファイルがない。というのもcljsbuildに「どこにコンパイルしてほしいClojureScriptファイルが入っているのか」という情報を渡していなかったため、単にテンプレ的に必要なファイルやディレクトリを作成したのみで終わってしまったわけだ。

なので「コンパイルしてほしいClojureScriptはsrcディレクトリに入っていますよー」という情報を渡してやる。

JavaScriptが出力される最低限

(defproject min-cljsbuild "0.0.2"
  :dependencies [[org.clojure/clojure "1.8.0"]
                 [org.clojure/clojurescript "1.9.293"]]

  :plugins [[lein-cljsbuild "1.1.4"]]
  
  :cljsbuild {:builds [{:source-paths ["src"]}]}) ;;ここ

まずは先ほどcljsbuildが作成したディレクトリを全部削除してしまう

rm -rf dev-resources;rm -rf test;rm -rf resources;rm -rf target

そしてもう一回実行:

lein cljsbuild once

そうするとこんなメッセージが出て:

Compiling ClojureScript...

Compiling "/min-cljsbuild/target/cljsbuild-main.js" from ["src"]...

Successfully compiled "/min-cljsbuild/target/cljsbuild-main.js" in 0.82 seconds.

こんなディレクトリ構成が出力される:

.
├── dev-resources
├── index.html
├── project.clj
├── resources
├── src
│   └── min_cljsbuild
│       └── core.cljs
├── target
│   ├── classes
│   │   └── META-INF
│   │       └── maven
│   │           └── min-cljsbuild
│   │               └── min-cljsbuild
│   │                   └── pom.properties
│   ├── cljsbuild-compiler-0
│   │   ├── cljs
│   │   │   ├── core.cljs
│   │   │   ├── core.js
│   │   │   └── core.js.map
│   │   ├── goog
│   │   │   ├── array
│   │   │   │   └── array.js
│   │   │   ├── asserts
│   │   │   │   └── asserts.js
│   │   │   ├── base.js
│   │   │   ├── debug
│   │   │   │   └── error.js
│   │   │   ├── deps.js
│   │   │   ├── dom
│   │   │   │   └── nodetype.js
│   │   │   ├── math
│   │   │   │   ├── integer.js
│   │   │   │   └── long.js
│   │   │   ├── object
│   │   │   │   └── object.js
│   │   │   ├── reflect
│   │   │   │   └── reflect.js
│   │   │   └── string
│   │   │       ├── string.js
│   │   │       └── stringbuffer.js
│   │   └── min_cljsbuild
│   │       ├── core.cljs
│   │       ├── core.cljs.cache.edn
│   │       ├── core.js
│   │       └── core.js.map
│   ├── cljsbuild-crossover
│   ├── cljsbuild-main.js
│   └── stale
│       └── extract-native.dependencies
└── test

前回見たようなJavaScriptファイルがtargetディレクトリに入っている。とくに重要なのがtarget/cljsbuild-compiler-0/goog/base.jsとtarget/cljsbuild-main.jsの2ファイルで、これが前回のout/goog/base.jsとout/main.jsに相当する。

ただ、target/cljsbuild-compiler-0/googといったディレクトリパスも「コンパイル中一時的な置き場として名前衝突が起きないようつけました」感満載だしcljsbuild-main.jsと最終的にロードするJavaScriptファイルにコンパイラツールの名前が付いているのもちょっとダサい。

ということで、後もう一歩、出力ファイルの名前とパスをproject.cljに記述する。

出力先を指定したproject.clj

(defproject min-cljsbuild "0.0.3"
  :dependencies [[org.clojure/clojure "1.8.0"]
                 [org.clojure/clojurescript "1.9.293"]]

  :plugins [[lein-cljsbuild "1.1.4"]]
  
  :cljsbuild {:builds [{:source-paths ["src"]
                        :compiler {:output-to "out/main.js" ;;ここ
                                   :output-dir "out"}}]})

コンパイラにmain.jsの出力先と名前、そしてgoogを含めた必要とされるJavaScriptライブラリの出力先となるディレクトリを指定している。

後は先ほどと同じく、cljsbuildに作成されたディレクトリを削除してからlein cljsbuild onceを実行する。

.
├── dev-resources
├── index.html
├── out
│   ├── cljs
│   │   ├── core.cljs
│   │   ├── core.js
│   │   └── core.js.map
│   ├── goog
│   │   ├── array
│   │   │   └── array.js
│   │   ├── asserts
│   │   │   └── asserts.js
│   │   ├── base.js
│   │   ├── debug
│   │   │   └── error.js
│   │   ├── deps.js
│   │   ├── dom
│   │   │   └── nodetype.js
│   │   ├── math
│   │   │   ├── integer.js
│   │   │   └── long.js
│   │   ├── object
│   │   │   └── object.js
│   │   ├── reflect
│   │   │   └── reflect.js
│   │   └── string
│   │       ├── string.js
│   │       └── stringbuffer.js
│   ├── main.js
│   └── min_cljsbuild
│       ├── core.cljs
│       ├── core.cljs.cache.edn
│       ├── core.js
│       └── core.js.map
├── project.clj
├── resources
├── src
│   └── min_cljsbuild
│       └── core.cljs
├── target
│   ├── classes
│   │   └── META-INF
│   │       └── maven
│   │           └── min-cljsbuild
│   │               └── min-cljsbuild
│   │                   └── pom.properties
│   ├── cljsbuild-crossover
│   └── stale
│       └── extract-native.dependencies
└── test

となって、手動でcljs.jarを使った時と概ね似た構成になる。違いはdev-resources、resources、testという空ディレクトリと、コンパイル時の一時ファイルの残りであるtargetディレクトリで、これらは挙動に何ら影響しない。この時点では削除してしまっても構わないくらいである。

あとは前回と同じindex.htmlを開けば同じアラートが表示される。

今回のコード:

gist.github.com

次回はcljsbuildのオプションを少し調べていきたい。