もう少しScalaのテンプレートエンジンを、と思い、今度はTwirlを触ってみました。
Twirl
https://github.com/playframework/twirl
Play Frameworkで使われているテンプレートエンジンだそうですね、Play自体使ったことないですけど…。
以前はPlayから完全に独立したテンプレートエンジンとして存在していたみたいですが、Playの一部に戻ったみたいですね。
※この経緯については、xuweiさんからコメントで補足をいただきました。ありがとうございます
旧リポジトリはこちら。
Twirl
https://github.com/spray/twirl
Playに戻っても、単独で使えることには変わりはなさそうです。
ドキュメントは、Playのものを見ることになりそうです。
The template engine
https://www.playframework.com/documentation/2.0/ScalaTemplates
タイプセーフなテンプレートエンジン、ということだそうな。ということは、コンパイルするんですな。
準備
Twirlを使用するには、sbtプラグインの設定を行うようにすればよいみたいです。
project/plugins.sbt
addSbtPlugin("com.typesafe.sbt" % "sbt-twirl" % "1.0.2")
あと、build.sbtには以下の1行を加えます。
lazy val root = (project in file(".")).enablePlugins(SbtTwirl)
Twirlを使うだけなら、依存関係の追加自体は不要です。
サンプルは?
新しいリポジトリにはそれっぽいディレクトリがなかったので、旧リポジトリをちょっと漁ってみました。
テンプレートのサンプル
https://github.com/spray/twirl/blob/master/example/src/main/twirl/org/example/hello.scala.txt
https://github.com/spray/twirl/blob/master/example/src/main/twirl/org/example/hello.scala.html
https://github.com/spray/twirl/blob/master/example/src/main/twirl/org/example/hello.scala.xml
テンプレートファイルは、「src/main/twirl」というディレクトリの中に配置するようです。
ファイル名は、[テンプレート名].scala.[拡張子]となり、拡張子で挙動が変わるっぽい?
Scalaコードのサンプル
https://github.com/spray/twirl/blob/master/example/src/main/scala/org/example/Main.scala
予備知識が全然なかったので、テンプレートの評価ってどうやるんだろう?と思って上記のコードを見て驚きました。ただのメソッド呼び出しですね。ということは、ホントにScalaコードとしてコンパイルされるんですね。
コンパイル、かぁ…。
ファイルが「src/main/twirl/org/example/hello.scala.txt」だったら、呼び出しは「org.example.txt」までがパッケージ名な感じで、「hello」で呼べますよっと。サンプルコードはFQCNで書いていますが、同じパッケージにいるなら普通に省略可能でしたね。
println("A rendered Twirl TXT template:") println(org.example.txt.hello("Alice", 16))
あと、「src/main/twirl」以下のディレクトリ名は、そのままパッケージ名の一部になる感じですね。
ちょっと試してみる
では、Twirlを自分でも試してみましょう。こんなコードを用意。
src/main/scala/org/littlewings/twirl/TwirlExample.scala
package org.littlewings.twirl object TwirlExample { def main(args: Array[String]): Unit = { // ここにコードを書く } } case class Person(name: String, age: Int)
何気に、Casee Classも。
では、テンプレートを書いてみます。
src/main/twirl/hello.scala.txt
@(name: String)
Hello @name!!
まずは、「src/main/twirl」ディレクトリ直下に作成。
呼び出し側のコード
println(txt.hello("<Twirl>"))
と実行結果。
Hello <Twirl>!!
動きましたね!けっこう簡単。
実行は、普通に「run」すればOKです。
> run
テンプレートは一緒にコンパイルされます。
コンパイルされるので、こういう風に変数名を間違ったりすると
@(name: String)
Hello @nam!!
コンパイルエラーになります。
[info] Compiling 1 Scala source to /xxxxx/target/scala-2.11/classes... [error] /xxxxx/src/main/twirl/hello.scala.txt:2: not found: value nam [error] Hello @nam!! [error] ^ [error] one error found
ちなみに、今回テンプレートの配置ディレクトリを呼び出し元と同じパッケージにしていないのですが、上記コードは、テンプレート側にパッケージ名と同じディレクトリを作成してテンプレートを配置してしまうと、そのままだと呼び出せなくなります。
その場合は、「_root_」付ければなんとかなりますが…。合わせるか無視するか、どちらかにしましょうねということでいいのかな。
今度は、拡張子を.htmlに。中身はHTMLではないですけれど。
src/main/twirl/hello.scala.html
@(name: String)
Hello @name!!
Raw: @Html(name)
呼び出し側。
println(html.hello("<Twirl>"))
実行結果。
Hello <Twirl>!! Raw: <Twirl>
拡張子を.htmllにした場合は、デフォルトでエスケープされるみたいですね。解除するには、@Htmlを使用する、と。
繰り返しの例。
src/main/twirl/people.scala.txt
@import org.littlewings.twirl.Person @(people: List[Person]) ひとびと @for(p <- people) { 名前:@p.name 年齢:@p.age }
しれっとimport入りです。
あと、@forと(の間にスペース入れると、コンパイルエラーになるんですね。
呼び出し側。
val people = List(Person("フグ田サザエ", 24), Person("磯野カツオ", 11), Person("磯野ワカメ", 9)) println(txt.people(people))
実行結果。
ひとびと 名前:フグ田サザエ 年齢:24 名前:磯野カツオ 年齢:11 名前:磯野ワカメ 年齢:9
テンプレートをサブディレクトリに配置して、ifも使ってみます。
src/main/twirl/template/people/list.scala.txt
@import org.littlewings.twirl.Person @(people: List[Person]) ひとびと @for(p <- people) { 名前:@p.name @if(p.age < 20) { 年齢:@p.age } }
呼び出し側。
val people = List(Person("フグ田サザエ", 24), Person("磯野カツオ", 11), Person("磯野ワカメ", 9)) println(template.people.txt.list(people))
結果。
ひとびと 名前:フグ田サザエ 名前:磯野カツオ 年齢:11 名前:磯野ワカメ 年齢:9
テンプレートのコンパイルがあるので、その時間がかかるところはちょっと気になるところですが、Scalateと違って実行時にファイルを作成しないところは良いですね。
必要に応じて、選択肢のひとつとして考えておきましょう。