Using EEx.eval_string with HEEx Engine on live_view 0.18.18

We were successfully experimenting rendering HTML from templates stored as strings dynamically with phoenix_live_view version bellow 0.16.
We were using the function EEx.eval_string/3 such as:

options = [engine: Phoenix.LiveView.HTMLEngine]
EEx.eval_string(my_template, [assigns: socket.assigns], options))

And it was working fine.

But now, trying to upgrade to phoenix_live_view:0.18.18 we got an error saying:

function Phoenix.LiveView.HTMLEngine.init/1 is undefined or private

Taking a look at the git repository, in fact, the module was change and it doesn’t have the init/1 anymore.

Looking a bit into it, we found a change on Phoenix.Component that seems to be related to this. Where it was:
lib/phoenix_component.ex:747

    options = [
      engine: Phoenix.LiveView.HTMLEngine,
      file: __CALLER__.file,
      line: __CALLER__.line + 1,
      caller: __CALLER__,
      indentation: meta[:indentation] || 0,
      source: expr
    ]

It is now:

options = [
      engine: Phoenix.LiveView.TagEngine,
      file: __CALLER__.file,
      line: __CALLER__.line + 1,
      caller: __CALLER__,
      indentation: meta[:indentation] || 0,
      source: expr,
      tag_handler: Phoenix.LiveView.HTMLEngine
    ]

We tried to change the engine to Phoenix.LiveView.TagEngine with the :tag_handler, but it fails because it requires a :caller option which we don’t have as we aren’t using the function inside a macro.

Any hints of why it changed and which engine should we use?

Thanks in advance.

Can you describe more about your use case? It’s usually a bad idea to do string eval as it’s super slow, but this will work if you truly need it:

template = """
<h1><%= @test %></h1>
"""
options = [
  engine: Phoenix.LiveView.TagEngine,
  file: __ENV__.file,
  line: __ENV__.line + 1,
  caller: __ENV__,
  indentation: 0,
  source: template,
  tag_handler: Phoenix.LiveView.HTMLEngine
]
    
template
|> EEx.eval_string([assigns: %{test: "test"}], options)
|> Phoenix.HTML.html_escape()
|> Phoenix.HTML.safe_to_string()

# "<h1>test</h1>"
2 Likes

I have a similar problem with EEx.function_from_file which worked with heex-engine prior 0.18, now I’m (misusing) Phoenix.Template.compile_all. What do you recommend if one just wants to use heex as a template engine, but can’t use embed_templates (not flexible enough)?

Thanks for the fast response, this solution works for me!

We are using the HEEx engine to generate some email bodies, and we want to programatically store the templates in the database instead of having static template files, for easier modification.

I tried to look into other solutions but this was the easiest for me. As it is an experiment I wasn’t too concerned about performance, but it is good to know.

Is there a better way to do it?

I would probably use another templating language that was made for this purpose and one that doesn’t allow for arbitrary code execution.

GitHub - edgurgel/solid: Liquid template engine in Elixir does that for the liquid templating language.

1 Like

I understand where this can go unsafely wrong. It is just too tempting to have the full power of HEEx.
I will look into @LostKobrakai solution, I don’t see why it shouldn’t fit our needs.

Thanks for the support!