Optimize Phoenix Component caching

This commit is contained in:
Max Veytsman
2022-09-02 21:01:48 -04:00
parent 1f8318db65
commit 0726bf6781
2 changed files with 43 additions and 49 deletions

View File

@ -12,69 +12,42 @@ defmodule Heroicons.Cache do
@doc false @doc false
def start_link(_), do: GenServer.start_link(__MODULE__, [], name: @name) def start_link(_), do: GenServer.start_link(__MODULE__, [], name: @name)
@doc "Fetch a pre-compiled Phoenix Component from the cache or disk, given a `path`" @doc "Fetch a icon's body and fingerprint from the cache or disk, given a `path`"
def fetch_component(path) do def fetch_icon(path) do
case :ets.lookup(@name.Components, path) do
[{^path, component}] ->
component
[] ->
GenServer.call(@name, {:cache_component, path})
end
end
@doc "Fetch a icon's body from the cache or disk, given a `path`"
def fetch_body(path) do
case :ets.lookup(@name, path) do case :ets.lookup(@name, path) do
[{^path, body}] -> [{^path, icon_body, fingerprint}] ->
body {icon_body, fingerprint}
[] -> [] ->
GenServer.call(@name, {:cache_body, path}) GenServer.call(@name, {:cache_icon, path})
end end
end end
@impl true @impl true
def init(_) do def init(_) do
:ets.new(@name, [:set, :protected, :named_table]) :ets.new(@name, [:set, :protected, :named_table])
:ets.new(@name.Components, [:set, :protected, :named_table])
{:ok, []} {:ok, []}
end end
@impl true @impl true
def handle_call({:cache_body, path}, _ref, state) do def handle_call({:cache_icon, path}, _ref, state) do
body = read_body(path) {icon_body, fingerprint} = read_icon(path)
:ets.insert_new(@name, {path, body}) :ets.insert_new(@name, {path, icon_body, fingerprint})
{:reply, body, state} {:reply, {icon_body, fingerprint}, state}
end end
def handle_call({:cache_component, path}, _ref, state) do defp read_icon(path) do
body = read_body(path)
component =
EEx.compile_string("<svg {@attrs}" <> body,
engine: Phoenix.LiveView.HTMLEngine,
file: __ENV__.file,
line: __ENV__.line + 1,
module: __ENV__.module,
indentation: 0
)
:ets.insert_new(@name.Components, {path, component})
{:reply, component, state}
end
defp read_body(path) do
icon = icon =
Path.join(:code.priv_dir(:heroicons), path) Path.join(:code.priv_dir(:heroicons), path)
|> File.read!() |> File.read!()
<<"<svg ", body::binary>> = icon <<"<svg", icon_body::binary>> = icon
body <<fingerprint::8*16>> = :erlang.md5(icon)
{icon_body, fingerprint}
end end
end end

View File

@ -71,13 +71,33 @@ defmodule Heroicons.Generator do
attrs = @assigns_to_attrs_mod.assigns_to_attributes(assigns) attrs = @assigns_to_attrs_mod.assigns_to_attributes(assigns)
assigns = @assign_mod.assign(assigns, :attrs, attrs) assigns = @assign_mod.assign(assigns, :attrs, attrs)
{component, _binding} = dynamic = fn track_changes? ->
Code.eval_quoted( changed =
Heroicons.Cache.fetch_component(path), case assigns do
assigns: assigns %{__changed__: changed} when track_changes? -> changed
) _ -> nil
end
component attrs =
case Phoenix.LiveView.Engine.changed_assign?(changed, :attrs) do
true -> elem(Phoenix.HTML.attributes_escape(assigns.attrs), 1)
false -> nil
end
[attrs]
end
{icon_body, fingerprint} = Heroicons.Cache.fetch_icon(path)
%Phoenix.LiveView.Rendered{
static: [
"<svg",
icon_body
],
dynamic: dynamic,
fingerprint: fingerprint,
root: true
}
end end
@doc false @doc false
@ -92,12 +112,13 @@ defmodule Heroicons.Generator do
{:safe, [?\s, safe_k, ?=, ?", safe_v, ?"]} {:safe, [?\s, safe_k, ?=, ?", safe_v, ?"]}
end end
{icon_body, _fingerprint} = Heroicons.Cache.fetch_icon(path)
{:safe, {:safe,
[ [
"<svg", "<svg",
Phoenix.HTML.Safe.to_iodata(attrs), Phoenix.HTML.Safe.to_iodata(attrs),
" ", icon_body
Heroicons.Cache.fetch_body(path)
]} ]}
end end
end end