9
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

servant-stache を作りました

Posted at

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

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

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

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

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

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

servant-stache の使い方

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

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アプリを作ってみましょう。

{-# 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

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

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

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

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

HTML

  • Content-Type として text/html;charset=utf-8 を使用する
  • パラメータの値は全てサニタイズされる

という特徴を持ちます。

逆に上記の処理を行わずに、素のパラメータでレンダリングしたテンプレートを任意のContent-Typeで返したい時はTemplate型を使用してください

servant-stache の仕組み

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

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

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

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

9
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
9
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?