Hi dear Alchemists.
I have a small Phoenix app using LiveView and Tailwind. I’ve been using FontAwesome with @fontface so far but I’d prefer to user SVG icons.
So I’ve been experimenting with generating SVGs at compile time with the use of macros. My first approach was to scan the priv/fontawesome/*/*.svg
dir and generate a function for each icon. While this is quite nice to use, this approach has two drawbacks:
- Compiling the helper module takes quite some time (1min).
- It generates a lot of code that I might never use and increases binary size.
So I went with a different approach:
defmacro icon(opts) do
{name, opts} = Keyword.pop!(opts, :name)
{style, opts} = Keyword.pop(opts, :style, "duotone")
path = Path.join([@root, style, String.replace(name, "-", "_") <> ".svg"])
icon = File.read!(path)
{i, _} = :binary.match(icon, ">")
{head, body} = String.split_at(icon, i)
attrs =
for {k, v} <- opts do
safe_k = k |> Atom.to_string() |> String.replace("_", "-") |> Phoenix.HTML.Safe.to_iodata()
safe_v = v |> Phoenix.HTML.Safe.to_iodata()
{:safe, [?\s, safe_k, ?=, ?", safe_v, ?"]}
end
{:safe, [head, Phoenix.HTML.Safe.to_iodata(attrs), body]}
end
The icon/1
macro generates the required SVG at compile time only when needed. In my template, I’m using it like this:
<%= icon name: "bell", style: "solid", class: "h-6 w-6" %>
So I’m thinking it’s working quite well with Phoenix EEx and follow the idea of generating static IO-data at compile time.
I’d like to expand the idea further and rewrite my macro to work as a Phoenix.Component. But the dark world of AST is quite hard to approach and it’s seems much harder than I thought.
defmacro icon(var!(assigns)) do
# somehow assigns might not been evaluated yet. the AST is {:x1, [], :elixir_fn}
I can retrieve the assigns in a quote
block. But I’d like to generate the SVG at compile time (so outside any quote block).
defmacro icon(var!(assigns)) do
# I still need to retrieve @name and @style from the assigns
# in order load my SVG here.
quote do
var!(attrs) = Phoenix.LiveView.Helpers.assigns_to_attributes(var!(assigns))
var!(assigns) = Phoenix.LiveView.assign(var!(assigns), :attrs, var!(attrs))
unquote(
EEx.compile_string(head <> "{@attrs}" <> body,
engine: Phoenix.LiveView.HTMLEngine,
file: __ENV__.file,
line: __ENV__.line + 1,
module: __ENV__.module,
indentation: 0
)
)
end
end
Maybe Phoenix.Component and HEEX provide the assigns
only at runtime and I’m trying to evaluate them before they even exist?
I’ve been trying to evaluate the assigns
quoted expression with Code.evaluate_quoted/3
without success. It also seems counter-intuitive to do so.
Any help warmly welcome