diff --git a/lib/simpleshop_theme/application.ex b/lib/simpleshop_theme/application.ex
index 2098f80..ca5b640 100644
--- a/lib/simpleshop_theme/application.ex
+++ b/lib/simpleshop_theme/application.ex
@@ -18,10 +18,10 @@ defmodule SimpleshopTheme.Application do
{Oban, Application.fetch_env!(:simpleshop_theme, Oban)},
# Image variant cache - ensures all variants exist on startup
SimpleshopTheme.Images.VariantCache,
- # Theme CSS cache
- SimpleshopTheme.Theme.CSSCache,
- # Start to serve requests, typically the last entry
- SimpleshopThemeWeb.Endpoint
+ # Start to serve requests
+ SimpleshopThemeWeb.Endpoint,
+ # Theme CSS cache - must start after Endpoint for static_path/1 to work
+ SimpleshopTheme.Theme.CSSCache
]
# See https://hexdocs.pm/elixir/Supervisor.html
diff --git a/lib/simpleshop_theme/theme/css_cache.ex b/lib/simpleshop_theme/theme/css_cache.ex
index e274f84..0fa9dca 100644
--- a/lib/simpleshop_theme/theme/css_cache.ex
+++ b/lib/simpleshop_theme/theme/css_cache.ex
@@ -82,7 +82,11 @@ defmodule SimpleshopTheme.Theme.CSSCache do
alias SimpleshopTheme.Theme.CSSGenerator
settings = Settings.get_theme_settings()
- css = CSSGenerator.generate(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
diff --git a/lib/simpleshop_theme/theme/css_generator.ex b/lib/simpleshop_theme/theme/css_generator.ex
index a334cb4..4c5b552 100644
--- a/lib/simpleshop_theme/theme/css_generator.ex
+++ b/lib/simpleshop_theme/theme/css_generator.ex
@@ -21,11 +21,14 @@ defmodule SimpleshopTheme.Theme.CSSGenerator do
pages don't need the attribute-based CSS selectors.
Also includes @font-face declarations for the fonts used by the typography preset.
+
+ Accepts an optional path_resolver function for digested font paths.
+ In production, pass `&SimpleshopThemeWeb.Endpoint.static_path/1`.
"""
- def generate(%ThemeSettings{} = settings) do
+ def generate(%ThemeSettings{} = settings, path_resolver \\ fn path -> path end) do
"""
/* Font faces for #{settings.typography} typography */
- #{Fonts.generate_font_faces(settings.typography)}
+ #{Fonts.generate_font_faces(settings.typography, path_resolver)}
/* Theme Tokens - Layer 2 (dynamically generated) */
.themed {
diff --git a/lib/simpleshop_theme/theme/fonts.ex b/lib/simpleshop_theme/theme/fonts.ex
index 93b189a..e4680a6 100644
--- a/lib/simpleshop_theme/theme/fonts.ex
+++ b/lib/simpleshop_theme/theme/fonts.ex
@@ -114,8 +114,11 @@ defmodule SimpleshopTheme.Theme.Fonts do
Generates @font-face CSS for a specific typography preset.
Only includes the fonts needed for that preset.
+
+ Accepts an optional path_resolver function to transform font URLs.
+ In production, pass `&SimpleshopThemeWeb.Endpoint.static_path/1` for digested paths.
"""
- def generate_font_faces(typography) do
+ def generate_font_faces(typography, path_resolver \\ &default_path_resolver/1) do
%{heading: heading_key, body: body_key} = fonts_for_typography(typography)
font_keys =
@@ -126,7 +129,7 @@ defmodule SimpleshopTheme.Theme.Fonts do
end
font_keys
- |> Enum.map(&generate_font_face_for_font/1)
+ |> Enum.map(&generate_font_face_for_font(&1, path_resolver))
|> Enum.join("\n")
end
@@ -134,20 +137,22 @@ defmodule SimpleshopTheme.Theme.Fonts do
Generates @font-face CSS for ALL fonts.
Used in the theme editor where users can switch between typography presets.
+
+ Accepts an optional path_resolver function for digested paths.
"""
- def generate_all_font_faces do
+ def generate_all_font_faces(path_resolver \\ &default_path_resolver/1) do
@fonts
|> Map.keys()
- |> Enum.map(&generate_font_face_for_font/1)
+ |> Enum.map(&generate_font_face_for_font(&1, path_resolver))
|> Enum.join("\n")
end
@doc """
- Generates preload link tags for a specific typography preset.
+ Returns the font file paths (without /fonts prefix) for a typography preset.
- Returns a list of maps with href, as, type, and crossorigin attributes.
+ Used by templates to generate preload links with digested paths via ~p sigil.
"""
- def preload_links(typography) do
+ def preload_font_paths(typography) do
%{heading: heading_key, body: body_key} = fonts_for_typography(typography)
# Preload the most commonly used weights
@@ -158,21 +163,60 @@ defmodule SimpleshopTheme.Theme.Fonts do
body: [400, 600]
}
- heading_links = generate_preload_links_for_font(heading_key, preload_weights.heading)
- body_links = generate_preload_links_for_font(body_key, preload_weights.body)
+ heading_paths = font_paths_for_weights(heading_key, preload_weights.heading)
+ body_paths = font_paths_for_weights(body_key, preload_weights.body)
# Deduplicate in case heading and body use the same font
- (heading_links ++ body_links) |> Enum.uniq_by(& &1.href)
+ (heading_paths ++ body_paths) |> Enum.uniq()
+ end
+
+ defp font_paths_for_weights(key, weights) do
+ case get_font(key) do
+ %{file_prefix: prefix, weights: available_weights} ->
+ weights
+ |> Enum.filter(&(&1 in available_weights))
+ |> Enum.map(fn weight ->
+ weight_suffix = if weight == 400, do: "regular", else: to_string(weight)
+ "#{prefix}-#{weight_suffix}.woff2"
+ end)
+
+ nil ->
+ []
+ end
+ end
+
+ @doc """
+ Generates preload link tags for a specific typography preset.
+
+ Returns a list of maps with href, as, type, and crossorigin attributes.
+
+ Accepts an optional path_resolver function for digested paths.
+ In production, pass `&SimpleshopThemeWeb.Endpoint.static_path/1`.
+ """
+ def preload_links(typography, path_resolver \\ &default_path_resolver/1) do
+ typography
+ |> preload_font_paths()
+ |> Enum.map(fn filename ->
+ %{
+ href: path_resolver.("/fonts/#{filename}"),
+ as: "font",
+ type: "font/woff2",
+ crossorigin: true
+ }
+ end)
end
# Private functions
- defp generate_font_face_for_font(key) do
+ defp default_path_resolver(path), do: path
+
+ defp generate_font_face_for_font(key, path_resolver) do
case get_font(key) do
%{family: family, file_prefix: prefix, weights: weights} ->
weights
|> Enum.map(fn weight ->
weight_suffix = if weight == 400, do: "regular", else: to_string(weight)
+ font_path = path_resolver.("/fonts/#{prefix}-#{weight_suffix}.woff2")
"""
@font-face {
@@ -180,7 +224,7 @@ defmodule SimpleshopTheme.Theme.Fonts do
font-style: normal;
font-weight: #{weight};
font-display: swap;
- src: url('/fonts/#{prefix}-#{weight_suffix}.woff2') format('woff2');
+ src: url('#{font_path}') format('woff2');
}
"""
end)
@@ -191,24 +235,4 @@ defmodule SimpleshopTheme.Theme.Fonts do
end
end
- defp generate_preload_links_for_font(key, weights) do
- case get_font(key) do
- %{file_prefix: prefix, weights: available_weights} ->
- weights
- |> Enum.filter(&(&1 in available_weights))
- |> Enum.map(fn weight ->
- weight_suffix = if weight == 400, do: "regular", else: to_string(weight)
-
- %{
- href: "/fonts/#{prefix}-#{weight_suffix}.woff2",
- as: "font",
- type: "font/woff2",
- crossorigin: true
- }
- end)
-
- nil ->
- []
- end
- end
end
diff --git a/lib/simpleshop_theme_web/components/layouts/shop_root.html.heex b/lib/simpleshop_theme_web/components/layouts/shop_root.html.heex
index ba2185c..9b8ecd4 100644
--- a/lib/simpleshop_theme_web/components/layouts/shop_root.html.heex
+++ b/lib/simpleshop_theme_web/components/layouts/shop_root.html.heex
@@ -7,8 +7,11 @@
<.live_title><%= assigns[:page_title] || @theme_settings.site_name %>
- <%= for preload <- SimpleshopTheme.Theme.Fonts.preload_links(@theme_settings.typography) do %>
-
+ <%= for preload <- SimpleshopTheme.Theme.Fonts.preload_links(
+ @theme_settings.typography,
+ &SimpleshopThemeWeb.Endpoint.static_path/1
+ ) do %>
+
<% end %>