Temple - HTML DSL for Elixir and Phoenix

Got to say, this is pretty great. Far preferable to writing eex. People should definitely give it a look.

Glad you like it!

Hopefully I can get 0.6.0 released soon. I currently am in the market for a new computer, which is one of the reasons why it’s been taking so long.

:beers:

2 Likes

I very much like the approach Temple takes in parsing.

Out of curiosity and because I think it improves the readability of templates further by removing more boilerplate, I was wondering if if would be possible to support shorthand class-syntax, i.e. div.large.red.button do ... end being turned into <div class="large red button">... </div>.
Since you are parsing the quoted AST manually, I think this should be possible.

What do you think?

2 Likes

Just seen this work, great work!

Out of curiosity, did you do any performance benchmarking with barebones phoenix.html and your library?

It would be an interesting test and maybe +1 for your libraries benefits :slight_smile:

If you are talking about runtime performance: There will not be any difference, as at compile-time the DSL gets turned into the same IOlist format as .eex-templates are. So at runtime there is not any difference.

2 Likes

I very much like the approach Temple takes in parsing.

Thanks!

Out of curiosity and because I think it improves the readability of templates further by removing more boilerplate, I was wondering if if would be possible to support shorthand class-syntax, i.e. div.large.red.button do ... end being turned into <div class="large red button">... </div>.
Since you are parsing the quoted AST manually, I think this should be possible.

What do you think?

I think that this is technically feasible, but I would have to see some more use cases before considering adding something like that. If you’d like, please open a Discussion on GitHub and we can discuss the feature further!

Out of curiosity, did you do any performance benchmarking with barebones phoenix.html and your library?

As of the 0.6.0 pre-releases, Temple generates EEx at compile time. So if you were to be using it with a Phoenix project, you can use the Temple.Engine or Temple.LiveViewEngine to further compile the generated EEx using the Phoenix.HTML.Engine or Phoenix.LiveView.Engine, which will be compiled the same way a traditional phoenix project would

So, the compiling will be slower as it includes an extra step, but once you’re at runtime it should be identical to a traditional phoenix project.

2 Likes

v0.6 has been released!

You can read about it on my blog and in the release notes.

Cheers! :beers:

5 Likes

In the 6.0 RC, I had this component, which acted as a wrapper around live_redirect to make them look like buttons. It mostly just takes any arguments you pass to it, injects some css classes and passes it all down to live_redirect.

In the release I get an error around protocol Phoenix.HTML.Safe not implemented for #Function<27.16960434....

Calling live_redirect without splat actually works fine, I think it’s to do with the order of expansion inside temples macros (just guessing at how Temple works internally).

Is this kind of component still possible?

defmodule AppWeb.Component.LiveRedirect do
  import Temple.Component

  # ... snip
  
  # convert any given arguments (as map) to keyword list
  # for use in live_redirect
  def splat(assigns) do
    assigns
    |> Map.delete(:inner_content) # needs change/not needed
    |> set_defaults()
    |> maybe_merge_class()
    |> Enum.into([])
  end

  render do
    case assigns[:disabled] do
      true ->
        span splat(assigns) do
          slot :default
        end

      _ ->
        Phoenix.LiveView.Helpers.live_redirect splat(assigns) do
           slot :default
        end
    end
  end
end

I think you’ll want to rename :inner_content to :inner_block in your splat function.

1 Like

Oh geez. Yes, that fixed it.

I knew inner_content had been replaced but the error from elixir tricked me I guess :flushed: and I had left that change on the “future fix” list.

Love Temple, it’s great.

1 Like

Internally, slots are represented as a function, so that is why it was saying it the Safe protocol isn’t implemented for type Function.

I actually have some protection against this tho with regard to rendering attributes, but it seems it doesn’t currently apply when you don’t pass any list literal.

I opened an issue for this: [bug] Don't render the inner_block assign when building element attributes · Issue #132 · mhanberg/temple · GitHub

1 Like

Oh, I think this actually work as expected. What you were experiencing is when the live_redirect function trying to compile the attrs. you’ll have to make sure that don’t passthrough the inner block key yourself in that case.

This is because it compiles to

<%= case assigns[:disabled] do %>
  <% true -> %>
    <span <%= Temple.Parser.Utils.runtime_attrs(splat(assigns)) %>>
        <%= Temple.Component.__render_block__(@inner_block, {:default, %{}}) %>
    </span>
   <% _ -> %>
      <%= Phoenix.LiveView.Helpers.live_redirect(splat(assigns)) do %>
        <%= Temple.Component.__render_block__(@inner_block, {:default, %{}}) %>
      <% end %>
<% end %>

as you can see, the splat passed to the span is run through a temple function that will filter out :inner_block, but that can’t happen when you pass the splat to live_redirect. So, you will have to change the Map.delete call in your code snippet to account for :inner_block

That makes sense.

I was considering passing my assigns to Temple.Parser.Util.runtime_attrs() to avoid any future dropbears, but it returns a string, and smells like a private API.

I wonder if a designated clean_attrs_keys or something would be good (or map_to_attrs_list(map()) :: keyword()). I.E:

def splat(assigns) do
  assigns 
  |> munge_etc()
  |> Enum.into([])
  |> Temple.clean_attributes_keys()
end

Not sure how common the splat pattern is. It’s very useful for wrapping standard phx stuff but I doubt I would want to take on the maintenance burden if I were writing Temple.

I will probably just extract splat into it’s own module and note where to check for keys I should be dropping if something breaks.

I was considering passing my assigns to Temple.Parser.Util.runtime_attrs() to avoid any future dropbears, but it returns a string, and smells like a private API.

Yes, do not use that function :sweat_smile:.