From c09ac657ab8fe6e7292471cc50dfaecbc722c899 Mon Sep 17 00:00:00 2001 From: Javier Cicchelli Date: Tue, 25 Aug 2026 17:05:08 +0200 Subject: [PATCH] Added font preload links to the Page+Defaults extension in the Website library target. --- .../Internal/Extensions/Page+Defaults.swift | 34 +++++++++++++++++-- .../Cases/Internal/Pages/IndexPageTests.swift | 7 ++++ .../Internal/Types/StaticFileTests.swift | 9 +++++ 3 files changed, 48 insertions(+), 2 deletions(-) diff --git a/Services/Website/Sources/Library/Internal/Extensions/Page+Defaults.swift b/Services/Website/Sources/Library/Internal/Extensions/Page+Defaults.swift index f852a71..2b60080 100644 --- a/Services/Website/Sources/Library/Internal/Extensions/Page+Defaults.swift +++ b/Services/Website/Sources/Library/Internal/Extensions/Page+Defaults.swift @@ -6,6 +6,16 @@ import Localization /// The site-wide defaults shared by every page of the website. extension Page { + // MARK: Constants + + /// The fonts preloaded on every page: one per face the stylesheets actually render. + /// + /// Listed rather than derived from every WOFF2 in ``StaticFile/all``: a subset gated by a `unicode-range` no page reaches would add a + /// download that never otherwise happens. Empty until the site ships fonts of its own. + static var preloadedFonts: [StaticFile] { + [] + } + // MARK: Computed /// The document language, derived from the page's locale and falling back to the default language. @@ -14,9 +24,29 @@ extension Page { ?? LanguageList().default } - /// The icon, manifest, and theme colour metadata shared by every page of the website. - @HTMLBuilder + /// The site-wide head metadata; a page that adds tags of its own composes ``siteMetadata`` rather than replacing it. var metadata: some HTML { + siteMetadata + } + + /// The ``preloadedFonts`` links, then the icon, manifest, and theme colour metadata shared by every page of the website. + /// + /// A preload URL must match the stylesheet's `@font-face` source exactly — unversioned, with `crossorigin` — or the browser fetches the + /// font twice. + @HTMLBuilder + var siteMetadata: some HTML { + for file in Self.preloadedFonts { + link( + .rel("preload"), + .href(file.urlPath(for: .woff2)), + .as(.font), + .custom( + name: "type", + value: AssetExtension.woff2.contentType + ), + .crossorigin(.anonymous) + ) + } link( .rel(.icon), .href(StaticFile.favicon.urlPath( diff --git a/Services/Website/Tests/Library/Cases/Internal/Pages/IndexPageTests.swift b/Services/Website/Tests/Library/Cases/Internal/Pages/IndexPageTests.swift index d06606a..8f81109 100644 --- a/Services/Website/Tests/Library/Cases/Internal/Pages/IndexPageTests.swift +++ b/Services/Website/Tests/Library/Cases/Internal/Pages/IndexPageTests.swift @@ -32,6 +32,13 @@ struct IndexPageTests { #expect(html.contains("/js/index.js")) } + @Test + func `renders no font preloads until fonts are declared`() { + // The template ships no fonts, so a preload link here would point at a file the service does not serve. + #expect(IndexPage.preloadedFonts.isEmpty) + #expect(!IndexPage(locale: .init(identifier: "en")).render().contains(#"rel="preload""#)) + } + @Test func `renders versioned asset URLs when given a version`() { let html = IndexPage( diff --git a/Services/Website/Tests/Library/Cases/Internal/Types/StaticFileTests.swift b/Services/Website/Tests/Library/Cases/Internal/Types/StaticFileTests.swift index 726c34f..9c61e8f 100644 --- a/Services/Website/Tests/Library/Cases/Internal/Types/StaticFileTests.swift +++ b/Services/Website/Tests/Library/Cases/Internal/Types/StaticFileTests.swift @@ -24,6 +24,15 @@ struct StaticFileTests { #expect(file.fileExtensions.map(file.relativePath) == paths) } + /// A preload URL must match the stylesheet's `@font-face` source exactly, so it carries the extension's folder and no version query. + @Test + func `unversioned font paths`() { + let font = File("a-font-400", as: .woff2) + + #expect(font.relativePath(for: .woff2) == "font/a-font-400.woff2") + #expect(font.urlPath(for: .woff2) == "/font/a-font-400.woff2") + } + // MARK: Constants tests @Test