Implemented the analytics preconnect hint on the Page protocol in the Infrastructure package.
This commit is contained in:
@@ -76,9 +76,9 @@ public extension Page {
|
|||||||
content
|
content
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The viewport declaration, the ``summary``, ``canonicalURL``, and ``socialCard`` tags, the ``structuredData`` script and the
|
/// The viewport declaration, the ``analytics`` origin preconnect hint, the ``summary``, ``canonicalURL``, and ``socialCard``
|
||||||
/// ``analytics`` tracker script (when provided), and the ``metadata`` followed by the ``stylesheets`` links and the deferred
|
/// tags, the ``structuredData`` script and the ``analytics`` tracker script (when provided), and the ``metadata`` followed by the
|
||||||
/// ``scripts`` tags, placed in the document head.
|
/// ``stylesheets`` links and the deferred ``scripts`` tags, placed in the document head.
|
||||||
///
|
///
|
||||||
/// The charset declaration is omitted: Elementary's `HTMLDocument` scaffolding already emits `<meta charset="UTF-8">` before this markup,
|
/// The charset declaration is omitted: Elementary's `HTMLDocument` scaffolding already emits `<meta charset="UTF-8">` before this markup,
|
||||||
/// and HTML5 allows only one.
|
/// and HTML5 allows only one.
|
||||||
@@ -93,6 +93,14 @@ public extension Page {
|
|||||||
.content("width=device-width, initial-scale=1")
|
.content("width=device-width, initial-scale=1")
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// Rendered first so the cross-origin handshake starts before the parser reaches the tracker script tag.
|
||||||
|
if let origin = analytics?.origin {
|
||||||
|
link(
|
||||||
|
.rel("preconnect"),
|
||||||
|
.href(origin)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
if let summary {
|
if let summary {
|
||||||
meta(
|
meta(
|
||||||
.name(.description),
|
.name(.description),
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
import Foundation
|
||||||
|
|
||||||
/// The analytics tracker a page embeds: where the script loads from, which site it reports as, which domains it reports from, and how it behaves.
|
/// The analytics tracker a page embeds: where the script loads from, which site it reports as, which domains it reports from, and how it behaves.
|
||||||
///
|
///
|
||||||
/// A page carries it as an optional value so the ``Page`` scaffolding renders the tracker's deferred `<script>` in the document head, or omits it
|
/// A page carries it as an optional value so the ``Page`` scaffolding renders the tracker's deferred `<script>` in the document head, or omits it
|
||||||
@@ -94,4 +96,22 @@ public struct Analytics: Sendable {
|
|||||||
return attributes
|
return attributes
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// The origin the tracker is served from (scheme and host, plus any explicit port), derived from the ``scriptURL``, or `nil` when the
|
||||||
|
/// URL carries no scheme or host.
|
||||||
|
///
|
||||||
|
/// A page renders it as a `preconnect` hint before the tracker script, so the connection handshake starts as early as possible.
|
||||||
|
public var origin: String? {
|
||||||
|
guard
|
||||||
|
let url = URL(string: scriptURL),
|
||||||
|
let scheme = url.scheme,
|
||||||
|
let host = url.host
|
||||||
|
else {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
let port = url.port.map { ":\($0)" } ?? ""
|
||||||
|
|
||||||
|
return "\(scheme)://\(host)\(port)"
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -131,10 +131,11 @@ struct PageTests {
|
|||||||
let html = StubPage().render()
|
let html = StubPage().render()
|
||||||
|
|
||||||
#expect(!html.contains("data-website-id"))
|
#expect(!html.contains("data-website-id"))
|
||||||
|
#expect(!html.contains(#"rel="preconnect""#))
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
func `renders the analytics tracker when provided`() {
|
func `renders the analytics tracker when provided`() throws {
|
||||||
let html = StubPage(analytics: .init(
|
let html = StubPage(analytics: .init(
|
||||||
scriptURL: "https://analytics.example.com/script",
|
scriptURL: "https://analytics.example.com/script",
|
||||||
websiteID: "0000-website-id",
|
websiteID: "0000-website-id",
|
||||||
@@ -142,6 +143,12 @@ struct PageTests {
|
|||||||
)).render()
|
)).render()
|
||||||
|
|
||||||
#expect(html.contains(#"<script defer src="https://analytics.example.com/script" data-website-id="0000-website-id" data-domains="example.com" data-exclude-hash="true" data-do-not-track="true" data-performance="true"></script>"#))
|
#expect(html.contains(#"<script defer src="https://analytics.example.com/script" data-website-id="0000-website-id" data-domains="example.com" data-exclude-hash="true" data-do-not-track="true" data-performance="true"></script>"#))
|
||||||
|
|
||||||
|
// The preconnect hint warms the tracker origin's connection before the parser reaches the script tag.
|
||||||
|
let preconnect = try #require(html.range(of: #"<link rel="preconnect" href="https://analytics.example.com">"#))
|
||||||
|
let script = try #require(html.range(of: #"<script defer src="https://analytics.example.com/script""#))
|
||||||
|
|
||||||
|
#expect(preconnect.lowerBound < script.lowerBound)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
|||||||
@@ -34,6 +34,39 @@ struct AnalyticsTests {
|
|||||||
])
|
])
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
func `derives its origin from the script URL`() {
|
||||||
|
let analytics = Analytics(
|
||||||
|
scriptURL: "https://analytics.example.com/script",
|
||||||
|
websiteID: "id-123",
|
||||||
|
domains: "example.com"
|
||||||
|
)
|
||||||
|
|
||||||
|
#expect(analytics.origin == "https://analytics.example.com")
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
func `keeps an explicit port in its origin`() {
|
||||||
|
let analytics = Analytics(
|
||||||
|
scriptURL: "http://localhost:3000/script",
|
||||||
|
websiteID: "id-123",
|
||||||
|
domains: "localhost"
|
||||||
|
)
|
||||||
|
|
||||||
|
#expect(analytics.origin == "http://localhost:3000")
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
func `carries no origin for a script URL without a scheme or host`() {
|
||||||
|
let analytics = Analytics(
|
||||||
|
scriptURL: "/script",
|
||||||
|
websiteID: "id-123",
|
||||||
|
domains: "example.com"
|
||||||
|
)
|
||||||
|
|
||||||
|
#expect(analytics.origin == nil)
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
func `omits the disabled behavior flags`() {
|
func `omits the disabled behavior flags`() {
|
||||||
let analytics = Analytics(
|
let analytics = Analytics(
|
||||||
|
|||||||
Reference in New Issue
Block a user