とある地味なブログ

プログラミングとお絵かきに関する雑記。

Play FrameworkでScalateを使うときにハマったこと

sbt-scalate-precompiler と実行時コンパイルを共存させる

GitHub - scalate/sbt-scalate-precompiler: Scalate Templates Precompiler

これ、リリース時にプリコンパイルしてくれてとても便利なんだけど、 Playと一緒に使うと難点があって、

ホットデプロイモードでリロードするたび2回コンパイルが走ってしまう

んです。 ただでさえ遅いコンパイルがさらに遅く。

そこで、開発時には実行時コンパイル、リリース時にはプリコンパイル、という風に設定を分けてみました。 実行時コンパイルであれば、ページにアクセスしたときコンパイルするだけになり、多少早くなります。

def settings(prefix: String) = ScalatePlugin.scalateSettings ++ Seq(
    ScalateKeys.scalateOverwrite := false,
    ScalateKeys.scalateTemplateConfig in Compile <<= (baseDirectory in Compile) { base =>
      Seq(
        ScalatePlugin.TemplateConfig(
          base / "app" / "some" / "packages" / prefix / "views",
          Seq(),
          Some(prefix) // パッケージプレフィックスを使う!!
        )
      )
    }
  )

lazy val compileCopyTask = taskKey[Unit](s"Copy ssp.")
def devSettings(prefix: String) = Seq(
  compileCopyTask := {
    println(s"Start copying ssp")
    val mainVersion = scalaVersion.value.split("""\.""").take(2).mkString(".")
    val to = target.value / ("scala-" + mainVersion) / "classes" / prefix
    to.mkdirs()
    val from = baseDirectory.value / "app" / "some" / "packages" / prefix / "views"
    IO.copyDirectory(from,to)
    println(s"$from  -> $to...done.")
  },
  compile in Compile <<= (compile in Compile) dependsOn compileCopyTask
)

val isDev = sys.env.getOrElse("MY_PLAY_ENV", "") == "dev"

lazy val root = (project in file(".")).enablePlugins(PlayScala).settings(if (isDev) devSettings else settings)

こんな感じで、build.sbtを書きます。

環境変数MY_PLAY_ENVによって、コンパイル時の動作が変わります。

settingsはsbt-scalate-precompilerのドキュメントの通りです。

devSettingsは、targetディレクトリ配下にテンプレートファイルを配置しています。

パッケージプレフィックスを使わなければ、

unmanagedResourceDirectories in Compile += baseDirectory.value / "app" / "some" / "packages" / "views"

の一行で済むのですが、パッケージプレフィックスを使うと、その名前のティレクトリ配下に置くようなパス指定になります。

val engine = new TemplateEngine
engine.layout("/prefix/index.ssp")
                ^^^^^^ <- これ

なので、target/scala-2.11/prefix/index.sspのように配置する実装が必要になりました。

TemplateEngineインスタンスは明示的にGC対象にする

PlayとScalateの実行時コンパイルを一緒に使うにあたって、もう一つ問題がありました。

ホットデプロイモードで数回リロードすると死ぬほど重くなる

原因ははっきりしていませんが、TemplateEngineインスタンスを消さずにリロードすると重くなるようです。

なので、Playアプリケーションのシャットダウン時に、インスタンスを解放するようにしてみました。

// どこかの処理
var engine = new TemplateEngine

// PlayのGlobalクラス
object Global extends GlobalSettings {
  ...
  override def onStop(app: Application): Unit = {
    engine = null
  }
  ...
}

(Playのバージョン低いので適宜読み替えてください)

とやると、処理が劇的に重くなることがなくなりました。

原因不明なのでスマートな解ではないかと思いますが、困っていたら試してみてください。