Play FrameworkによるScalajsの続き

# play + Scala-bundlerの利用
[以前のエントリ]()でScala.jsとPlayを使った、サーバ・クライアントの例を書きましたが、Scalajs-bundlerを使った際にいろいろと苦労したのでメモします。この辺はきちんとドキュメントを読めば書いてあるのですが、なかなか分かりにくいのです。

## 準備
例によってテンプレートよりプロジェクトを作成します。

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

`plugins.sbt`にScalajs-bundlerのプラグインを追記します。一般のときとインポートするものが違うので注意です。依存性の関係から、バージョンは0.6系を使います。`project/build.properties`を適切なバージョン（例えば`sbt.version=1.2.8`)にするのを忘れてはいけません！

```plugins.sbt
// Comment to get more information during initialization
logLevel := Level.Warn

// Resolvers
resolvers ++= Seq("Typesafe repository" at "https://repo.typesafe.com/typesafe/releases/")

// Sbt plugins
addSbtPlugin("com.vmunier"               % "sbt-web-scalajs"           % "1.0.8-0.6")
addSbtPlugin("org.scala-js"              % "sbt-scalajs"               % "0.6.25")

// sbt-webと使う場合
addSbtPlugin("ch.epfl.scala" % "sbt-web-scalajs-bundler" % "0.14.0")

addSbtPlugin("com.typesafe.play"         % "sbt-plugin"                % "2.6.15")
addSbtPlugin("org.portable-scala"        % "sbt-scalajs-crossproject"  % "0.6.0")
addSbtPlugin("com.typesafe.sbt"          % "sbt-gzip"                  % "1.0.2")
addSbtPlugin("com.typesafe.sbt"          % "sbt-digest"                % "1.1.4")
```

`build.sbt`には、server側に`WebScalaJSBundlerPlugin`を有効にします。client側にも`ScalaJSBundlerPlugin`が必要です。ここでは、reactをインポートできるようにしています。

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

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,WebScalaJSBundlerPlugin)
  .dependsOn(sharedJvm)

lazy val client = (project in file("client"))
  .settings(commonSettings)
  .settings(
    scalaJSUseMainModuleInitializer := true,
    libraryDependencies ++= Seq(
      "org.scala-js" %%% "scalajs-dom" % "0.9.6",
      "com.github.japgolly.scalajs-react" %%% "core" % "1.3.1"
    ),

    npmDependencies in Compile ++= Seq(
      "react" -> "16.5.1",
      "react-dom" -> "16.5.1"
    )
  )
  .enablePlugins(ScalaJSWeb,ScalaJSPlugin,ScalaJSBundlerPlugin)
  .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 := "com.example"
)
// loads the server project at sbt startup
onLoad in Global := (onLoad in Global).value andThen { s: State => "project server" :: s }
```

## トランスパイル済みのJavascriptの参照
Scala.jsでは、`fastOptJS`などのコマンドを使って出力したJavascriptをHTML内にscriptタグで参照しますが、Playを使った場合は、Twirlと[Scalajs-Scripts](https://github.com/vmunier/scalajs-scripts)で動的に参照を記載します。ここで、前に述べた`WebScalaJSBundlerPlugin`を有効にしていないと、webpackでバンドルされたJavascriptを参照できずに失敗します。

サンプルコードのままで、`index.scala.html`を以下のように変更します。@scalajs.html.scriptsというのがScalajs-Scriptsの文法で、最初の引数が、クライアントで出力されるJavascriptの接頭語の文字になります。いずれにせよ、これによってClient側で記載したMain関数部分がインポートされて、ブラウザ表示の際に自動起動されるようになります。

```server/app/views/index.scala.html
@(message: String)

@main("Play with Scala.js") {
<div id="hello"></div>
@scalajs.html.scripts(
    "client",
    name => routes.Assets.versioned(name).toString,
    name => getClass.getResource(s"/public/$name") != null)
}
```

## Client側の実装
一番簡単なReactのコードを実装してみます。`scalaJSUseMainModuleInitializer := true`としているので、このコードが自動起動されます。

```client/src/main/scala/com/example/ScalaJSExample.scala
package com.example

import org.scalajs.dom
import dom.document
import japgolly.scalajs.react._
import japgolly.scalajs.react.vdom.html_<^._

object ScalaJSExample {

  def main(args: Array[String]): Unit = {
    val target =  document.getElementById("hello")
    val Hello =
      ScalaComponent.builder[String]("Hello")
        .render_P(name => <.div("Hello there ", name))
        .build

    Hello("everyone").renderIntoDOM(target)
  }
}
```

`sbt run`でPlayを起動します。`http:localhost:9000`にアクセスすると`Hello there everyone`と表示されていると思います。うん、非常につまらないサンプルですね。

出力されたHTMLに目を向けてみると、以下のように出力されていました。これがBundle済みのソースコードを参照しているというのが、いまだにピンときません。

```
<script src="/versionedAssets/client-fastopt-bundle.js" type="text/javascript"></script>
```


# 参考
- [Scalajs-bundler](https://github.com/scalacenter/scalajs-bundler)
- [Scalajs Scripts](https://github.com/vmunier/scalajs-scripts)