Scalaで何かしらのテンプレートエンジンを使ってみようと思い、Play Frameworkに組み込まれているTwirlを使ってみました。
目次
前提
Play Framework組み込みということで、Playのプロジェクトであれば依存関係の追加などはせずに使用できます。
- Play Frameworkのsbtプロジェクトを作成済みである
テンプレートファイルの配置場所
テンプレートファイルは sourceDirectories / TwirlKeys.compileTemplates で指定したディレクトリ配下に配置する必要があります。sbtのinspectコマンドでこの設定を調べたところPlayプロジェクトでは"/app"なようです。(PlayLayoutPluginによって指定されている)
https://github.com/playframework/playframework/blob/53e55f017f508f0dcdf2a70327bd73153fe87e5e/dev-mode/sbt-plugin/src/main/scala/play/sbt/PlayLayoutPlugin.scala
命名規則
テンプレートは簡単な命名規則に従って、ただの Scala の関数としてコンパイルされます。views/Application/index.scala.html というテンプレートファイルを作成すると、コンパイルにより views.html.Application.index という apply() メソッドを持つクラスが生成されます。
Playのドキュメントを見ると上記のように簡単に書かれているのですが、 https://github.com/playframework/twirl/blob/main/compiler/src/main/scala/play/twirl/compiler/TwirlCompiler.scala あたりのコードを読むとそれほど単純でもないようです。
フォルダ階層にtopDirectory="views"が含まれているか否かによって生成されるパッケージが異なるようで、以下のようになります。
- "viewsが含まれる" → <viewsより上のフォルダ階層>.views.<テンプレート拡張子>.<viewsより下のフォルダ階層>.<テンプレートファイル名>
- "viewsが含まれない" → <フォルダ階層>.<テンプレート拡張子>.<テンプレートファイル名>
例
- views/Application/index.scala.html → views.html.Application.index
- shop/views/Application/index.scala.html → shop.views.html.Application.index
- mypage/top.scala.html → mypage.html.top
- user/mypage/top.scala.html → user.mypage.html.top
テンプレート作成
次のようなモデルを用意しておきます。
package models case class SampleModel(name: String, age: Integer, country: String)
views/Test.scala.htmlにテンプレートを作成します。
@import models._ @(user: SampleModel) <!DOCTYPE html> <html lang="ja"> <head> <meta charset="utf-8" /> <title>User Profile | example.com</title> <style> html { background-color: #fdfdfd; font-size: 100%; } body { margin: 0 auto; width: 60vw; } h1 { font-size: 2.5rem; margin: 1.2rem 0; } p { margin: 0.9rem 0; font-size: 1rem; } .data-box { background-color: #efefef; padding: 0 1.5rem; font-size: 90%; } .data-line { display: flex; padding: 1rem 0; border-bottom: 1px solid #cccccc; } .data-line:last-child { border-bottom: none; } .data-item-1-3 { flex: 0 1 33.3%; } .data-item-2-3 { flex: 0 1 66.6%; } .item-key { font-weight: bold; } .item-value { } </style> </head> <body> <main> <h1>User Profile</h1> <p>Profile:</p> <div class="data-box"> <div class="data-line"> <div class="data-item-1-3 item-key">Name</div> <div class="data-item-2-3 item-value">@user.name</div> </div> <div class="data-line"> <div class="data-item-1-3 item-key">Age</div> <div class="data-item-2-3 item-value">@user.age</div> </div> <div class="data-line"> <div class="data-item-1-3 item-key">Country</div> <div class="data-item-2-3 item-value">@user.country</div> </div> </div> </main> </body> </html>
コントローラの作成
今回はただモデルを生成してビューに渡すだけの簡易コントローラを作成します。
package controllers import models.SampleModel import play.api._ import play.api.mvc._ import javax.inject.Inject class IndexController @Inject()(cc: ControllerComponents) extends AbstractController(cc) { def index(): Action[AnyContent] = Action { implicit request => Ok(views.html.Test(SampleModel("Mano Sakuragi", 16, "Japan"))) } }
参考文献
Scala Templates - 2.3.x
https://www.playframework.com/documentation/ja/2.3.x/ScalaTemplates
Java Templates - 2.8.x
https://www.playframework.com/documentation/2.8.x/JavaTemplates
sbt Reference Manual — Inspect the build
https://www.scala-sbt.org/1.x/docs/Howto-Inspect-the-Build.html