Scala.jsでクロスコンパイル - 1. 環境構築


Scala.jsは、ScalaのコードをJavaScript（JS）に変換するコンパイラです。
[公式の紹介ページ](https://www.scala-js.org/)では、以下の4点がScala.jsの優れた点であると謳われています。

* 正確性: Scalaの優れた型システムやコンパイル時検査を活用
* 高性能: [実行時間は生JSの高々2倍](https://www.scala-js.org/doc/internals/performance.html)
* 相互運用性: [DOM](http://www.scala-js.org/libraries/facades.html)や[JSのライブラリ](http://www.scala-js.org/libraries/facades.html)が利用可能
* IDE: IntelliJがScala.jsの記述を完璧にサポート

Scala.jsは、単なるJSの代替記法（altJS）に加え、JSとJava仮想マシン（JVM）とで動作するコードのクロスビルドツールとして利用することができます。
クロスビルドに関する資料として、以下のようなものが挙げられます。

* [Scala.js - Cross-building （公式）](https://www.scala-js.org/doc/project/cross-build.html)
* [Isomorphic web development with scala and scala.js](https://www.slideshare.net/TanUkkii/isomorphic-web-development-with-scala-and-scalajs)
* [scalaとか・・・ - scalazとscalapropsとmsgpack4zを全部Scala.js対応した](http://d.hatena.ne.jp/xuwei/20160321/1458542313)
* [PABlog - Scala.jsとJVMの両対応コードとScala.jsのテストの書き方](http://blog.pab-tech.net/2014/02/26/htmlda-scala-js.html)

しかし、これらの資料は、十分にクロスビルドを説明しているとは言えません。
クロスビルド初心者は、チュートリアルが存在しないので、試す前に諦めてしまうかもしれません。
私も全然わからず、ソースコードから動作を推測するなどして、やっと動かすことができました。
そこで、コードを書く部分を含めた、Scala.jsクロスコンパイルの簡単なチュートリアルを作成します。

本ページでは、Scala.jsでクロスコンパイルできる環境を構築し、JSとJVM向けにそれぞれ実行可能なものが生成されることを確認します。

# 環境のセットアップ
事前に[SBTをインストール](http://www.scala-sbt.org/download.html)しておいてください。

以下に示すように`sbt new`コマンドを実行すると、自動的にScalajsクロスビルド対応のSBTプロジェクトが生成されます。

```text
$ sbt new umireon/scalajs-cross-seed.g8
Minimum Cross-building Scala.js build.

name [My Something Project]: Scalajs Cross Example⏎

Template applied in ./scalajs-cross-example

```

自動生成されたプロジェクトの内容は以下の通りです。

```text
scalajs-cross-example/
├── build.sbt
├── js/src/main/scala/example/Hello.scala
├── jvm/src/main/scala/example/Hello.scala
├── project/
│   ├── build.properties
│   └── scalajs.sbt
└── shared/src
    ├── main/scala/example/Hello.scala
    └── test/scala/example/HelloSpec.scala
```

これで、Scala.jsクロスビルドの準備は完了です。
ここでは、build.sbtの内容に触れませんが、気になる方は[公式ドキュメント](https://www.scala-js.org/doc/project/cross-build.html)を確認してください。

# ビルドの確認
先ほど生成したプロジェクトにはテストが含まれています。
Scala.jsが正しく動作しているか確認するために、JSとJVM上でビルド及びテストを実行してみます。
プロジェクトディレクトリへ移動し、以下のコマンドを実行してみてください。

```text:
$ sbt test
[info] HelloSpec:
[info] The Hello object
[info] - should say hello
[info] Run completed in 660 milliseconds.
[info] Total number of tests run: 1
[info] Suites: completed 1, aborted 0
[info] Tests: succeeded 1, failed 0, canceled 0, ignored 0, pending 0
[info] All tests passed.

[info] Fast optimizing /path/to/scalajs-cross-example/hello-test-fastopt.js
[info] HelloSpec:
[info] The Hello object
[info] - should say hello
[info] Run completed in 606 milliseconds.
[info] Total number of tests run: 1
[info] Suites: completed 1, aborted 0
[info] Tests: succeeded 1, failed 0, canceled 0, ignored 0, pending 0
[info] All tests passed.
[success] Total time: 24 s, completed 2017/04/18 14:23:36

```

出力結果を見ると、`All tests passed.`が2回現れていることがわかります。
これは、1番目がJVMにおけるテストの結果、2番目がJSにおけるテストの結果を示すものです。

ビルド後、ディレクトリ構成は以下のように変化します。

```text
scalajs-cross-example/
├── build.sbt
├── js
│   ├── src/main/scala/example/Hello.scala
│   └── target/
├── jvm
│   ├── src/main/scala/example/Hello.scala
│   └── target/
├── project/
│   ├── build.properties
│   └── scalajs.sbt
├── shared/src
│   ├── main/scala/example/Hello.scala
│   └── test/scala/example/HelloSpec.scala
└── target/
```

# ディレクトリ構成
本節では、上記のディレクトリ構成のうち、Scala.jsクロスビルド特有のものについて説明します。

`shared`は、JSとJVMで共有するコードを格納するディレクトリです。
ここに格納されたScalaコードは、JVMビルド時とJSビルド時に別々にコンパイルされます。
したがって、クロスビルド実現のためには、JVMとJSの両環境で実行可能なScala.jsコードを書く必要があります。

`js`及び`jvm`は、各環境にて実行可能なオブジェクトが生成されるディレクトリです。
JS用の生成物は`js/target/`、JVM用の生成物は`jvm/target`にそれぞれ出力されます。
また、`Hello.scala`のように、片方の環境のみで実行したいコードもここに格納します。
参考のため、各`Hello.scala`の内容を以下に示します。

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

import scala.scalajs.js

object Hello extends Greeting with js.JSApp {
  def main(): Unit = {
    println(greeting)
  }
}
```

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

object Hello extends Greeting with App {
  println(greeting)
}
```

# 生成物の確認

JVMでは、JARファイルを生成し、それが実行可能であることを確認します。
JARファイルは`sbt package`コマンドを実行することにより生成され、`jvm/target/scala-2.12`以下に得られます。

```
$ ls scalajs-cross-example/jvm/target/scala-2.12
classes				test-classes
hello_2.12-0.1.0-SNAPSHOT.jar
```

実行結果は、以下のコマンドにより確認できます。

```
$ sbt helloJVM/run
...
[info] Running example.Hello
hello
[success] Total time: 1 s, completed 2017/04/18 15:11:04
```

JSでは、JSファイルを生成し、それが実行可能であることを確認します。
JSファイルは`sbt fullOptJS`コマンドを実行することにより生成され、`js/target/scala-2.12`以下に得られます。

```
$ ls scalajs-cross-example/jvm/target/scala-2.12
classes                   hello-test-fastopt.js
hello-jsdeps.js           hello-test-fastopt.js.map
hello-jsdeps.min.js       hello-test-jsdeps.js
hello-opt.js              hello_sjs0.6_2.12-0.1.0-SNAPSHOT.jar
hello-opt.js.map          test-classes
```

実行結果は、以下のコマンドにより確認できます。

```
$ node scalajs-cross-example/js/target/scala-2.12/hello-opt.js
hello
```

# 続き
Scala.jsでクロスコンパイル - 2. 型とコレクション
そのうち書く