完成形
やりたいことは、下記のような形のプロジェクトでsbt(activator)のrunコマンドだけでScalaとJSXのコンパイルとサーバ起動が出来るようになること。
手順
reactjsプラグイン
まず、sbt上でJSXをコンパイルできるようになるプラグインを使います。
sbt-reactjs
plugins.sbtに下記を追記します。
addSbtPlugin("com.github.ddispaltro" % "sbt-reactjs" % "0.6.8")
これでsbt上でJSXのコンパイルが出来るようになります。
ちなみにデフォルトではnode.jsを使ってコンパイルしようとするようですが、入っていなくてもjs-engineのTriremeを使用するのでひとまず気にしなくて良さそうです。
プロジェクト設定
ちょっと大きなプロダクトになるとマルチプロジェクト構成にする場合がほとんどだと思います。
今回はPlayの標準の規約ではなくて、独自のパッケージのルールでソースコードを配置したいと思います。
organization := "my.company"
name := "PlayReact"
version := "1.0"
lazy val commonSettings = Seq(scalaVersion := "2.11.8")
lazy val web = (project in file("web"))
.settings(commonSettings: _*)
.enablePlugins(PlayScala)
.disablePlugins(PlayLayoutPlugin)
.settings(
sourceDirectories in (Compile, TwirlKeys.compileTemplates) := Seq((scalaSource in Compile).value / "my" / "company" / "system"),
sourceDirectories in (Test, TwirlKeys.compileTemplates) := Seq((scalaSource in Test).value / "my" / "company" / "system"),
sourceDirectory in Assets := (scalaSource in Compile).value / "my" / "company" / "system" / "assets",
sourceDirectory in TestAssets := (scalaSource in Test).value / "my" / "company" / "system" / "assets",
resourceDirectory in Assets := baseDirectory.value / "public",
ReactJsKeys.harmony := true,
ReactJsKeys.es6module := true,
ReactJsKeys.stripTypes := true
)
.dependsOn(common)
lazy val common = (project in file("common"))
.settings(commonSettings: _*)
最終的に上のような感じにしました。
.disablePlugins(PlayLayoutPlugin)
でPlay標準のレイアウトを解除します。(app下にソース置かなきゃいけないやつ)
また、Twirlのテンプレートの配置位置も調整しています。
.jsx
ファイルはAssets扱いなのでsourceDirectory in Assets
にて配置位置の調整を行います。
PlayではSbtWebを使用しているので、sbt-reactjsを使う際に改めてプラグイン有効化を行う必要はありません。
JSXのコンパイルオプションはReactJsKeys
で指定できます。
ルーティング設定
GET / my.company.system.controllers.Application.index
GET /api my.company.system.controllers.Application.api
GET /assets/*file controllers.Assets.at(path="/public", file)
FQCNでクラス名を指定しなければいけないところ以外は特に変わったことはありません。
コントローラーとJSXとビュー
パッケージ以外はいつも通りのコントローラーです。
package my.company.system.controllers
import my.company.common.Resource
import play.api.libs.json.Json
import play.api.mvc._
class Application extends Controller {
def index = Action {
Ok(views.html.index("Your new application is ready."))
}
def api = Action {
Ok(Json.toJson(new Resource().data)) // Seq("hoge1", "hoge2", "hoge3")
}
}
JSXの配置場所はbuild.sbtで指定したmy.company.system.assets
パッケージ下のjavascript
パッケージです。
コードの中身は何の変哲もないJSXファイルです。
const DataList = React.createClass({
getInitialState: () => {
return {data: []};
},
componentDidMount: function() {
const self = this;
superagent
.get("/api")
.end(function(err, res){
self.setState({data: res.body});
});
},
render: function() {
const toListItem = x => <li>{x}</li>;
return (
<ul>
{this.state.data.map(toListItem)}
</ul>
);
}
});
ReactDOM.render(
<DataList />,
document.getElementById("main")
);
テンプレートは以下のようになります。
JSXでコンパイルされた物は@routes.Assets.at("javascripts/ファイル名.js")
でパスが取得できます。
@(title: String)
<!DOCTYPE html>
<html>
<head>
<title>@title</title>
<script src="//fb.me/react-with-addons-0.14.8.min.js"></script>
<script src="//fb.me/react-dom-0.14.8.min.js"></script>
<script src="//cdnjs.cloudflare.com/ajax/libs/superagent/1.2.0/superagent.min.js"></script>
</head>
<body>
Hello World!!
<div id="main"></div>
<script src="@routes.Assets.at("javascripts/app.js")"></script>
</body>
</html>
実行結果
http://localhost:9000/
にアクセスして得られるレスポンス
Hello World!!
- hoge1
- hoge2
- hoge3
終わり
javascript側はもっといろいろ考えることがありそうですが、ひとまずsbtだけでScalaとJSXのコンパイルができるようになりました。
実際にやってみるとsbtだけでフロントエンドとバックエンドの両方がコンパイル&実行できてとても楽です。