Scala.jsを試してみた

Web開発とJavaScriptは切っても切り離せません。サーバサイドにどのような言語を使っていたとしてもJavaScriptとはよろしくお付き合いする必要があります。しかし世の中にはJavaScriptの代替となるAltJSなるものがあります。別の言語で記述したプログラムをJavaScriptにトランスレートしてくれるというものです。

AltJSには様々なものがありますが、Scalaを使っているからにはScala.jsしかありません。というわけで、Scala.jsをScalatraと組み合わせて使う場合のサンプルプロジェクトを作成してみました。

github.com

このプロジェクトはsbtのマルチプロジェクトになっており、以下の3プロジェクトで構成されています。

  • common(サーバとクライアントで共通的に使用するコードを格納)
  • server(Scalatraで実装されたWebアプリケーションを格納)
  • client(Scala.jsでJavaScriptに変換されるコードを格納)

commonプロジェクトには以下のケースクラスが格納されています。クライアント・サーバ間のAjaxでやりとりするJSONマッピングするためのものです。

package model

case class Book(title: String, author: Seq[String])

serverプロジェクトにはScalatraで以下のアクションが配置されています。リクエストを受けて上記のケースクラスのシーケンスをJSONで返すだけの単純なWeb APIです。

package controller

import org.scalatra._
import org.scalatra.json.JacksonJsonSupport
import org.json4s.DefaultFormats
import model.Book

class IndexController extends ScalatraFilter with JacksonJsonSupport {

  implicit val jsonFormats = DefaultFormats

  post("/books"){
    contentType = formats("json")
    Seq(
      Book("Scalatra in Action", Seq("Dave Hrycyszyn", "Stefan Ollinger", "Ross A. Baker")),
      Book("Scala Recipes", Seq("Naoki Takezoe", "Takako Shimamoto"))
    )
  }
}

clientプロジェクトにはAjaxでこのWeb APIにアクセスして取得した結果を画面に表示する処理を行う以下のオブジェクトが格納されています。JSONUpickleというScala/Scala.js両対応のJSONライブラリを使用してでシリアライズしています。

package client

import scala.scalajs.js.JSApp
import org.scalajs.dom
import org.scalajs.jquery.jQuery
import dom.ext.Ajax
import scala.concurrent.ExecutionContext.Implicits.global
import model.Book
import upickle._

object SampleApp extends JSApp {
  def main(): Unit = {
    Ajax.post("/books").foreach{ xhr =>
      val books = read[Seq[Book]](xhr.responseText)
      val ul = jQuery("ul#books")
      books.foreach { book =>
        ul.append(s"<li>${book.title} - ${book.author.mkString(", ")}</li>")
      }
    }
  }
}

serverプロジェクトにはこの処理を呼び出すために以下のHTML(正確にはTwirlテンプレート)が格納されています。

@()
@main {
  <script src="/assets/js/client-fastopt.js"></script>
  <script type="text/javascript">
    client.SampleApp().main();
  </script>
  <ul id="books">
  </ul>
}

さて、このプロジェクトですが、実際に実行するには以下の手順が必要になります。

  1. commonパッケージをコンパイルしてScala用のクラスファイルとScala.js用のJavaScriptコードを生成
  2. clientパッケージをコンパイルしてJavaScriptファイルを生成
  3. serverパッケージを普通にコンパイルし、Scala.jsで生成されたJavaScriptをプロジェクト内にコピー

今回のプロジェクトでは、このあたりをScala.jsが提供しているプラグインや、sbtの機能を利用してなるべく簡単にできるようにしてありますので、sbtの設定ファイルを参照していただければと思います。

このようにScala.jsを使用することでサーバサイドとクライアントサイドでユーティリティやAjaxでやりとりするケースクラス等を共有でき、さらにタイプセーフに記述できます。AltJSの中にもTypeScriptなど型安全性を提供してくれるものはありますが、それらと比べるとサーバサイドと言語を統一できるというメリットがあります。

一見キワモノと思われがちなScala.jsですが、AngularJSやReactなどと連携するためのライブラリも既に存在しますし、Scalaを使っているのであれば一度試してみてはいかがでしょうか。僕も今回作成したプロジェクトをベースにいろいろいじってみようと思います。