Scala.jsの導入から簡易なサンプルの作成まで

Scala.jsを使ってみたので、開発環境の構築から、簡単なスクリプトをブラウザ上で動かすところまでやった履歴を残しておく。

個人的にScalaを使う時はTypeSafe Activatorでパッケージ管理してEclipseで開発することにしているので、今回もその流れに沿って導入する。Scala.jsの開発はvimとsbtで十分なように思うが、個人的にこれに慣れているの。

OSはUbuntuを利用している。Windowsでもだいたい同じように動くはず。

created_at:2015/03/22
Scala 2.11.5, Scala.js 0.6.2, scalajs-dom 0.8.0, scalatest 0.8.0, Typesafe Activator 1.3.2, Scala IDE for Eclipse 4.0.0

Eclipseの用意

まずは下記からScala IDE for Eclipseのダウンロード。

http://scala-ide.org/

落としたファイルを解凍する。バージョンは適宜読み替え。

$ tar -xvf scala-SDK-4.0.0-vfinal-2.11-linux.gtk.x86_64.tar.gz

配下の実行ファイルを叩けばEclipseが立ち上がる。私が使ったバージョンではXmxは2GBになっていた。メモリが少ない場合やもっと確保したい場合は、eclipse.iniを編集する。

Typesafe Activatorの用意

次に下記から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

まずは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と表示される。

jQueryを使う

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を引数に返す

引数で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))

Javaのクラスを呼んでみる

たとえば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なんかはいる。

https://github.com/scala-js/scala-js/tree/fcca53409b17e92431f2ce0771b9053067517889/javalib/src/main/scala/java

件の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で書くと楽だろうか。