Integrated Analytics into the Infrastructure package (#40)
This PR contains the work done to define the `Analytics` type into the _Infrastructure_ package and also, to integrate this type into its `Page` protocol. Reviewed-on: rock-n-code/loud-amsterdam#40 Co-authored-by: Javier Cicchelli <javier@rock-n-code.com>
This commit is contained in:
@@ -4,8 +4,8 @@ import Foundation
|
||||
/// A page of a website: an HTML document with the shared scaffolding assembled around the page's content.
|
||||
///
|
||||
/// A conforming page supplies its locale, its title, the stylesheets and scripts it needs, its head metadata, and its content; the protocol assembles the
|
||||
/// rest of the document around them: the viewport declaration, the summary, canonical, and social card tags, the structured data script, and the
|
||||
/// metadata followed by the stylesheet links in the head, and the content followed by the script tags in the body.
|
||||
/// rest of the document around them: the viewport declaration, the summary, canonical, and social card tags, the structured data script, the analytics
|
||||
/// tracker script, and the metadata followed by the stylesheet links in the head, and the content followed by the script tags in the body.
|
||||
public protocol Page: HTMLDocument, Sendable {
|
||||
|
||||
// MARK: Associated types
|
||||
@@ -18,6 +18,9 @@ public protocol Page: HTMLDocument, Sendable {
|
||||
|
||||
// MARK: Properties
|
||||
|
||||
/// The analytics tracker embedded as a deferred script in the document head, or `nil` (the default) to omit it.
|
||||
var analytics: Analytics? { get }
|
||||
|
||||
/// The version token appended to the page's asset URLs, or `nil` to leave them unversioned.
|
||||
var assetVersion: String? { get }
|
||||
|
||||
@@ -77,14 +80,15 @@ public extension Page {
|
||||
}
|
||||
}
|
||||
|
||||
/// The viewport declaration, the ``summary``, ``canonicalURL``, and ``socialCard`` tags and the ``structuredData`` script
|
||||
/// (when provided), and the ``metadata`` followed by the ``stylesheets`` links, placed in the document head.
|
||||
/// The viewport declaration, the ``summary``, ``canonicalURL``, and ``socialCard`` tags, the ``structuredData`` script and the
|
||||
/// ``analytics`` tracker script (when provided), and the ``metadata`` followed by the ``stylesheets`` links, placed in the document head.
|
||||
///
|
||||
/// The charset declaration is omitted: Elementary's `HTMLDocument` scaffolding already emits `<meta charset="UTF-8">` before this markup,
|
||||
/// and HTML5 allows only one.
|
||||
///
|
||||
/// The structured data is an inert data block — browsers never execute it, so a site's `Content-Security-Policy` does not apply to it —
|
||||
/// that search engines read for the organization's name, logo, and profiles.
|
||||
/// that search engines read for the organization's name, logo, and profiles. The analytics tracker, by contrast, is an executable script the
|
||||
/// policy must allow, and it is `defer`red so it never delays the page render; each behavior flag renders its `data-` attribute only when enabled.
|
||||
@HTMLBuilder
|
||||
var head: some HTML {
|
||||
meta(
|
||||
@@ -127,6 +131,19 @@ public extension Page {
|
||||
}
|
||||
}
|
||||
|
||||
if let analytics {
|
||||
script(
|
||||
.defer,
|
||||
.src(analytics.scriptURL)
|
||||
) {}
|
||||
.attributes(contentsOf: analytics.attributes.map {
|
||||
.custom(
|
||||
name: $0.name,
|
||||
value: $0.value
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
metadata
|
||||
|
||||
for file in stylesheets {
|
||||
@@ -140,6 +157,11 @@ public extension Page {
|
||||
}
|
||||
}
|
||||
|
||||
/// The analytics tracker is omitted unless the page provides one.
|
||||
var analytics: Analytics? {
|
||||
nil
|
||||
}
|
||||
|
||||
/// The social card is omitted unless the page provides one.
|
||||
var socialCard: SocialCard? {
|
||||
nil
|
||||
|
||||
@@ -0,0 +1,97 @@
|
||||
/// 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
|
||||
/// when the page has none. The attribute names follow the Umami tracker convention. The three behavior flags default to on, and each renders its
|
||||
/// `data-` attribute only when enabled, since the tracker treats an absent attribute as off.
|
||||
public struct Analytics: Sendable {
|
||||
|
||||
// MARK: Type aliases
|
||||
|
||||
public typealias Attribute = (name: String, value: String)
|
||||
|
||||
// MARK: Properties
|
||||
|
||||
/// Whether the tracker drops the URL fragment from reported pageviews, so fragment navigation does not split a page's views.
|
||||
public let excludeHash: Bool
|
||||
|
||||
/// Whether the tracker honors the visitor's browser Do Not Track preference.
|
||||
public let doNotTrack: Bool
|
||||
|
||||
/// The comma-delimited domains the tracker reports from; visits from any other host are ignored.
|
||||
public let domains: String
|
||||
|
||||
/// Whether the tracker collects Core Web Vitals from visitors (requires an Umami instance at v3.1 or newer).
|
||||
public let performance: Bool
|
||||
|
||||
/// The URL the tracker script is loaded from.
|
||||
public let scriptURL: String
|
||||
|
||||
/// The analytics website identifier the tracker reports as.
|
||||
public let websiteID: String
|
||||
|
||||
// MARK: Initializers
|
||||
|
||||
/// Creates an analytics configuration.
|
||||
/// - Parameters:
|
||||
/// - scriptURL: the URL the tracker script is loaded from.
|
||||
/// - websiteID: the analytics website identifier the tracker reports as.
|
||||
/// - domains: the comma-delimited domains the tracker reports from; visits from any other host are ignored.
|
||||
/// - excludeHash: whether the tracker drops the URL fragment from reported pageviews; defaults to `true`.
|
||||
/// - doNotTrack: whether the tracker honors the visitor's browser Do Not Track preference; defaults to `true`.
|
||||
/// - performance: whether the tracker collects Core Web Vitals (requires Umami v3.1 or newer); defaults to `true`.
|
||||
public init(
|
||||
scriptURL: String,
|
||||
websiteID: String,
|
||||
domains: String,
|
||||
excludeHash: Bool = true,
|
||||
doNotTrack: Bool = true,
|
||||
performance: Bool = true
|
||||
) {
|
||||
self.scriptURL = scriptURL
|
||||
self.websiteID = websiteID
|
||||
self.domains = domains
|
||||
self.excludeHash = excludeHash
|
||||
self.doNotTrack = doNotTrack
|
||||
self.performance = performance
|
||||
}
|
||||
|
||||
// MARK: Computed
|
||||
|
||||
/// The tracker script's attributes, in a stable order: the website id and the reporting domains, then each enabled behavior flag.
|
||||
///
|
||||
/// A disabled flag is left out entirely, since the tracker treats an absent attribute as off. Each `name` is a full attribute name following the
|
||||
/// Umami `data-` convention, which a page applies to the deferred script verbatim — so the page renders the tracker without knowing its shape.
|
||||
public var attributes: [Attribute] {
|
||||
var attributes = [(
|
||||
name: "data-website-id",
|
||||
value: websiteID
|
||||
), (
|
||||
name: "data-domains",
|
||||
value: domains
|
||||
)]
|
||||
|
||||
if excludeHash {
|
||||
attributes.append((
|
||||
name: "data-exclude-hash",
|
||||
value: "true"
|
||||
))
|
||||
}
|
||||
|
||||
if doNotTrack {
|
||||
attributes.append((
|
||||
name: "data-do-not-track",
|
||||
value: "true"
|
||||
))
|
||||
}
|
||||
|
||||
if performance {
|
||||
attributes.append((
|
||||
name: "data-performance",
|
||||
value: "true"
|
||||
))
|
||||
}
|
||||
|
||||
return attributes
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user