Web開発とJavaScriptは切っても切り離せません。サーバサイドにどのような言語を使っていたとしてもJavaScriptとはよろしくお付き合いする必要があります。しかし世の中にはJavaScriptの代替となるAltJSなるものがあります。別の言語で記述したプログラムをJavaScriptにトランスレートしてくれるというものです。
AltJSには様々なものがありますが、Scalaを使っているからにはScala.jsしかありません。というわけで、Scala.jsをScalatraと組み合わせて使う場合のサンプルプロジェクトを作成してみました。
このプロジェクトは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にアクセスして取得した結果を画面に表示する処理を行う以下のオブジェクトが格納されています。JSONはUpickleという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> }
さて、このプロジェクトですが、実際に実行するには以下の手順が必要になります。
- commonパッケージをコンパイルしてScala用のクラスファイルとScala.js用のJavaScriptコードを生成
- clientパッケージをコンパイルしてJavaScriptファイルを生成
- serverパッケージを普通にコンパイルし、Scala.jsで生成されたJavaScriptをプロジェクト内にコピー
今回のプロジェクトでは、このあたりをScala.jsが提供しているプラグインや、sbtの機能を利用してなるべく簡単にできるようにしてありますので、sbtの設定ファイルを参照していただければと思います。
このようにScala.jsを使用することでサーバサイドとクライアントサイドでユーティリティやAjaxでやりとりするケースクラス等を共有でき、さらにタイプセーフに記述できます。AltJSの中にもTypeScriptなど型安全性を提供してくれるものはありますが、それらと比べるとサーバサイドと言語を統一できるというメリットがあります。
一見キワモノと思われがちなScala.jsですが、AngularJSやReactなどと連携するためのライブラリも既に存在しますし、Scalaを使っているのであれば一度試してみてはいかがでしょうか。僕も今回作成したプロジェクトをベースにいろいろいじってみようと思います。