Play에서 맞춤설정된 프로젝트에서 React.js 사용

완성형



하고 싶은 것은, 아래와 같은 형태의 프로젝트에서 sbt(activator)의 run 커멘드만으로 Scala와 JSX의 컴파일과 서버 기동을 할 수 있게 되는 것.



절차



reactjs 플러그인



먼저 sbt에서 JSX를 컴파일 할 수있는 플러그인을 사용합니다.
sbt-reactjs

plugins.sbt에 다음을 추가합니다.

plugins.sbt
addSbtPlugin("com.github.ddispaltro" % "sbt-reactjs" % "0.6.8")

이제 sbt에서 JSX를 컴파일 할 수 있습니다.
덧붙여서 디폴트에서는 node.js를 사용해 컴파일하려고 하는 것 같습니다만, 들어가 있지 않아도 js-engine 의 Trireme를 사용하므로 일단 신경쓰지 않아도 좋을 것 같습니다.

프로젝트 설정



조금 큰 제품이 되면 멀티 프로젝트 구성으로 하는 경우가 대부분이라고 생각합니다.
이번에는 Play의 표준 규약이 아니라 독자적인 패키지의 규칙으로 소스 코드를 배치하고 싶습니다.

build.sbt
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로 지정할 수 있습니다.

라우팅 설정



routes
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와 뷰



패키지 이외는 항상 그대로의 컨트롤러입니다.

Application.scala
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 파일입니다.

app.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")로 패스를 얻을 수 있습니다.

index.scala.hml
@(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만으로 프런트 엔드와 백 엔드의 양쪽 모두가 컴파일 & 실행할 수 있어 매우 편합니다.

    좋은 웹페이지 즐겨찾기