Embedded ocaml templates

I am very happy to announce the release of ocaml-embedded-templates.

This is a tool similar to camlmix, but camlmix was not updated for 7 years, and there is no easy way to handle a lot of templates (my command takes a directory as an argument and generate an ocaml module by going through the directory recursively)
I also choosed to use a syntax similar to EJS, and there is a ppx for inline EML.

You can check it out here : https://github.com/EmileTrotignon/embedded_ocaml_templates

Here is a more extensive exemple of what can be done with this : https://github.com/EmileTrotignon/resume_of_ocaml (This project generate my resume/website in both latex and html).

This is my first opam package : feedback is very much welcome.

3 Likes

This looks very interesting, I’ll try it out. I’m writing a web framework and need a good view compiler. Currently I’m using functions as views, e.g.

let view_article id name p =
  p {|<article id="|};
  p id;
  p {|"><h2>|};
  p name;
  p {|</h2></article>|}

Obviously, this is sub-optimal.

My question is, how close is ocaml-embedded-templates output to my view functions? I.e., will a template like:

<%# id name %>
<article id="<%-id%>"><h2><%-name%></h2></article>

…produce a function like mine?

I will not really do that.
Your function/view is of type string -> string -> (string -> unit) -> unit.
The function generated by EML will be of type string -> string -> string.

However I see the benefits of your approach, and I think it might be a good idea to add this, maybe as an option to EML, or maybe p could be an optionnal parameter to the function. It would be really easy to implement and provide a lot of benefits (less allocations I guess, depending on p's value).
Dont you think having type (string -> unit) -> string -> string -> unit would be better though ? This would allow to curry the template by the output function you like.

Tell me what you think :slight_smile:

Exactly, the idea is to avoid allocations, e.g. imagine that p is print_string. In my web framework I have an API for responding with a view, Response.of_view (view_article id name). Keeping the p as the final parameter allows me to partially apply all the other parameters of the view and always pass a ((string -> unit) -> unit) to Response.of_view.

To be honest, this approach is not completely my idea, I saw ECaml doing something very close, but didn’t use ECaml itself because it is GPL licensed (although that’s probably not a great reason).

EDIT: but I will play around with your repo, see how it works. Thanks for publishing!

What about

open Tyxml
let%html view_article id name p =
   "<article id="id"><h2>"name"</h2></article>"

? :slight_smile:
You can even use Reason’s JSX if that’s your thing.

3 Likes

Oh Tyxml is quite nice, I did not knew it could do that.

I have pushed a commit that allow you to do exactly what you wanted, I also modified the exemple to use the new option, tell me if you think it’s useful.

For now I want an unopinionated templating language that can output anything from HTML to JSON to CSV, etc., but Tyxml is a very cool technology, I will take another look at it.

Cool! Will check it out. Thank you!

How can I test this in the ocaml repl or utop? I’ve tried:

# #require "tyxml-jsx";;
# let%html view_article id name p =
   "<article id="id"><h2>"name"</h2></article>";;
Error: Uninterpreted extension 'html'.
# #ppx "/Users/boris/.opam/ocsi/lib/tyxml-jsx/./ppx.exe";;
# let%html view_article id name p =
   "<article id="id"><h2>"name"</h2></article>";;[%%html                                                                
  let view_article id name p =
    "<article id=" id "><h2>" name "</h2></article>"]
File "/var/folders/kl/7z2_bknj4ynch1dlb6d8w5jm0000gn/T/camlppx94864b", line 1:
Error: I can't decide whether /var/folders/kl/7z2_bknj4ynch1dlb6d8w5jm0# let%html view_article id name p =
   "<article id="id"><h2>"name"</h2></article>";;
Error: Error while running external preprocessor

Is this syntax from a different package?

Yes its from another package. The ppx rewriter for embedded ocaml templates uses %eml.
Apart from that, I have no idea how to load a ppx in the REPL.

No need to mess with utop’s #ppx directives, it is set up automatically when you require the ppx library (but fwiw, you were trying to wire in the jsx ppx, which is not what Drup’s example is using).

─( 08:54:06 )─< command 0 >───────────────────────────────────────────────────────────────{ counter: 0 }─
utop # #require "tyxml";;
─( 08:54:06 )─< command 1 >───────────────────────────────────────────────────────────────{ counter: 0 }─
utop # #require "tyxml-ppx";;
─( 08:54:12 )─< command 2 >───────────────────────────────────────────────────────────────{ counter: 0 }─
utop # open Tyxml;;
─( 08:54:16 )─< command 3 >───────────────────────────────────────────────────────────────{ counter: 0 }─
utop # let%html view_article id name p =
   "<article id="id"><h2>"name"</h2></article>";;
val view_article :
  string ->
  [< Html_types.h2_content_fun ] Tyxml_html.elt list ->
  'a -> [> Html_types.article ] Tyxml_html.elt = <fun>
2 Likes