simpleshop_theme/lib/simpleshop_theme/theme/css_cache.ex
Jamey Greenwood 9783199691 perf: use digested font paths in CSS and preloads
Add path_resolver parameter to font generation functions so both
font preloads and CSS @font-face declarations use the same digested
paths in production. This prevents duplicate font downloads when
preloads were using digested paths but CSS used non-digested paths.

- Add path_resolver parameter to Fonts.generate_font_faces/2,
  Fonts.preload_links/2, and Fonts.generate_all_font_faces/1
- Update CSSGenerator.generate/2 to accept path_resolver
- Update CSSCache.warm/0 to use Endpoint.static_path/1
- Move CSSCache to start after Endpoint in supervision tree
- Add 1-year cache headers for static assets

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-25 09:32:06 +00:00

112 lines
2.0 KiB
Elixir

defmodule SimpleshopTheme.Theme.CSSCache do
@moduledoc """
GenServer that maintains an ETS table for caching generated theme CSS.
This provides fast lookups for theme CSS without regenerating it on every request.
The cache is invalidated when theme settings are updated.
"""
use GenServer
@table_name :theme_css_cache
## Client API
@doc """
Starts the CSS cache GenServer.
"""
def start_link(opts) do
GenServer.start_link(__MODULE__, opts, name: __MODULE__)
end
@doc """
Gets cached CSS for the site theme.
Returns `{:ok, css}` if found in cache, or `:miss` if not cached.
## Examples
iex> CSSCache.get()
{:ok, "/* Theme CSS ... */"}
iex> CSSCache.get()
:miss
"""
def get do
case :ets.lookup(@table_name, :site_theme) do
[{:site_theme, css}] -> {:ok, css}
[] -> :miss
end
end
@doc """
Caches CSS for the site theme.
## Examples
iex> CSSCache.put(css_string)
:ok
"""
def put(css) when is_binary(css) do
:ets.insert(@table_name, {:site_theme, css})
:ok
end
@doc """
Invalidates the cached CSS, forcing regeneration on next request.
## Examples
iex> CSSCache.invalidate()
:ok
"""
def invalidate do
:ets.delete(@table_name, :site_theme)
:ok
end
@doc """
Warms the cache by generating and storing CSS from current theme settings.
## Examples
iex> CSSCache.warm()
:ok
"""
def warm do
alias SimpleshopTheme.Settings
alias SimpleshopTheme.Theme.CSSGenerator
settings = Settings.get_theme_settings()
# Use endpoint's static_path for digested URLs in production
path_resolver = &SimpleshopThemeWeb.Endpoint.static_path/1
css = CSSGenerator.generate(settings, path_resolver)
put(css)
:ok
end
## Server Callbacks
@impl true
def init(_opts) do
:ets.new(@table_name, [
:set,
:public,
:named_table,
read_concurrency: true,
write_concurrency: false
])
# Warm the cache on startup
warm()
{:ok, %{}}
end
end