SBTを使わないNode.JS friendlyなScala.js開発環境を作成する

[Scala Matsuri 2016](http://scalamatsuri.org/)おもしろかったですね！
Scala.js推しな会社のブースにいたりしたので、久しぶりにモチベーションが上がって前からやりたかったScala系の依存の解決をnpmで解決できるようにしました。
これでSBTに頼らずにNode.JS friendlyな環境を作れるようになります！

話的には

* [Scala.jsをnodeプロジェクトで扱う](http://qiita.com/kinzal/items/985a9cb5adb47da0f32c)
* [続・Scala.jsをnodeプロジェクトで扱う](http://qiita.com/kinzal/items/82044b9744e7bd5cd9b1)
* [Scala.jsはじめました！](http://www.slideshare.net/kinzal/scalajs-51163091)

の続きな感じです。

# 目的・モチベーション

Scala.jsを[チュートリアル](http://www.scala-js.org/tutorial/basic/)にある通りSBTを使用するとJSのエコシステムには乗れなくなります。

一応、[Live Reloading](https://github.com/lihaoyi/workbench)があったり、[WebJars](http://www.webjars.org/)でJavaScriptの依存解決もできたりする（やったことはないけど）ので最低限開発には困らないです。
が、HTMLやCSSを含めたビルドフローを考えると、SBTだとやっぱり物足りなくてJSのエコシステムに乗せた方が話が早そうです。
（SBT+npmな構成にすれば解決できる問題ではありますが、役割が被るのでどちらかに寄せたいです）


全く関係ないけど、[ScalaCSS](https://japgolly.github.io/scalacss/book/index.html)+[twirl](https://github.com/spray/twirl)+Scala.jsでフルスタックScala開発とか熱そうな気はする。

# Scala.jsコンパイル環境の作成

とりあえず[scalajs-standalone-bin](https://www.npmjs.com/package/scalajs-standalone-bin)を入れて、Scala.jsをコンパイルできる環境を作成します。

```json:package.json
"scripts": {
  "postinstall": "scalajs-standalone-bin-install"
}
```

```sh
$ npm install --save-dev scalajs-standalone-bin
```

なんとなく[node-java](https://www.npmjs.com/package/java)を使えばnpmだけで完結して開発環境作れるかと思いましたが、そんなことは全くなくJDKが必要になるので事前にJDKをインストールしておいてください。

また、scalajs-standalone-binで使用している[bin-wrapper](https://www.npmjs.com/package/bin-wrapper)のバグだと思うのですが、`postinstall`の挙動がおかしくなるときがあります。
もし、上手くインストールできないというときは何度か`npm install`を繰り返してみてください。(npm3になって大丈夫になった気もする)

```scala:src/example/ScalaJSExample.scala
package example

import scala.scalajs.js

object ScalaJSExample extends js.JSApp {
  def main(): Unit = {
    println("HelloWorld!!!")
  }
}
```

```json:package.json
"scripts": {
  ...
  "build": "scalajsc $(find src -name *.scala) -d .tmp  && scalajsld -o dist/example.js .tmp"
}
```

```sh
$ mkdir -p .tmp dist
$ npm run build                  

> scalajs-skeleton@1.0.0 compile /Users/Kinzal/Downloads/scalajs-skeleton
> scalajsc $(find src -name *.scala) -d .tmp  && scalajsld -o dist/example.js .tmp

Fast optimizing dist/example.js
```

これでScalaライブラリへの依存のないScala.js開発環境が作成できました。

# Scalaライブラリへの依存を解決する

依存の解決をするための題材として[scala-js-example-app](https://github.com/sjrd/scala-js-example-app/blob/master/src/main/scala/example/ScalaJSExample.scala)のコードをコンパイルできるようにします。

```scala:src/example/ScalaJSExample.scala
package example

import scala.scalajs.js
import js.annotation.JSExport
import org.scalajs.dom

object ScalaJSExample extends js.JSApp {
  def main(): Unit = {
    val paragraph = dom.document.createElement("p")
    paragraph.innerHTML = "<strong>It works!</strong>"
    dom.document.getElementById("playground").appendChild(paragraph)
  }

  /** Computes the square of an integer.
   *  This demonstrates unit testing.
   */
  def square(x: Int): Int = x*x
}
```

```sh
$ npm run build

> scalajs-skeleton@1.0.0 compile /Users/Kinzal/Downloads/scalajs-skeleton
> scalajsc $(find src -name *.scala) -d .tmp  && scalajsld -o dist/example.js .tmp

src/example/ScalaJSExample.scala:5: error: object scalajs is not a member of package org
import org.scalajs.dom
           ^
src/example/ScalaJSExample.scala:9: error: not found: value dom
    val paragraph = dom.document.createElement("p")
                    ^
src/example/ScalaJSExample.scala:11: error: not found: value dom
    dom.document.getElementById("playground").appendChild(paragraph)
    ^
three errors found

npm ERR! Darwin 14.0.0
npm ERR! argv "/usr/local/bin/node" "/usr/local/bin/npm" "run" "compile"
npm ERR! node v5.1.1
npm ERR! npm  v3.3.12
npm ERR! code ELIFECYCLE
npm ERR! scalajs-skeleton@1.0.0 compile: `scalajsc $(find src -name *.scala) -d .tmp  && scalajsld -o dist/example.js .tmp`
npm ERR! Exit status 1
npm ERR! 
npm ERR! Failed at the scalajs-skeleton@1.0.0 compile script 'scalajsc $(find src -name *.scala) -d .tmp  && scalajsld -o dist/example.js .tmp'.
npm ERR! Make sure you have the latest version of node.js and npm installed.
npm ERR! If you do, this is most likely a problem with the scalajs-skeleton package,
npm ERR! not with npm itself.
npm ERR! Tell the author that this fails on your system:
npm ERR!     scalajsc $(find src -name *.scala) -d .tmp  && scalajsld -o dist/example.js .tmp
npm ERR! You can get their info via:
npm ERR!     npm owner ls scalajs-skeleton
npm ERR! There is likely additional logging output above.

npm ERR! Please include the following file with any support request:
npm ERR!     /Users/Kinzal/Downloads/scalajs-skeleton/npm-debug.log
```

特に依存解決をしていないので、想定通りエラーが発生しましたね。
ここから依存するライブラリへのjarにパスを通してコンパイルできるようにします。

基本的には依存するjarを[maven](http://mvnrepository.com/)からダウンロードして、`scalajsc`の`classpath`に通すだけになります。
mavenからのダウンロードには

* [node-java-maven](https://www.npmjs.com/package/node-java-maven)

を使用します。
なんでこんなものが存在しているのかはわかりませんが、あるならありがたく使わせてもらいます。

```json:package.json
  "scripts": {
    ...
    "postinstall": "scalajs-standalone-bin-install && node-java-maven"
  },
  "java": {
    "dependencies": [
      {
        "groupId": "org.scala-js",
        "artifactId": "scalajs-dom_sjs0.6_2.11",
        "version": "0.8.2"
      }
    ]
  }
```

```sh
$ npm install --save-dev node-java-maven
```

これで`~/.m2`以下に依存するjarがダウンロードされます。
ただ、ここでダウンロードしたjarの中に`scala-library`と`scalajs-library`も含まれますが、少しバージョンが古いのでその2つは除外して`classpath`に指定します。

```json:package.json
  "scripts": {
    ...
    "build": "scalajsc -classpath $(find ~/.m2 -path '*scala-library*' -prune -o -path '*scalajs-library*' -prune -o -name '*.jar' -print | paste -s -d ':' -):$(find ./node_modules/scalajs-standalone-bin -name '*.jar' | paste -s -d ':' -) $(find src -name '*.scala') -d .tmp  && scalajsld -o dist/example.js .tmp"
  }
```

```sh
$ npm run build         

> scalajs-skeleton@1.0.0 build /Users/Kinzal/Downloads/scalajs-skeleton
> scalajsc -classpath $(find ~/.m2 -path '*scala-library*' -prune -o -path '*scalajs-library*' -prune -o -name '*.jar' -print | paste -s -d ':' -):$(find ./node_modules/scalajs-standalone-bin -name '*.jar' | paste -s -d ':' -) $(find src -name '*.scala') -d .tmp  && scalajsld -o dist/example.js .tmp

Fast optimizing dist/example.js
```

これでScalaライブラリの依存を解決して、Scala.jsを開発できる環境ができました。
あとは出力したファイルをwatchしたり、browserifyにかけたりで簡単にJSのエコシステムに載せれそうですね！！

ちなみにビルド速度は結構早いです。

```
$ time npm run build

> scalajs-skeleton@1.0.0 build /Users/Kinzal/Downloads/scalajs-skeleton
> scalajsc -classpath $(find ~/.m2 -path '*scala-library*' -prune -o -path '*scalajs-library*' -prune -o -name '*.jar' -print | paste -s -d ':' -):$(find ./node_modules/scalajs-standalone-bin -name '*.jar' | paste -s -d ':' -) $(find src -name '*.scala') -d .tmp  && scalajsld -o dist/example.js .tmp

Fast optimizing dist/example.js
npm run build  16.17s user 1.20s system 200% cpu 8.656 total
```

# 課題

1. 複数プロジェクトで使用すると`~/.m2`以下に依存するライブラリが混在する
2. `scalajsc`で中間ファイルが出力される
    - 標準出力に中間ファイルを出力して`scalajsld`にパイプで渡したいけど渡し方がわからない
3. `scalajsc`、`scalajsld`で出力するディレクトリが存在しないとエラーになる
4. `find`を多用してて読みづらい

このあたりは[tpr](https://github.com/k-kinzal/tpr)で解決したい内容ではあったのですが、正直コマンドをラップして使いやすくするためだけのツールにモチベーションがわかなすぎて開発停滞してます。

# おわりに

一応断っておきますが、今回は実験して動くの確認したレベルなので、これでちゃんとアプリケーションを開発できるかは知らないです。
たぶんいくつか地雷はあると思うので、誰か思いっきり踏み抜いてください。