# EEl - Embedded Erlang - template renderer (WIP)

**URL:** <https://erlangforums.com/t/eel-embedded-erlang-template-renderer-wip/1827>\
**Category:** Libraries\
**Tags:** embedded-erlang, eel\
**Created:** [September 8, 2022, 7:20am UTC](https://erlangforums.com/t/eel-embedded-erlang-template-renderer-wip/1827 "2022-09-08T07:20:19Z")\
**Posts on this page:** 16\
**Page:** 1

<div class="post-metadata">

**Author:** ![williamthome](https://erlangforums.com/user_avatar/erlangforums.com/williamthome/32/471_2.png) [@williamthome](https://erlangforums.com/u/williamthome)\
**Post date:** [September 8, 2022, 7:20am UTC](https://erlangforums.com/t/eel-embedded-erlang-template-renderer-wip/1827/1 "2022-09-08T07:20:19Z")

</div>

Hi all!

I’m working on a library like the [EEx](https://elixirschool.com/en/lessons/misc/eex) for Elixir.  
It compiles and evaluates a template, e.g:

```erlang
1> eel:eval(<<"Hello, <%= Name .%>!">>, #{'Name' => <<"World">>}).
<<"Hello, World!">>

```

Or more complex like this:  
 ![eel](https://erlangforums.com/uploads/default/original/2X/5/5e8f1aba73bcffae71e737afddaa6c052cddab4d.png)

It compiles the template to AST by `erl_parse:parse_exprs` and eval using `erl_eval:exprs`.

The lib repo:

> **[GitHub - williamthome/eel: Embedded Erlang (EEl)](https://github.com/williamthome/eel)**
>
> Embedded Erlang (EEl). Contribute to williamthome/eel development by creating an account on GitHub.

I’m also working on a library on top of it. On any news, I will post about it here.

**Notes**

- This library is a **WIP** , and ideas and help are very welcome 😃
- The syntax highlighting of the screenshot comes from a fork of the [vscode\_erlang](https://github.com/williamthome/vscode_erlang/tree/eel)

---

<div class="post-metadata">

**Author:** ![williamthome](https://erlangforums.com/user_avatar/erlangforums.com/williamthome/32/471_2.png) [@williamthome](https://erlangforums.com/u/williamthome)\
**Post date:** [September 9, 2022, 1:18am UTC](https://erlangforums.com/t/eel-embedded-erlang-template-renderer-wip/1827/2 "2022-09-09T01:18:15Z")

</div>

On top of EEl, I’m creating WErl (I don’t have planned a good name for it, but this refers to “Web Erlang”). The idea is to use WebSocket to send changes to the page and only render what changed. EEl was designed thinking on this. Under the hood it uses [morphdom](https://github.com/patrick-steele-idem/morphdom) in the front end, the same library used by Phoenix Framework, and on the server it uses [cowboy](https://github.com/ninenines/cowboy) as a dependency.  
When the socket connects, it receives the static content as a list, then in any update sent by the server, it can resolve the dynamic content changes.

Some front code:

 ![werl](https://erlangforums.com/uploads/default/original/2X/1/1defe47c6d5a3eecb405b3ec9d8622087c9b2821.jpeg)

Partial server code:

 ![werl-server](https://erlangforums.com/uploads/default/original/2X/b/b9a2aa97952e749f9c4752cb5ea51809a83606df.jpeg)

WErl in action:  
 ![werl](https://erlangforums.com/uploads/default/original/2X/9/957db2dd0138db56686963a0dc9235d84e85a6a9.gif)

This lib it’s helping me to test the EEl and maybe can have some use cases in the future.  
At the moment I’m working on a code abstraction.

---

<div class="post-metadata">

**Author:** ![starbelly](https://erlangforums.com/user_avatar/erlangforums.com/starbelly/32/209_2.png) [@starbelly](https://erlangforums.com/u/starbelly)\
**Post date:** [September 9, 2022, 2:27am UTC](https://erlangforums.com/t/eel-embedded-erlang-template-renderer-wip/1827/3 "2022-09-09T02:27:41Z")

</div>

Love this. You ended up using the name I was going to use when I mused about doing this, I suppose it’s a logical conclusion.

I think if this matured enough maybe it would be a great fit for OTP, at least I can think of lot of uses for it 🙂

---

<div class="post-metadata">

**Author:** ![williamthome](https://erlangforums.com/user_avatar/erlangforums.com/williamthome/32/471_2.png) [@williamthome](https://erlangforums.com/u/williamthome)\
**Post date:** [September 10, 2022, 12:09am UTC](https://erlangforums.com/t/eel-embedded-erlang-template-renderer-wip/1827/4 "2022-09-10T00:09:07Z")

</div>

I just published an [extension for VSCode](https://marketplace.visualstudio.com/items?itemName=williamthome.eel) that provides the syntax highlighting.

---

<div class="post-metadata">

**Author:** ![fancycade](https://erlangforums.com/user_avatar/erlangforums.com/fancycade/32/1187_2.png) [@fancycade](https://erlangforums.com/u/fancycade)\
**Post date:** [September 17, 2022, 4:54pm UTC](https://erlangforums.com/t/eel-embedded-erlang-template-renderer-wip/1827/5 "2022-09-17T16:54:30Z")

</div>

This is really great!

I’ve been concerned about how dependent Erlang community is on erlydtl, and the development of it has somewhat stagnated. Also, Erlang syntax in the template as opposed to Django templating language is much preferred.

Pardon my misunderstanding, but will it be possible to take the AST generated from the template and put it in an erlang module like the way erlydtl does it? Combined with the rebar3 erlydtl plugin this makes it easy to deploy releases.

Looking forward to the progress on this.

---

<div class="post-metadata">

**Author:** ![williamthome](https://erlangforums.com/user_avatar/erlangforums.com/williamthome/32/471_2.png) [@williamthome](https://erlangforums.com/u/williamthome)\
**Post date:** [September 17, 2022, 5:16pm UTC](https://erlangforums.com/t/eel-embedded-erlang-template-renderer-wip/1827/6 "2022-09-17T17:16:27Z")

</div>

Yes, this is in my roadmap for the eel. I think something like this is really helpful.

---

<div class="post-metadata">

**Author:** ![williamthome](https://erlangforums.com/user_avatar/erlangforums.com/williamthome/32/471_2.png) [@williamthome](https://erlangforums.com/u/williamthome)\
**Post date:** [September 24, 2022, 9:25pm UTC](https://erlangforums.com/t/eel-embedded-erlang-template-renderer-wip/1827/7 "2022-09-24T21:25:50Z")

</div>

There is a new feature. Now you can compile the template to a module.

![eel_to_module](https://erlangforums.com/uploads/default/original/2X/8/8950fbdd575ec18ffdfc232f14283f2015822440.gif)

---

<div class="post-metadata">

**Author:** ![williamthome](https://erlangforums.com/user_avatar/erlangforums.com/williamthome/32/471_2.png) [@williamthome](https://erlangforums.com/u/williamthome)\
**Post date:** [September 25, 2022, 6:47pm UTC](https://erlangforums.com/t/eel-embedded-erlang-template-renderer-wip/1827/8 "2022-09-25T18:47:34Z")

</div>

New feature available. There is a new module called `eel_template`. This module’s a `gen_server` and holds the last render information, by this, it has the capability to update only what changed.  
For example:

```nohighlight
1> {ok, Tpl} = eel:start_priv_file_template(eel, <<"greetings.html.eel">>).
{ok,<0.162.0>}

2> eel_template:render(Tpl, #{'Who' => 'Erlang', 'Msg' => <<"Hello, EEl!">>}).
{<<"<p>Me: \"Hello, Erlang!\"</p>\n<p>You: \"Hello, EEl!\"</p>">>,
#{1 => <<"Erlang">>,2 => <<"Hello, EEl!">>}}

3> eel_template:render(Tpl).
{<<"<p>Me: \"Hello, Erlang!\"</p>\n<p>You: \"Hello, EEl!\"</p>">>,
#{}}

4> eel_template:render(Tpl, #{'Msg' => <<"Hello again!">>}).
{<<"<p>Me: \"Hello, Erlang!\"</p>\n<p>You: \"Hello again!\"</p>">>,
#{2 => <<"Hello again!">>}}

```

The `v(2)` evaluates ‘Who’ and ‘Msg’ variables, `v(3)` none, and `v(4)` only the ‘Msg’.

---

<div class="post-metadata">

**Author:** ![williamthome](https://erlangforums.com/user_avatar/erlangforums.com/williamthome/32/471_2.png) [@williamthome](https://erlangforums.com/u/williamthome)\
**Post date:** [May 27, 2023, 4:24am UTC](https://erlangforums.com/t/eel-embedded-erlang-template-renderer-wip/1827/9 "2023-05-27T04:24:21Z")

</div>

Ok, I’m here again after a while to talk about this lib called [EEl](https://github.com/williamthome/eel).  
Just merged a new code, I rewrote everything from scratch.  
The idea is the same, but now it’s more simple and more versatile.  
There is now the possibility to define a custom engine using the [eel\_engine](https://github.com/williamthome/eel/blob/main/src/eel_engine.erl) behavior. The default one is the [eel\_smart\_engine](https://github.com/williamthome/eel/blob/main/src/eel_smart_engine.erl). The job of the engine is to convert the code into [AST](https://www.erlang.org/doc/apps/erts/absform.html) (Abstract Syntax Tree).

> Compare the [eel\_smart\_engine](https://github.com/williamthome/eel/blob/main/src/eel_smart_engine.erl#L56) implementation to a [simple engine](https://github.com/williamthome/eel/blob/main/src/eel_tokenizer.erl#L270) used for tests.

See the [README](https://github.com/williamthome/eel/tree/main#readme) for more details, but in a nutshell, strings and files can be compiled and evaluated to binary. For example, using a simple binary as the template:

```erlang
1> eel:eval(<<"Hello, <%= Name .%>!">>, #{'Name' => <<"World">>}).
[<<"Hello, ">>,<<"World">>,<<"!">>]

```

or compiling it to a module

```erlang
1> eel:compile_to_module(<<"Hello, <%= Name .%>!">>, foo).
{ok,foo}
2> foo:eval(#{'Name' => <<"World">>}).
[<<"Hello, ">>,<<"World">>,<<"!">>]

```

or using the [eel](https://github.com/williamthome/eel/blob/main/src/eel.erl) module functions.

Taking the module example, the is a render function that gives this result:

```erlang
3> foo:render(#{'Name' => <<"World">>}).
{[<<"Hello, ">>,<<"World">>,<<"!">>],
 #{ast =>
       [{2,
         {{1,8},
          [{call,1,
               {remote,1,{atom,1,eel_converter},{atom,1,to_binary}},
               [{'fun',1,
                    {clauses,[{clause,1,[],[],[{var,1,'Name'}]}]}}]}]}}],
   bindings => #{'Name' => <<"World">>},
   changes => [{2,<<"World">>}],
   dynamic => [{2,{{1,8},<<"World">>}}],
   static => [{1,{{1,1},<<"Hello, ">>}},{3,{{1,20},<<"!">>}}],
   vars => [{2,['Name']}]}}

```

It returns a tuple with the binary and a metadata that I called `snapshot`. See for what the snapshot is useful by looking at the [example](https://github.com/williamthome/eel/tree/main#example) in the docs, but basically, it holds information to prevent the need to recompile and gives the information to only evaluate what changed.

> Note about the key of the bindings/variables passed to render/eval. Is totally fine to write keys in `snake_case` like `#{name => <<"World">>}` instead of `#{'Name' => <<"World">>}`, or better, `#{ThisIsFine => ok}` can be write as `#{this_is_fine => ok}`.

From time to time I see people asking about Erlang for Web Development or what framework to use. I believe this lib can help in some manner.

Just a side note writing the template directly in an Erlang module, e.g.

```erlang
foo() ->
    eel:compile(<<\"
        You need to \"escape\" all \"quoted texts\" inside this binary.
        Also, you will have some indentation issues.
    \">>).

```

[This recent proposal](https://erlangforums.com/t/feature-heredocs-triple-quoted-text/2638) can help to write better multi-line texts.

Disclaimer: It is still a WIP. There are few docs, specs and tests.

And that’s it.  
Suggestions welcome.

All the best,  
William

---

<div class="post-metadata">

**Author:** ![josevalim](https://erlangforums.com/user_avatar/erlangforums.com/josevalim/32/353_2.png) [@josevalim](https://erlangforums.com/u/josevalim)\
**Post date:** [May 27, 2023, 9:27am UTC](https://erlangforums.com/t/eel-embedded-erlang-template-renderer-wip/1827/10 "2023-05-27T09:27:47Z")

</div>

Btw, if you possible, you should return IO list.

```erlang
 eel:eval(<<"Hello, <%= Name .%>!">>, #{'Name' => <<"World">>}).

```

Should compile to something like:

```
[<<"Hello, ">>, Name, <<"!">>]

```

And return an IO list. This would be the most efficient approach as each static part of the template is allocated as a literal. If someone wants an actual binary, `erlang:list_to_binary/1` can do the job. ([I think I live streamed about this a long time ago](https://www.twitch.tv/videos/338906698?collection=7NeiSsi6bhUvMw)).

This is specially important for something like WErl, because if you give each template a fingerprint (MD5 of its contents), you know you don’t need to send the static bits again.

---

<div class="post-metadata">

**Author:** ![williamthome](https://erlangforums.com/user_avatar/erlangforums.com/williamthome/32/471_2.png) [@williamthome](https://erlangforums.com/u/williamthome)\
**Post date:** [May 27, 2023, 2:23pm UTC](https://erlangforums.com/t/eel-embedded-erlang-template-renderer-wip/1827/11 "2023-05-27T14:23:39Z")

</div>

Thanks for the suggestion, @josevalim.  
Just merged a PR with this change and updated my comment above with the new output:

> [@williamthome](#):
>
> ```erlang
> 1> eel:eval(<<"Hello, <%= Name .%>!">>, #{'Name' => <<"World">>}).
> [<<"Hello, ">>,<<"World">>,<<"!">>]
> 
> ```

Appreciate your comments and tips!

---

<div class="post-metadata">

**Author:** ![williamthome](https://erlangforums.com/user_avatar/erlangforums.com/williamthome/32/471_2.png) [@williamthome](https://erlangforums.com/u/williamthome)\
**Post date:** [May 30, 2023, 5:26am UTC](https://erlangforums.com/t/eel-embedded-erlang-template-renderer-wip/1827/12 "2023-05-30T05:26:33Z")

</div>

I made some changes thinking in performance and, to my surprise, [EEl](https://github.com/williamthome/eel) was faster than [EEx](https://hexdocs.pm/eex/1.12.3/EEx.html).

 ![Screenshot from 2023-05-30 02-01-14](https://erlangforums.com/uploads/default/original/2X/3/325ebf02f0922af5e10cf637774db9c0a3639c1a.png)

The evaluated templates in the [benchmark](https://github.com/williamthome/eel/pull/29) was really simple:

```elixir
list = Enum.to_list 1..10

Benchee.run(%{
  "eex" => fn ->
    EEx.eval_string "Foo<%= Enum.map(list, fn(x) -> %><p><%= x %></p><% end) %>Bar", [list: list]
  end,
  "eel" => fn ->
    :eel.eval("Foo<%= lists:map(fun(X) -> %><p><%= X .%></p><% end, List) .%>Bar", %{List: list})
  end
})

```

Of course, they are not a good parameter, but make me happy and I’ll continue dedicating my time to this lib. There is more to improve, but, in general, it’s a good start 😃  
I’ll start to do some real use with EEl to find and fix bugs.  
Give it a try if you have some interest.  
Suggestions welcome! o/

---

<div class="post-metadata">

**Author:** ![williamthome](https://erlangforums.com/user_avatar/erlangforums.com/williamthome/32/471_2.png) [@williamthome](https://erlangforums.com/u/williamthome)\
**Post date:** [May 30, 2023, 1:49pm UTC](https://erlangforums.com/t/eel-embedded-erlang-template-renderer-wip/1827/13 "2023-05-30T13:49:13Z")

</div>

The difference is even bigger using compiled versions:

```erlang
Name ips average deviation median 99th %
eel 19.67 K 50.85 μs ±19.27% 49.82 μs 88.78 μs
eex 15.62 K 64.04 μs ±17.40% 63.79 μs 109.07 μs

Comparison: 
eel 19.67 K
eex 15.62 K - 1.26x slower +13.19 μs

```

The script:

```erlang
list = Enum.to_list 1..10
eex_quoted = EEx.compile_string("Foo<%= Enum.map(list, fn(x) -> %><p><%= x %></p><% end) %>Bar")
{:ok, eel_snapshot} = :eel.compile("Foo<%= lists:map(fun(X) -> %><p><%= X .%></p><% end, List) .%>Bar")

Benchee.run(%{
  "eex" => fn ->
    Code.eval_quoted(eex_quoted, [list: list])
  end,
  "eel" => fn ->
    {:ok, render} = :eel_renderer.render(%{List: list}, eel_snapshot)
    :eel_evaluator.eval(render)
  end
})

```

---

<div class="post-metadata">

**Author:** ![josevalim](https://erlangforums.com/user_avatar/erlangforums.com/josevalim/32/353_2.png) [@josevalim](https://erlangforums.com/u/josevalim)\
**Post date:** [May 31, 2023, 9:18am UTC](https://erlangforums.com/t/eel-embedded-erlang-template-renderer-wip/1827/14 "2023-05-31T09:18:28Z")

</div>

Can you please try running benchmarks with `Mix.install([])` at the top, so protocols are consolidated? I am guessing the additional cost is pretty much us trying to convert arguments to strings.

---

<div class="post-metadata">

**Author:** ![williamthome](https://erlangforums.com/user_avatar/erlangforums.com/williamthome/32/471_2.png) [@williamthome](https://erlangforums.com/u/williamthome)\
**Post date:** [May 31, 2023, 11:23am UTC](https://erlangforums.com/t/eel-embedded-erlang-template-renderer-wip/1827/15 "2023-05-31T11:23:07Z")

</div>

Sure! But this error occurs

```erlang
mix run benchmark.exs
** (Mix) Mix.install/2 cannot be used inside a Mix project

```

> [@josevalim](#):
>
> I am guessing the additional cost is pretty much us trying to convert arguments to strings.

If I understand correctly, I guess the same. Because of this a snapshot (metadata) is returned and not just a whole AST list in the compile function. For example, this is what the `eel:compile` can outputs (it’s an “older” result, I changed the snapshot to a `record()`, I’ll submit this change in my next PR)

```erlang
{["<html><head><title>",<<"Hey!">>,
  "</title></head><body>Hello, ",<<"World">>,
  "!</body></html>"],
 #{ast =>
       [{2,
         {{1,20},
          [{call,1,
               {remote,1,{atom,1,eel_converter},{atom,1,to_string}},
               [{'fun',1,
                    {clauses,[{clause,1,[],[],[{var,1,'Title'}]}]}}]}]}},
        {4,
         {{1,61},
          [{call,1,
               {remote,1,{atom,1,eel_converter},{atom,1,to_string}},
               [{'fun',1,
                    {clauses,[{clause,1,[],[],[{var,1,...}]}]}}]}]}}],
   bindings => #{'Name' => <<"World">>,'Title' => <<"Hey!">>},
   changes => [{2,<<"Hey!">>},{4,<<"World">>}],
   dynamic =>
       [{2,{{1,20},<<"Hey!">>}},{4,{{1,61},<<"World">>}}],
   static =>
       [{1,{{1,1},"<html><head><title>"}},
        {3,{{1,33},"</title></head><body>Hello, "}},
        {5,{{1,73},"!</body></html>"}}],
   vars => [{2,['Title']},{4,['Name']}]}}

```

The `static` values (strings) are nevermore evaluated, just zipped with the new `dynamic` result (AST).  
I guess EEl eval can be even faster because the `snapshot` holds the dynamic values as a cache and only the changes are evaluated.  
The `render` function returns `{iodata(), snapshot()}`, `iodata()` is the string and `snapshot()` is the updated metadata that helps the next render to be faster.

---

<div class="post-metadata">

**Author:** ![josevalim](https://erlangforums.com/user_avatar/erlangforums.com/josevalim/32/353_2.png) [@josevalim](https://erlangforums.com/u/josevalim)\
**Post date:** [May 31, 2023, 12:57pm UTC](https://erlangforums.com/t/eel-embedded-erlang-template-renderer-wip/1827/16 "2023-05-31T12:57:49Z")

</div>

> [@williamthome](#):
>
> ```erlang
> mix run benchmark.exs
> ** (Mix) Mix.install/2 cannot be used inside a Mix project
> 
> ```

Ah, perfect, so protocols are already consolidated. Thanks!
