プログラマブル深海魚

目立たないけど華やかに

Play Framework上でTwirlを使う

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