Scala.jsを使ってみたので、開発環境の構築から、簡単なスクリプトをブラウザ上で動かすところまでやった履歴を残しておく。
個人的にScalaを使う時はTypeSafe Activatorでパッケージ管理してEclipseで開発することにしているので、今回もその流れに沿って導入する。Scala.jsの開発はvimとsbtで十分なように思うが、個人的にこれに慣れているの。
OSはUbuntuを利用している。Windowsでもだいたい同じように動くはず。
まずは下記からScala IDE for Eclipseのダウンロード。
落としたファイルを解凍する。バージョンは適宜読み替え。
$ tar -xvf scala-SDK-4.0.0-vfinal-2.11-linux.gtk.x86_64.tar.gz
配下の実行ファイルを叩けばEclipseが立ち上がる。私が使ったバージョンではXmxは2GBになっていた。メモリが少ない場合やもっと確保したい場合は、eclipse.iniを編集する。
次に下記からTypeSafe Activatorをダウンロード。コマンドラインで使うだけなので、download the mini-packageと書いてある小さなリンクから最小限の構成をダウンロードする。
https://typesafe.com/get-started
解凍する。
$ unzip typesafe-activator-1.3.2-minimal.zip
どこか適当なディレクトリに置いてパスを通しておく。
$ mv activator-1.3.2-minimal ~/foo/activator-1.3.2 $ PATH=$PATH:~/foo/activator-1.3.2 $ export PATH=$PATH
activator newコマンドでプロジェクトを新規作成する。1回目はいろいろダウンロードするのですごく時間がかかる。
$ activator new Choose from these featured templates or enter a template name: 1) minimal-akka-java-seed 2) minimal-akka-scala-seed 3) minimal-java 4) minimal-scala 5) play-java 6) play-scala
1〜6を選べ的な内容が出る。他にもPlayでscala.jsを使うplay-scalajs-showcaseというテンプレートもいるけど(Playを使う人は参考に見ておくと良いかも)、今回はシンプルにminimal-scalaを選ぶ。
> 4 Enter a name for your application (just press enter for 'hello-scala-eclipse') > scala-js-example
project/plugins.sbtというファイルを作って、下記の内容を書き足す。Eclipseプロジェクト生成用のプラグインと、Scala.jsのプラグイン。
addSbtPlugin("com.typesafe.sbteclipse" % "sbteclipse-plugin" % "3.0.0") addSbtPlugin("org.scala-js" % "sbt-scalajs" % "0.6.2")
次にbuild.sbtを下記のように編集する。今回はscalajs-domを入れている。
enablePlugins(ScalaJSPlugin) name := """scala-js-example""" version := "1.0" scalaVersion := "2.11.5" libraryDependencies ++= Seq( "org.scala-js" %%% "scalajs-dom" % "0.8.0", "org.scalatest" % "scalatest_2.11" % "2.2.1" % "test" )
設定できたらEclipseプロジェクト化する。これの実行も初回は時間がかかる。
activator eclipse
出来上がったらEclipseを立ち上げ、File → Import → General → Existing Projects into Workspaceから生成したプロジェクトフォルダを読み込む。
まずはhello world的なことでも。下記のようなObjectを書く。JSAppをextendsして、mainを持ってる。
package com.example import scala.scalajs.js object Hello extends js.JSApp { def main() { println("hello world") } }
activator fastOptJSでJSを生成する。fastOptJSの他にFull-OptimizeしてくれるfullOptJSがある。普段はfastOptJSでリリース近づいてきたらfullOptJSで良いのだろうか。
$ activator fastOptJS
実行すると標準出力の最後の方に下記のように、jsが出力されたパスがinfoで出る。
[info] Fast optimizing /home/user/workspace/scala-js-example/target/scala-2.11/scala-js-example-fastopt.js [success] Total time: 1 s, completed 2015/03/21 11:05:21
プロジェクトのルートに適当なHTMLを書く。
<!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <script type="text/javascript" src="./target/scala-2.11/scala-js-example-fastopt.js"></script> </head> <body> <h1>てきとーなHTML</h1> <script> com.example.Hello().main(); </script> </body> </html>
printlnはJavaScriptのcosole.log的なものに書かれる。このHTML開くとコンソールにhello worldと出力される。
ちなみに出来上がったjsファイルは99KBありました。でかいね。でもfullOptJSすると16KBになった。
$ activator fullOptJS
consoleに出力しただけだとアレなんで、画面上にも出るようにしてみる。
package com.example import scala.scalajs.js object Hello extends js.JSApp { def main() { val document = js.Dynamic.global.document document.getElementById("foo").innerHTML = "hello world" } }
これでid=fooな要素をHTML側に用意すれば、画面にhello worldと表示される。
build.sbtに下記のdependencyを追加する。
"be.doeraene" %%% "scalajs-jquery" % "0.8.0"
Eclipseに認識させる為に、activator eclipseを再度実行してプロジェクトをRefresh(F5)しておく。
$ activator eclipse
こんな感じで書ける。
package com.example import scala.scalajs.js import org.scalajs.jquery.jQuery object Hello extends js.JSApp { def main() { jQuery("#foo").html("hello world") } }
当然ながらこのスクリプトはjQueryをHTMLの方でリンクしていないと動かない。
引数とかも普通に渡せる。下記はjQueryのElementと数値を引数で渡して、2倍した値をセットする場合の例。JSに出したい関数は@JSExportしておく。
package com.example import scala.scalajs.js import org.scalajs.jquery.jQuery import org.scalajs.jquery.JQuery import scala.scalajs.js.annotation.JSExport object Hello extends js.JSApp { def main() { jQuery("#foo").html("hello world") } @JSExport def foo(jq: JQuery, i: Int) { jq.html((i * 2).toString) } }
呼び出す側はこんな感じ。com.example.Hello().fooとして呼び出せる。
<!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.3/jquery.min.js"></script> <script src="./target/scala-2.11/scala-js-example-fastopt.js"></script> </head> <body> <h1>てきとーなHTML</h1> <div id="foo"></div> <script> com.example.Hello().foo($("#foo"), 5); </script> </body> </html>
型のチェックはやってくれないので、違う型を送るとJavaScript側でエラーになったりする。
引数でcase classを返すこともできるらしい。こんな感じ。
package com.example import scala.scalajs.js import scala.annotation.meta.field import org.scalajs.jquery.jQuery import org.scalajs.jquery.JQuery import scala.scalajs.js.annotation.JSExport object Hello extends js.JSApp { def main() { jQuery("#foo").html("hello world") } case class Item( @(JSExport @field) str: String, @(JSExport @field) i: Int) @JSExport def foo(): Item = Item("文字列", -1) }
<div id="foo"></div> <script> var item = com.example.Hello().foo(); $("#foo").html( item.str + ", " + item.i ); </script>
これで値の返し方はだいぶ楽になる。
Arrayを返すこともできる。但し普通のArrayではなくjs.Arrayを使う。
def foo(): js.Array[Item] = js.Array(Item("文字列1", -1), Item("文字列2", 0))
たとえばURLEncodeとかしてみよう。
package com.example import scala.scalajs.js import scala.scalajs.js.annotation.JSExport object Hello extends js.JSApp { def main() { java.net.URLEncoder.encode("えんこーど", "utf-8") } }
これはコンパイルでエラーになる。
java.lang.RuntimeException: There were linking errors at scala.sys.package$.error(package.scala:27)
Javaのソースの中でも対応している機能と対応していない機能があるらしい。
例えば下記のコードはコンパイルが通る。
java.net.URI.create("http://example.com/").getHost
対応状況は下記あたりを見ると概ねわかりそう。RandomやRegexなんかはいる。
件のURLEncodeについてはJavaScript側のencodeURIComponentを呼び出す機能が提供されている。
js.URIUtils.encodeURIComponent("えんこーど")
js.URIUtils, js.Date, js.Math, js.timers.RawTimers あたりは頻繁に使いそう。
最初はこんなバギーなもの使うよりJavaScriptを直書きした方が楽だろと思っていたのだけど、いざ書いてみるとなかなかに快適で驚いた。特にちょっと込み入った処理とか長い文字列の連結を書く場合にはScalaの書きやすさに助けられた。ネームスペースの安心さもある。
このページの表示に使っているJavaScriptもScala.jsを使っている。
悪い点としては、コンパイルが面倒なので画面上の細かい処理をScala.jsで修正→コンパイル→表示→修正→コンパイル→表示としていると、更新フローが自動化できていてボタン1つで反映されるようになっていても若干ストレスが溜まる。
ライブラリ的なものはScala.jsで書いてテストもScala側で完結させておいて、画面のちょっとした呼び出しはJavaScriptで書くと楽だろうか。