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 %>