servant-stache を作りました

[servant-stache](https://hackage.haskell.org/package/servant-stache) は見る人が見ればわかるように、HaskellのWebフレームワーク[servant](http://hackage.haskell.org/package/servant)のContent-Typeとして、同じくHaskellのMustacheテンプレートライブラリである[stache](https://hackage.haskell.org/package/stache)でコンパイルしたファイルを`HTML`として利用できるようにするためのライブラリです。

要するにservant-stacheを利用すると以下のようなコードが書けるようになります。

```hs
type API = Get '[HTML "user"] User
      :<|> "style.css" :> Get '[Template CSS "style"] CSSData
```

上記のコードでは、例えば`/`でレンダリングされるHTMLはテンプレート`user.mustache`に`User`の値を適用して生成されます。

servant-stacheを作ろうと思ったきっかけは

<blockquote class="twitter-tweet" data-lang="ja"><p lang="ja" dir="ltr">2年前に書いた「【型レベルWeb DSL】 Servantの紹介」のサンプルコードが古くなって動かなくなってたので最新版に対応しました〜🙌<a href="https://t.co/cpJDWl8dsa">https://t.co/cpJDWl8dsa</a></p>&mdash; lotz△ (@lotz84_) <a href="https://twitter.com/lotz84_/status/1023043692272082945?ref_src=twsrc%5Etfw">2018年7月28日</a></blockquote>
<script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>

2年前に書いた記事をアップデートした時に、例として利用していた[servant-ede](http://hackage.haskell.org/package/servant-ede)の開発が止まってしまっていたことです。そもそもservant-edeで利用されているHaskellのテンプレートライブラリ[`ede`](http://hackage.haskell.org/package/ede)の開発が止まってしまっているのでservant-edeの開発が止まってしまうのも仕方がないことなのですが、テンプレートで書いたHTMLを簡単にservantでサーブできるというservant-edeの機能は捨てがたいものだったので、新しくテンプレートライブラリにstacheを採用した同様のライブラリを作ることにしました。stacheを採用した理由はmustacheという標準的なテンプレートであるということと、[stackbuilders](https://www.stackbuilders.com/)という信頼できる企業がメンテナンスしているということです（後者の理由によりstacheの軽量版ライブラリである[`microstache`](https://hackage.haskell.org/package/microstache)の採用は見送りました）。

servant-stache の使い方
----------------------

例えば以下のような `index.mustache` というテンプレートを `templates` フォルダの中に作成します。

```mustache:templates/index.html
<!DOCTYPE html>
<html>
  <head><title>User</title></head>
  <body>
    <ul>
      <li><strong>Name:</strong> {{ name }}</li>
      <li><strong>Age:</strong> {{ age }}</li>
    </ul>
  </body>
</html>
```

このテンプレートを利用したWebページを配信するServantアプリを作ってみましょう。

```hs
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE DeriveGeneric #-}
{-# LANGUAGE DeriveAnyClass #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE TypeOperators #-}

import GHC.Generics

import Data.Aeson
import qualified Network.Wai.Handler.Warp as Warp
import Servant
import Servant.Mustache


data User = User
  { name :: String
  , age :: Int
  } deriving (Generic, ToJSON)


type API = Get '[HTML "index"] User

api :: Proxy API
api = Proxy

server :: Server API
server = pure (User "lotz" 28)

main :: IO ()
main = do
  loadTemplates "./templates"
  putStrLn "Listening on port 8080"
  Warp.run 8080 $ serve api server
```

実行すれば以下のように見えるはずです。

<img src="https://qiita-image-store.s3.amazonaws.com/0/17134/e0c71e43-d7b2-15b9-8d3b-fcc04199c7d8.png" width=400>

ポイントは以下の２つです。

* Servantアプリを実行する前に `loadTemplates` でテンプレートを読み込む
* サーバーとしてテンプレートのパラメータをJSONとして生成するような(ToJSONのインスタンスになっている)型の値を返す関数を実装する

これだけ書けばMustacheのテンプレートで作成したHTMLを簡単に配信することができます。

[`HTML`](https://hackage.haskell.org/package/servant-stache-0.1.0.0/docs/Servant-Mustache.html#t:HTML)は

* Content-Type として `text/html;charset=utf-8` を使用する
* パラメータの値は全て[サニタイズ](https://www.weblio.jp/content/%E3%82%B5%E3%83%8B%E3%82%BF%E3%82%A4%E3%82%BA)される

という特徴を持ちます。

逆に上記の処理を行わずに、素のパラメータでレンダリングしたテンプレートを任意のContent-Typeで返したい時は[`Template`](https://hackage.haskell.org/package/servant-stache-0.1.0.0/docs/Servant-Mustache.html#t:Template)型を使用してください

servant-stache の仕組み
-----------------------

`loadTemplates`を行った際に以下のように宣言されているグローバルな変数にテンプレートを保存しています。

```hs
{-# NOINLINE __template_store #-}
__template_store :: MVar Stache.Template
__template_store = unsafePerformIO newEmptyMVar
```

そしてコンテンツをレンダリングする時にテンプレートを取り出して使用します。

```hs
instance (KnownSymbol file, Accept ct, ToJSON a) => MimeRender (Template ct file) a where
  mimeRender _ val =
    encodeUtf8 $ Stache.renderMustache template (toJSON val)
    where template = tstore { Stache.templateActual = Stache.PName (pack filename) }
          filename = symbolVal (Proxy :: Proxy file)
          tstore = unsafePerformIO (readMVar __template_store)
```

`HTML`をレンダリングする際も上述した前処理を行った後は`Template`のレンダリング処理を利用しています。

おわりに
-------
今後もメンテナンスは続けていくつもりなのでGitHubレポジトリにIssueやPRを投げていただければ対応します。個人的に作ろうと思ってるWebベースのプロジェクトがあるので、次はそっちでドッグフーディングしてみたいと思います。
<https://github.com/lotz84/servant-stache>