CLOVER🍀

That was when it all began.

Scala.jsで遊んでみよう

ScalaコードをJavaScriptコンパイルする、Scala.jsというものがあるらしいです。

Scala.jsを使って、ScalaJavaScriptコンパイルする
http://www.infoq.com/jp/news/2013/06/scalajs

これを使用すると、ScalaのコードをJavaScriptコンパイルすることができる…らしいです。

オフィシャルサイトは、こちら。

Scala.js A Scala to JavaScript compiler
http://www.scala-js.org/

このサイトには、こんなことが書いてあります。

Important notice! Scala.js is still experimental! Although this is a project of LAMP/EPFL for which we will continue to provide best-effort improvements and bug fixes, it is not supported by Typesafe, and not part of any of their support contracts. You have been warned!

http://www.scala-js.org/

まあ、実験的なプロジェクトみたい?Typesafe社のサポートはないみたいですね。

それで、最近こちらのブログでScala.jsが盛り上がっていました。

Scala.jsが凄い
http://mizchi.hatenablog.com/entry/2014/02/14/211400

これを見た時は、「へぇ〜」といった感じだったのですが、とっかかりとして挙げられている以下のリポジトリの内容を見て、これまでCoffeeScriptみたいなJavaScriptを生成するものに興味を示したことがなかったのですが、Scalaを使っていることもあり「これは面白そうかも」と思いました。

Example application written in Scala.js
https://github.com/sjrd/scala-js-example-app

このリポジトリをcloneして遊んでみよう、みたいなチュートリアルなのですが、ここはまっさらから始めてみることにしましょう。

あ、最終的にできあがるものは、上記リポジトリと見た目は同じです。テストコードは抜きですが。

準備

どうも、Scala.jsはsbtのプラグインとして利用するみたいです。

なので、まずはプラグインの設定をします。
project/plugins.sbt

addSbtPlugin("org.scala-lang.modules.scalajs" % "scalajs-sbt-plugin" % "0.3")

現在の最新版は、0.3みたいですね。

そして、sbt自体の設定。
build.sbt

name := "scala-js-getting-started"

version := "0.0.1-SNAPSHOT"

scalaVersion := "2.10.3"

organization := "org.littlewings"

scalacOptions ++= Seq("-Xlint", "-deprecation", "-unchecked")

scalaJSSettings

// Specify additional .js file to be passed to package-js and optimize-js
unmanagedSources in (Compile, ScalaJSKeys.packageJS) +=
    baseDirectory.value / "js" / "startup.js"

いろいろ書いていますが、重要なのはここです。

scalaJSSettings

// Specify additional .js file to be passed to package-js and optimize-js
unmanagedSources in (Compile, ScalaJSKeys.packageJS) +=
    baseDirectory.value / "js" / "startup.js"

Scala.jsプラグインの設定と、その下になんかJavaScriptの追加があります。このファイルは、後で出しましょう。

Scalaコードを書く

とりあえず、中身はほぼ一緒ですけど、パッケージ名/クラス名を変えてこんな感じにしました。
src/main/scala/org/littlewings/scalajs/ScalaJsGettingStarted.scala

package org.littlewings.scalajs

import scala.scalajs.js
import js.Dynamic.global

object ScalaJsGettingStarted {
  def main(): Unit = {
    val p = global.document.createElement("p")
    p.innerHTML = "<strong>It works!</strong>"
    global.document.getElementById("playground").appendChild(p)
  }
}

globalは、ブラウザ上のwindowオブジェクトに該当しそうですね。

起動スクリプトの追加

先ほど、build.sbtの中で書いていたJavaScriptですが、このような中身で用意します。
js/startup.js

ScalaJS.modules.org_littlewings_scalajs_ScalaJsGettingStarted().main();

「ScalaJS.modules」の後は、「.」で繋いで、作成したクラスのパッケージ名を「_」に変えて、エントリポイントのメソッドを呼び出せばよいみたいですね。

なお、このようなファイルを用意して、sbtに

// Specify additional .js file to be passed to package-js and optimize-js
unmanagedSources in (Compile, ScalaJSKeys.packageJS) +=
    baseDirectory.value / "js" / "startup.js"

のように追加をしてあげないと、ブラウザでロードしても動いてくれませんのでご注意を。

パッケージング

では、Scalaコードをパッケージング(JavaScriptに変換)してみましょう。
コマンドは、

> packageJS

です。

では、実行。

> packageJS
[info] Updating {file:/xxxxx/scala-js-getting-started/}scala-js-getting-started...
[info] Resolving org.fusesource.jansi#jansi;1.4 ...
[info] Done updating.
[info] Extracting /xxxxx/.ivy2/cache/org.scala-lang.modules.scalajs/scalajs-library_2.10/jars/scalajs-library_2.10-0.3.jar ...
[info] Compiling 1 Scala source to /xxxxx/target/scala-2.10/classes...
[info] Packaging /xxxxx/scala-js-getting-started/target/scala-2.10/scala-js-getting-started.js ...
[info] Packaging /xxxxx/scala-js-getting-started/target/scala-2.10/scala-js-getting-started-extdeps.js ...
[success] Total time: 23 s, completed 2014/02/21 22:58:40

できあがるファイルは、こんな感じです。

$ ll -h target/scala-2.10/
合計 38M
drwxrwxr-x 3 xxxxx xxxxx 4.0K  221 22:58 ./
drwxrwxr-x 5 xxxxx xxxxx 4.0K  221 22:58 ../
drwxrwxr-x 3 xxxxx xxxxx 4.0K  221 22:58 classes/
-rw-rw-r-- 1 xxxxx xxxxx  22M  221 22:58 scala-js-getting-started-extdeps.js
-rw-rw-r-- 1 xxxxx xxxxx  16M  221 22:58 scala-js-getting-started-extdeps.js.map
-rw-rw-r-- 1 xxxxx xxxxx    0  221 22:58 scala-js-getting-started-intdeps.js
-rw-rw-r-- 1 xxxxx xxxxx 3.6K  221 22:58 scala-js-getting-started.js
-rw-rw-r-- 1 xxxxx xxxxx 1.2K  221 22:58 scala-js-getting-started.js.map

この、「.js」で終わっている3ファイルを使用します。ファイル名は、sbtでのプロジェクト名が反映されています。

先のブログにも出ていましたが、確かに「extdeps.js」で終わっているファイルのサイズがヤバいですね。

あとは、このJavaScriptを読み込むHTMLを作成して、読み込ませます。
index-dev.html

<!DOCTYPE html>
<html>
<head>
  <title>Example Scala.js application</title>
  <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
</head>
<body>

<h1>Example Scala.js application - development version</h1>

<p>After having compiled and packaged properly the code for the application
(using `sbt packageJS`), you should see "It works" herebelow.
See README.md for detailed explanations.</p>

<div id="playground">
</div>

<script type="text/javascript" src="./target/scala-2.10/scala-js-getting-started-extdeps.js"></script>
<script type="text/javascript" src="./target/scala-2.10/scala-js-getting-started-intdeps.js"></script>
<script type="text/javascript" src="./target/scala-2.10/scala-js-getting-started.js"></script>

</body>
</html>

読み込ませているのは、ここですね。

<script type="text/javascript" src="./target/scala-2.10/scala-js-getting-started-extdeps.js"></script>
<script type="text/javascript" src="./target/scala-2.10/scala-js-getting-started-intdeps.js"></script>
<script type="text/javascript" src="./target/scala-2.10/scala-js-getting-started.js"></script>

ブラウザで開いて、「It works!」と表示されていれば成功です。

オプティマイズする

先ほどは「packageJS」でしたが、今度はオプティマイズして生成されるJavaScriptのサイズを小さくしてみます。

とりあえず、いったんクリア。

> clean
[success] Total time: 1 s, completed 2014/02/21 23:04:13

実行するコマンドは、

> optimizeJS

です。

では、実行。

> optimizeJS
[info] Updating {file:/xxxxx/scala-js-getting-started/}scala-js-getting-started...
[info] Resolving org.fusesource.jansi#jansi;1.4 ...
[info] Done updating.
[info] Extracting /xxxxx/.ivy2/cache/org.scala-lang.modules.scalajs/scalajs-library_2.10/jars/scalajs-library_2.10-0.3.jar ...
[info] Compiling 1 Scala source to /xxxxx/scala-js-getting-started/target/scala-2.10/classes...
[info] Packaging /xxxxx/scala-js-getting-started/target/scala-2.10/scala-js-getting-started-extdeps.js ...
[info] Packaging /xxxxx/scala-js-getting-started/target/scala-2.10/scala-js-getting-started.js ...
[info] Optimizing /xxxxx/scala-js-getting-started/target/scala-2.10/scala-js-getting-started-opt.js ...
[success] Total time: 191 s, completed 2014/02/21 23:08:25

うちの環境で、3分ちょいですか…まあ、Core 2 Duoという貧弱環境ですので。サイズは、確かにだいぶ小さくなりますね。

生成されるファイルは、このようになります。

$ ll -h target/scala-2.10/
合計 38M
drwxrwxr-x 3 xxxxx xxxxx 4.0K  221 23:08 ./
drwxrwxr-x 5 xxxxx xxxxx 4.0K  221 23:05 ../
drwxrwxr-x 3 xxxxx xxxxx 4.0K  221 23:05 classes/
-rw-rw-r-- 1 xxxxx xxxxx  22M  221 23:05 scala-js-getting-started-extdeps.js
-rw-rw-r-- 1 xxxxx xxxxx  16M  221 23:05 scala-js-getting-started-extdeps.js.map
-rw-rw-r-- 1 xxxxx xxxxx    0  221 23:05 scala-js-getting-started-intdeps.js
-rw-rw-r-- 1 xxxxx xxxxx 227K  221 23:08 scala-js-getting-started-opt.js
-rw-rw-r-- 1 xxxxx xxxxx 3.6K  221 23:05 scala-js-getting-started.js
-rw-rw-r-- 1 xxxxx xxxxx 1.2K  221 23:05 scala-js-getting-started.js.map

この場合、使用するファイルは「scala-js-getting-started-opt.js」だけになります。

こちらも、同じようにJavaScriptを読み込むHTMLをこんな感じで用意します。
index.html

<!DOCTYPE html>
<html>
<head>
  <title>Example Scala.js application</title>
  <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
</head>
<body>

<h1>Example Scala.js application - optimized version</h1>

<p>After having compiled and optimized properly the code for the application
(using `sbt optimizeJS`), you should see "It works" herebelow.
See README.md for detailed explanations.</p>

<div id="playground">
</div>

<script type="text/javascript" src="./target/scala-2.10/scala-js-getting-started-opt.js"></script>

</body>
</html>

こちらも、同じようにブラウザで開いて「It works!」と表示されればOKです。

面白い試みなのですが、実際に使うには乗り越える必要があるものが多そうですね…。とりあえず、動かしてみましたよということで。

オマケ:オプティマイズして生成するJavaScriptを、PrettyPrintしてみよう

optimizeJSで生成されるJavaScriptですが、以下をbuild.sbtに加えておくと、最適化後のJavaScriptが整形されて出力されます。

ScalaJSKeys.optimizeJSPrettyPrint := true

以下の設定よりも後ろに入れるんでしょう。

scalaJSSettings

まあ、中身を見たいと思うかどうかはさておきですが。