Scala Matsuri 2016おもしろかったですね!
Scala.js推しな会社のブースにいたりしたので、久しぶりにモチベーションが上がって前からやりたかったScala系の依存の解決をnpmで解決できるようにしました。
これでSBTに頼らずにNode.JS friendlyな環境を作れるようになります!
話的には
の続きな感じです。
目的・モチベーション
Scala.jsをチュートリアルにある通りSBTを使用するとJSのエコシステムには乗れなくなります。
一応、Live Reloadingがあったり、WebJarsでJavaScriptの依存解決もできたりする(やったことはないけど)ので最低限開発には困らないです。
が、HTMLやCSSを含めたビルドフローを考えると、SBTだとやっぱり物足りなくてJSのエコシステムに乗せた方が話が早そうです。
(SBT+npmな構成にすれば解決できる問題ではありますが、役割が被るのでどちらかに寄せたいです)
全く関係ないけど、ScalaCSS+twirl+Scala.jsでフルスタックScala開発とか熱そうな気はする。
Scala.jsコンパイル環境の作成
とりあえずscalajs-standalone-binを入れて、Scala.jsをコンパイルできる環境を作成します。
"scripts": {
"postinstall": "scalajs-standalone-bin-install"
}
$ npm install --save-dev scalajs-standalone-bin
なんとなくnode-javaを使えばnpmだけで完結して開発環境作れるかと思いましたが、そんなことは全くなくJDKが必要になるので事前にJDKをインストールしておいてください。
また、scalajs-standalone-binで使用しているbin-wrapperのバグだと思うのですが、postinstall
の挙動がおかしくなるときがあります。
もし、上手くインストールできないというときは何度かnpm install
を繰り返してみてください。(npm3になって大丈夫になった気もする)
package example
import scala.scalajs.js
object ScalaJSExample extends js.JSApp {
def main(): Unit = {
println("HelloWorld!!!")
}
}
"scripts": {
...
"build": "scalajsc $(find src -name *.scala) -d .tmp && scalajsld -o dist/example.js .tmp"
}
$ 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のコードをコンパイルできるようにします。
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
}
$ 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からダウンロードして、scalajsc
のclasspath
に通すだけになります。
mavenからのダウンロードには
を使用します。
なんでこんなものが存在しているのかはわかりませんが、あるならありがたく使わせてもらいます。
"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"
}
]
}
$ npm install --save-dev node-java-maven
これで~/.m2
以下に依存するjarがダウンロードされます。
ただ、ここでダウンロードしたjarの中にscala-library
とscalajs-library
も含まれますが、少しバージョンが古いのでその2つは除外してclasspath
に指定します。
"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"
}
$ 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
課題
- 複数プロジェクトで使用すると
~/.m2
以下に依存するライブラリが混在する -
scalajsc
で中間ファイルが出力される- 標準出力に中間ファイルを出力して
scalajsld
にパイプで渡したいけど渡し方がわからない
- 標準出力に中間ファイルを出力して
-
scalajsc
、scalajsld
で出力するディレクトリが存在しないとエラーになる -
find
を多用してて読みづらい
このあたりはtprで解決したい内容ではあったのですが、正直コマンドをラップして使いやすくするためだけのツールにモチベーションがわかなすぎて開発停滞してます。
おわりに
一応断っておきますが、今回は実験して動くの確認したレベルなので、これでちゃんとアプリケーションを開発できるかは知らないです。
たぶんいくつか地雷はあると思うので、誰か思いっきり踏み抜いてください。