For a personal project I’m working, I need to use boxicons in surface templates. Since the package didn’t exist, I decided to take the matter into my own hands. The results are here:
PS: The library is very small, so I’d very happy if you could check it out and point out things you would have don’t better. Anything. Even style pointers are most welcomed.
Very nice and congrats! Just a small thing but it sticks out immediately: could you give the background of the search area a softer white/grey colour so the contrast isn’t as overwhelming?
One thing to keep in mind is that defining modules is more expensive than defining multiple function clauses so since all components have the same props, I believe you would have a more consistent API, along with faster compilation, if you define a single component with name and maybe also a type prop to differentiate each icon. Something like:
def render(%{name: "video-plus", type="solid"} = assigns) do
~F[<svg ... width={@size} height={@size} class={@class} .../></svg>]
end
def render(%{name: "video-plus", type="regular"} = assigns) do
~F[<svg ... width={@size} height={@size} class={@class} .../></svg>]
end
def render(%{name: "bell", type="solid"} = assigns) do
~F[<svg ... width={@size} height={@size} class={@class} .../></svg>]
end
def render(%{name: "bell", type="regular"} = assigns) do
~F[<svg ... width={@size} height={@size} class={@class} .../></svg>]
end
...
Turns out, with this approach, compilation times went bananas! I’m compiling that single module in about 60 seconds, while it used to take around 10-15 seconds to compile all the 1500 modules.
I do appreciate how the API is much better now thou.
Large numbers of function clauses does eventually create exponentially slower compilation times. I have a similar issue in ex_CLDR and I know the gettext team went with splitting into modules as an option for the same reason.
I haven’t dug far enough to know where performance gets pathologically bad but ‘hundreds’ of clauses seems to exhibit this behaviour.
Is it because the 1500 modules are compiled in parallel, whereas the one giant module can only be compiled in one thread?
Can you compare the CPU time. I am curious whether too many function clauses have non linear performance penalty, on top of the lack of parallelization?
OK, here are the results (I little different than what I experienced before for some reason)
1 module version:
❯ time mix compile
Compiling 2 files (.ex)
Compiling lib/boxicon.ex (it's taking more than 10s)
Generated surface_boxicon app
mix compile 27.49s user 2.24s system 106% cpu 27.818 total
1527 modules version
❯ time mix compile
Compiling 1527 files (.ex)
Generated surface_boxicon app
mix compile 71.68s user 8.96s system 437% cpu 18.427 total
Looks great! Maybe you could make the included icons and types configurable. Then you can reduce the number of function heads to generate at compile time. Most projects would only be using a small subset.
Edit: didn’t look at the implementation first. I was assuming you were using a macro to generate the function heads at compile time.
You wouldn’t necessarily need to implement any macros, but meta programming would still be useful. Something like:
defmodule Boxicon do
@doc "Type of the icon"
prop type, :string, values!: ["solid", "regular", "logos"]
@doc "Name of the icon"
prop name, :string, required: true
@doc "Width & height of the icon"
prop size, :integer, default: 24
@doc "CSS classes for the wrapping svg element"
prop class, :string, default: "icon"
@icons "populate icons from static data at compile time"
for {type, name, path} <- @icons do
def render(%{name: unquote(name), type: unquote(type)} = assigns) do
~F[<svg xmlns="http://www.w3.org/2000/svg" width={"#{@size}"} height={"#{@size}"} class={"#{@class}"} viewBox="0 0 24 24"><path d=unquote(path)/></svg>]
end
end
end
Happy to help with how to structure the data to populate @icons at compile time if you need.
The compile times were a lot faster when I just specified the “logos” type to be included so if you allowed configuring the included icons it should be pretty quick.
I haven’t looked too deep but are there any Surface specific things in this library? I wonder if it would be more reusable to use Phoenix’s component & functional component API? (Reusable across non-Surface LiveView apps and normal non-LiveView Phoenix apps)
I’m thinking something like:
# usage
<%= component(&BoxIcon.bell/1, []) %>
# definition
defmodule BoxIcon do
def bell(assigns) do
~H[<svg>...</svg>]
end
end