README.md

# SvgSpriteEx

`SvgSpriteEx` lets you turn svg files into compile-time svg refs for Phoenix
components and LiveView.

You can render svgs in two ways:

- `ref={sprite_ref("...")}` renders a `<svg><use ... /></svg>` wrapper backed
  by a generated sprite sheet
- `ref={inline_ref("...")}` renders the full svg inline in the document

## Installation

Add `svg_sprite_ex` to your dependencies:

```elixir
def deps do
  [
    {:svg_sprite_ex, "~> 0.2.0"}
  ]
end
```

Then register the sprite compiler ahead of the default Mix compilers so it can
install its Elixir compile callback and discover `sprite_ref/1`, `sprite_ref/2`,
and `inline_ref/1` usages.

```elixir
def project do
  [
    app: :my_app,
    version: "0.2.0",
    elixir: "~> 1.19",
    compilers: [:svg_sprite_ex_assets] ++ Mix.compilers(),
    deps: deps()
  ]
end
```

Note that `:svg_sprite_ex_assets` **must** appear before the `:elixir` compiler.

When using Phoenix code reloading in development, add `:svg_sprite_ex_assets`
to `reloadable_compilers`. Phoenix only reruns the compilers listed there
during request-time reloads, so omitting it can still reload the page before
the generated sprite sheet or inline registry has been rebuilt.

```elixir
config :my_app, MyAppWeb.Endpoint,
  reloadable_compilers: [:svg_sprite_ex_assets, :elixir, :app]
```

Adjust the list to match the compilers used in your project.

## Configuration

```elixir
import Config

config :svg_sprite_ex,
  source_root: Path.expand("../priv/icons", __DIR__),
  build_path: Path.expand("../priv/static/svgs", __DIR__),
  public_path: "/svgs",
  default_sheet: "sprites"
```

### Required configuration

- `source_root` - absolute path to the directory that contains source svg files.
- `build_path` - absolute path where the compiler generates sprite sheets.
- `public_path` - public URL prefix for `sprite_ref/1` hrefs.

### Optional configuration

- `default_sheet` - default sprite sheet name when no `sheet` option is
  given. Defaults to `sprites`.

Given the config above, if your svg file lives at
`priv/icons/regular/xmark.svg`, the logical svg name is `regular/xmark`.

Note that `sprite_ref` and `inline_ref` only accept compile-time literal
values. This is how the compiler discovers which svgs need to be included in
the generated outputs.

## How it works

When you run `mix compile`, the compiler:

- scans compiled modules for `sprite_ref` and `inline_ref` calls
- hashes the referenced svg files and compiler inputs to detect asset changes
- writes one svg sprite sheet per sheet name into `build_path`
- compiles generated modules for inline svg lookup and runtime metadata lookup

Your application must serve the generated files from the same public path you
configured. For example: Write sprite sheets into `priv/static/svgs`, and
serve them from `/svgs`.

## Phoenix usage

Use `SvgSpriteEx` in any component, LiveView, or HTML module that renders svgs:

```elixir
defmodule MyAppWeb.MyComponents do
  use Phoenix.Component
  use SvgSpriteEx
end
```

This will import:

- the `<.svg>` function component from `SvgSpriteEx.Svg`
- the `sprite_ref` and `inline_ref` macros from `SvgSpriteEx.Ref`

### Render using a sprite sheet

```elixir
defmodule MyAppWeb.MyComponents do
  use Phoenix.Component
  use SvgSpriteEx

  def close_icon(assigns) do
    ~H"""
    <.svg ref={sprite_ref("regular/xmark")} class="size-4" />
    """
  end
end
```

By default the svgs are placed in a sprite sheet called `sprites.svg`, but you
can also compile svgs to other named sheets:

```elixir
<.svg ref={sprite_ref("regular/xmark", sheet: "dashboard")} class="size-4" />
```

### Render inline svgs

Inline mode skips the sprite sheet and renders the svg inline in the document.

```elixir
<.svg ref={inline_ref("regular/xmark")} class="size-4" />
```

This lets you serve the raw svg markup in the page instead of a `<use>`
reference, without doing runtime file reads.

## Runtime metadata

`SvgSpriteEx` also exposes runtime metadata for compiled outputs:

```elixir
SvgSpriteEx.sprite_sheets()
#=> [%SvgSpriteEx.SpriteSheetMeta{...}]

SvgSpriteEx.sprite_sheet("dashboard")
#=> %SvgSpriteEx.SpriteSheetMeta{...}

SvgSpriteEx.sprites_in_sheet("dashboard")
#=> [%SvgSpriteEx.SpriteMeta{...}]

SvgSpriteEx.inline_svgs()
#=> [%SvgSpriteEx.InlineSvgMeta{...}]

SvgSpriteEx.inline_svg("regular/xmark")
#=> %SvgSpriteEx.InlineSvgMeta{...}
```

## Patterns

### Preload a single sprite sheet

When a layout or component knows it will use a specific sprite sheet, you can
preload it by looking up the compiled sheet metadata with `sprite_sheet/1` and
rendering a `<link rel="preload" ...>` tag.

In a helper or function component:

```elixir
defmodule MyAppWeb.MyComponents do
  use Phoenix.Component

  attr :sheet, :string, required: true

  def sprite_sheet_preload(assigns) do
    assigns = assign(assigns, :sheet_meta, SvgSpriteEx.sprite_sheet(assigns.sheet))

    ~H"""
    <link
      :if={@sheet_meta}
      rel="preload"
      href={@sheet_meta.public_path}
      as="image"
      type="image/svg+xml"
    />
    """
  end
end
```

Then in a layout or page template:

```elixir
<.sprite_sheet_preload sheet="dashboard" />
```