Scala.jsことはじめ

# Scala.js
AltJSというと、TypeScriptなんかが一番の流行で、それは認めるところなんだけれど、Webpackなどを含む環境のセットアップがどうしても馴染めません。Scala.jsというのはそういう人のための（？）、最適なツールのように思えます。歴史は結構古いみたいで、いまだに開発は止まっていないようです。この時点の最新版は[1.0.0-M6](https://www.scala-js.org/libraries/)でした。
ドキュメントにはなんだか、日本で人気とあります。本当だろうか。

# 準備
## クライアントだけの単独プロジェクト
ScalaとSBTとNodejsがインストールされている環境を前提としています。まずは以下のようにプロジェクトのひな型を準備します。

```
 sbt new scala/scala-seed.g8
```

```project/plugins.sbt
addSbtPlugin("org.scala-js" % "sbt-scalajs" % "1.0.0-M6")
// 外部のJavascript参照のためのプラグイン(0.6系では不要）
addSbtPlugin("org.scala-js" % "sbt-jsdependencies" % "1.0.0-M6")
```

```build.sbt
import Dependencies._

lazy val root = (project in file("."))
  .enablePlugins(ScalaJSPlugin, JSDependenciesPlugin)
  .settings(
    inThisBuild(List(
      organization := "com.example",
      scalaVersion := "2.12.7",
      version      := "0.1.0-SNAPSHOT"
    )),
    name := "Tutrial"
  )

scalaJSUseMainModuleInitializer := true

```
これでビルド環境の準備は完了です。
`build.sbt`の最後の`scalaJSUseMainModuleInitializer`は、当該オブジェクトをApplicationとして出力するためのもので、具体的には出力された`-fastopt.js`の最後で、引数なしでMainメソッドが呼ばれるようになります。要するにエントリーポイントです。falseの場合はLibraryとして出力されます。

簡単なアプリを書いてみます（これは、ほとんどテンプレのままですが）

```src/main/scala/example/Hello.scala
package example

object Hello extends Greeting {
  def main(args: Array[String]): Unit = {
    println(greeting)
  }
}

trait Greeting {
  lazy val greeting: String = "hello"
}
```

普通にSBTのコマンド実行で、Javascriptが出力され、runで実行も可能です。普通にScalaで実行しているように見えて、実は出力されたJavascriptをNode.js経由で実行しているのがミソです。ちなみに`sbt fullOptJS`とするとGoogle Closure Compilerを使ったさらに最適化されたJavascriptコードが出力されます。

```
sbt fastOptJS
sbt run
```

ここで[ScalaTags](http://www.lihaoyi.com/scalatags/)などを使うことによって、動的にHTMLやJavascriptを生成することができます。サーバサイドを含めて簡易的に実行するためには、[Workbench](https://github.com/lihaoyi/workbench)プラグインを使うとよいでしょう。



## Cross-Building環境
Workbenchを使ってもよいのですが、Scala.jsなんかを使う人は、サーバーサイドもScalaで書こうという言う人が多いのではないかと思われます。例えば、PlayとかAkka-Httpとかです。これについては、すでにgilter8でテンプレートが用意されています。

Scala.js 1.0以降は[sbt.crossproject](https://github.com/portable-scala/sbt-crossproject)を使って、サーバー、クライアントそれぞれをコンパイルするのがベストプラクティスのようです。
ちなみにウェブサーバーと一緒に使うためのプラグインとしては、[sbt-web-scalajs](https://index.scala-lang.org/vmunier/sbt-web-scalajs/sbt-web-scalajs/1.0.6?target=_2.12_1.0)というのを使うのが一般的のようです。
具体的には以下のようにserverと、clientを分離して、build.sbtに記載します。この際、同名のフォルダをプロジェクト内に作っておきます。ついでにsharedというフォルダを作って、両方のから参照できるようにしています。詳細は上記のテンプレートで確認してください。


``` project/plugins.sbt
addSbtPlugin("com.vmunier" % "sbt-web-scalajs" % "1.0.8-0.6")
addSbtPlugin("org.scala-js" % "sbt-scalajs" % "0.6.25")
addSbtPlugin("org.portable-scala" % "sbt-scalajs-crossproject"  % "0.6.0")
```

```build.sbt
lazy val server = project.settings(
  scalaJSProjects := Seq(client),
  pipelineStages in Assets := Seq(scalaJSPipeline)
).enablePlugins(SbtWeb)

lazy val client = project.enablePlugins(ScalaJSPlugin, ScalaJSWeb)
```

# Javascript Libraryの利用
Typescriptと同じように、既往のJavascriptライブラリを使ってScala.jsを安全にコンパイルする際には、型情報が必要になります。有名どころについては、[Facade](https://www.scala-js.org/libraries/facades.html)が提供されていて、それを依存ライブラリに加えればよいのですが、1.0系用になっていなかったり、整備されていなかったりします。Typescriptの型情報をScala.jsに変換する[scala-js-ts-importer](https://github.com/sjrd/scala-js-ts-importer)というのもあるので、適宜使ってみるとよいでしょう。
とにかく、コンパイルするソースコードから、型情報を記載したscalaファイルが参照可能になっていることが肝要です。書き方については非常に分かりにくいのですが、ThreeJSのFacadeからサンプルを引いてみます。

```threejs.scala
@js.native
@JSGlobal("THREE.Camera")
class Camera extends Object3D {
  var matrixWorldInverse: Matrix4 = js.native
  var projectionMatrix: Matrix4 = js.native
  override def lookAt(vector: Vector3): Unit = js.native
  def clone(camera: Camera): Camera = js.native
}
```

上記のコードがあると、Scala.jsのコードから`val camera = new Camera()`という感じでJavascriptのTHREE.Cameraクラスが扱えます。`@JSGlobal("THREE.Camera")`というところが、それらを結びつけるアノテーションです（0.6以前は@JSName）。


# Playによるサンプルアプリの作成
それでは、Playを使って簡単なThreeJSのアプリを作ってみます。以下のようにテンプレートからプロジェクトを生成します。ライブラリの依存関係が厳しそうなので、極力ライブラリのバージョンはいじらないほうが良いと思います。

```
sbt new vmunier/play-scalajs.g8
```

上述のように、内部にはclient, server, sharedというフォルダが生成されています。server以下は見慣れたPlayのフォルダ構成が見て取れます`build.sbt`で参照されている[Scala.js-Scripts](https://github.com/vmunier/scalajs-scripts)がclientとserverのコードをつないでくれます。Scala.js-Scriptsは、具体的にはScala.jsの出力をTwirlやScalatagsといったテンプレートエンジンにつなぐものです。

```build.sbt
import sbtcrossproject.{crossProject, CrossType}

lazy val server = (project in file("server"))
  .settings(commonSettings)
  .settings(
    scalaJSProjects := Seq(client),
    pipelineStages in Assets := Seq(scalaJSPipeline),
    pipelineStages := Seq(digest, gzip),
    // triggers scalaJSPipeline when using compile or continuous compilation
    compile in Compile := ((compile in Compile) dependsOn scalaJSPipeline).value,
    libraryDependencies ++= Seq(
      "com.vmunier" %% "scalajs-scripts" % "1.1.2",
      guice,
      specs2 % Test
    ),
  )
  .enablePlugins(PlayScala)
  .dependsOn(sharedJvm)

lazy val client = (project in file("client"))
  .settings(commonSettings)
  .settings(
    scalaJSUseMainModuleInitializer := true,
    libraryDependencies ++= Seq(
      "org.scala-js" %%% "scalajs-dom" % "0.9.6"
    ),
    jsDependencies ++= Seq(
      "org.webjars" % "three.js" % "r88" / "r88/three.js"
    )
  )
  .enablePlugins(ScalaJSPlugin, ScalaJSWeb)
  .dependsOn(sharedJs)

lazy val shared = crossProject(JSPlatform, JVMPlatform)
  .crossType(CrossType.Pure)
  .in(file("shared"))
  .settings(commonSettings)

lazy val sharedJvm = shared.jvm
lazy val sharedJs = shared.js

lazy val commonSettings = Seq(
  scalaVersion := "2.12.7",
  organization := "jp.ad.wide.hongo.kasuya"
)

// loads the server project at sbt startup
onLoad in Global := (onLoad in Global).value andThen {s: State => "project server" :: s}
```

ThreeJSの読み込みのためにjsDependenciesに追記をしています。この参照は[WebJars](https://www.webjars.org/)からとってきています。ここで追加されたファイルは、依存ライブラリとしてまとめてコンパイルされます。この例では、`client/target/scala2.12/client-jsdeps.js`にまとめられます。

## ThreeJS型情報のインポート
[ThreeJSのFacade](https://github.com/antonkulaga/threejs-facade)を読み込もうと思ったら、リポジトリが死んでいたので、ソース丸ごとclient側のプロジェクトにインポートしました。あまりいいやり方ではないと思いますが、`client/src/main/scala`フォルダに、対象コードをコピーします。ちなみにコンパイルの際には、`@JSName`を`@JSGlobal`に直せという警告がたくさん出ます。1.0系の場合はそれらを実際に変換して直さないとエラーで停止します。将来のためには、きちんと整備したほうが良いでしょう。

## プログラムの動作原理
テンプレートで実行できるアプリケーションは、Playが分かれば簡単に読み解けると思います。MVCでいうところのControllerである`ScalaJSExample.scala`が、ViewにあるTwirlテンプレートの`views.html.index`を呼び出しています。

```ScalaJSExample.scala
@Singleton
class Application @Inject()(cc: ControllerComponents) extends AbstractController(cc) {
  def index = Action {
    Ok(views.html.index(SharedMessages.itWorks))
  }
}
```

`views.html.index`はその中で、`main.scala.html`を呼び出しています。この中の`@scalajs.html.scripts`ディレクティブが重要な要素で、生成されたJavaScriptおよびその依存関係にあるコードをロードする<script>タグを挿入します。これは[Scala.js Scripts](https://github.com/vmunier/scalajs-scripts)の機能です。

```main.scala.html
@(title: String)(content: Html)
<!DOCTYPE html>

<html>
  <head>
    <title>@title</title>
    <link rel="stylesheet" media="screen" href="@routes.Assets.versioned("stylesheets/main.css")">
    <link rel="shortcut icon" type="image/png" href="@routes.Assets.versioned("images/favicon.png")">
  </head>
  <body>
    @content
    @scalajs.html.scripts("client", routes.Assets.versioned(_).toString, name => getClass.getResource(s"/public/$name") != null)
  </body>
</html>
```

乱暴ですが、サンプルコードを利用して、そこにThreeJSの基本的なコードを書いてみます。main関数内で、initという名前のコールバックを指定しています。内容としては、よくある箱を表示するサンプルです。きちんと型情報があるので、`dom.window.requestAnimationFrame`に登録する関数などは、Javascriptよりは少し厳密になっていますdomを使っていますが、jQueryの方が簡単に書けると思います。

```ScalaJSExample.scala

import org.scalajs.dom
import dom.document
import org.denigma.threejs._
import org.scalajs.dom.raw.HTMLElement

object ScalaJSExample {

  def main(args: Array[String]): Unit = {
    dom.window.addEventListener("load", init)
  }

  def init (e:dom.Event) = {
    val canvas: HTMLElement = document.getElementById("myCanvas").asInstanceOf[HTMLElement]
    val width = 960
    val height = 540

    println(canvas)
    val renderer = new WebGLRenderer()
    renderer.setSize(width, height)
    canvas.appendChild(renderer.domElement)

    val scene = new Scene()

    val camera = new OrthographicCamera(width / -2, width / 2, height / 2, height / -2, 1, 10000)
    camera.position.z = 1000

    // 箱を作成
    val geometry = new BoxGeometry(400, 400, 400);
    val material = new MeshNormalMaterial();
    val box = new Mesh(geometry, material);
    def tick(timestamp:Double):Unit = {
      box.rotation.y += 0.01
      renderer.render(scene, camera)
      dom.window.requestAnimationFrame(tick)
    }
    scene.add(box)
    tick(0)
  }
}

```

うまくいくと、以下のように表示されます。
<img width="996" alt="sample.png" src="https://qiita-image-store.s3.amazonaws.com/0/51703/c4d47545-958b-645f-32b9-8bdd880fcc4d.png">

# まとめ
確かにScalaで全部かけるのは楽しいと思いますが、いかんせん情報も少なくて学習負荷が高いです。加えて、0.6系から1.0系へ移行の最中だとは思いますが、関連ライブラリの対応が不十分でコンパイルがうまくできないことが多いです。しばらくは0.6系を使うのが正解のようにも思えます。と、言いつつここまでかなり時間をかけてしまったので、今後も使い続けてみようと思います。


# 参考
- [scala.js公式ページ](https://www.scala-js.org/)
- [sbt-web-scalajs](https://index.scala-lang.org/vmunier/sbt-web-scalajs/sbt-web-scalajs/1.0.6?target=_2.12_1.0)
- [scala.js-scripts](https://github.com/vmunier/scalajs-scripts)
- [ScalaJS for TypeScripter](https://creators-note.chatwork.com/entry/2018/02/13/120946)