@escape-game-over/atlas 0.1.60 → 0.1.62
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/docs/client-scripts.md +4 -13
- package/package.json +1 -1
- package/src/astro/ConsentBanner.astro +19 -18
- package/src/astro/ConsentElement.astro +8 -2
- package/src/astro/Document.astro +4 -6
- package/src/astro/axon-pixel.ts +0 -11
- package/src/astro/clarity.ts +5 -12
- package/src/astro/consent-element.ts +3 -0
- package/src/astro/consent-vendors.ts +100 -0
- package/src/astro/dev-log.ts +3 -3
- package/src/astro/drip.ts +0 -8
- package/src/astro/element.ts +2 -2
- package/src/astro/filters.ts +2 -2
- package/src/astro/meta-pixel.ts +0 -15
- package/src/astro/no-client-router.ts +35 -0
- package/src/astro/site-routes.ts +7 -1
- package/src/astro/snap-pixel.ts +0 -15
- package/src/astro/tiktok-pixel.ts +0 -15
- package/src/astro/AxonPixel.astro +0 -18
- package/src/astro/Clarity.astro +0 -18
- package/src/astro/Drip.astro +0 -18
- package/src/astro/MetaPixel.astro +0 -18
- package/src/astro/SnapPixel.astro +0 -18
- package/src/astro/TikTokPixel.astro +0 -18
package/docs/client-scripts.md
CHANGED
|
@@ -63,9 +63,7 @@ reloads on nearly every back. An inline script at the end of `<body>` saves the
|
|
|
63
63
|
position on the history entry once scrolling settles, and puts it back before
|
|
64
64
|
the first paint. On the entry, not in storage keyed by URL, so a link to the same
|
|
65
65
|
page still opens at the top. Never on `pagehide`: WebKit fires it after moving to
|
|
66
|
-
the previous entry, and the write wipes the position about to be restored.
|
|
67
|
-
site on Astro's `ClientRouter` turns it off; the router keeps its own position
|
|
68
|
-
in the same history state.
|
|
66
|
+
the previous entry, and the write wipes the position about to be restored.
|
|
69
67
|
|
|
70
68
|
**Browser code has one import path: `@escape-game-over/atlas/client`**, which
|
|
71
69
|
re-exports every module above. None of it touches the DOM at import, so an
|
|
@@ -102,16 +100,9 @@ undo and then the function again — so it has to be able to run twice. State th
|
|
|
102
100
|
must survive a move goes in a `WeakMap` keyed by `root`, which is what the
|
|
103
101
|
carousel example does with its index.
|
|
104
102
|
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
gets a live list and dead controls: the markup was swapped, the handlers still
|
|
109
|
-
point at what was there before. Driving `attach` from `astro:page-load` and
|
|
110
|
-
calling its return on teardown is the fix, and it is why none of these modules
|
|
111
|
-
does anything at import time.
|
|
112
|
-
|
|
113
|
-
Note that the abort half alone does not help. It makes the breakage look
|
|
114
|
-
handled. Without a re-`attach` there is simply nothing wired.
|
|
103
|
+
Astro's `ClientRouter`, which swaps pages without reloading them, is refused at
|
|
104
|
+
build time by `siteRoutes()`: every module here, and every analytics vendor,
|
|
105
|
+
assumes one full page load per navigation.
|
|
115
106
|
|
|
116
107
|
## `filters`
|
|
117
108
|
|
package/package.json
CHANGED
|
@@ -5,36 +5,37 @@
|
|
|
5
5
|
* buttons.
|
|
6
6
|
*
|
|
7
7
|
* Renders nothing — and ships no script — when this site's analytics need no
|
|
8
|
-
* permission.
|
|
8
|
+
* permission. Otherwise the banner starts hidden and shows only to a visitor
|
|
9
9
|
* with no answer on record, or when a `data-consent-reopen` control is pressed.
|
|
10
|
+
*
|
|
11
|
+
* It also starts the vendors that wait on the answer — the Meta, TikTok, Snap
|
|
12
|
+
* and Axon pixels, Drip and Clarity — fetching each one's code only once its
|
|
13
|
+
* category is granted.
|
|
10
14
|
*/
|
|
11
15
|
import type { HTMLAttributes } from "astro/types";
|
|
12
16
|
import { type AnalyticsSettings, consentRequired } from "../analytics/index.ts";
|
|
13
|
-
import AxonPixel from "./AxonPixel.astro";
|
|
14
|
-
import Clarity from "./Clarity.astro";
|
|
15
17
|
import ConsentElement from "./ConsentElement.astro";
|
|
16
|
-
import
|
|
17
|
-
import MetaPixel from "./MetaPixel.astro";
|
|
18
|
-
import SnapPixel from "./SnapPixel.astro";
|
|
19
|
-
import TikTokPixel from "./TikTokPixel.astro";
|
|
18
|
+
import type { ConsentVendors } from "./consent-vendors.ts";
|
|
20
19
|
|
|
21
20
|
type Props = HTMLAttributes<"div"> & {
|
|
22
21
|
readonly analytics: AnalyticsSettings | undefined;
|
|
23
22
|
};
|
|
24
23
|
|
|
25
24
|
const { analytics, ...attrs } = Astro.props;
|
|
25
|
+
const vendors: ConsentVendors = {
|
|
26
|
+
meta: analytics?.meta,
|
|
27
|
+
tiktok: analytics?.tiktok,
|
|
28
|
+
snapchat: analytics?.snapchat,
|
|
29
|
+
axon: analytics?.axon,
|
|
30
|
+
drip: analytics?.drip,
|
|
31
|
+
clarity: analytics?.clarity,
|
|
32
|
+
};
|
|
26
33
|
---
|
|
27
34
|
|
|
28
35
|
{consentRequired(analytics) && (
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
{analytics?.tiktok && <TikTokPixel settings={analytics.tiktok} />}
|
|
35
|
-
{analytics?.snapchat && <SnapPixel settings={analytics.snapchat} />}
|
|
36
|
-
{analytics?.axon && <AxonPixel settings={analytics.axon} />}
|
|
37
|
-
{analytics?.drip && <Drip settings={analytics.drip} />}
|
|
38
|
-
{analytics?.clarity && <Clarity settings={analytics.clarity} />}
|
|
39
|
-
</>
|
|
36
|
+
<ConsentElement
|
|
37
|
+
{...attrs}
|
|
38
|
+
vendors={vendors}>
|
|
39
|
+
<slot />
|
|
40
|
+
</ConsentElement>
|
|
40
41
|
)}
|
|
@@ -3,15 +3,21 @@
|
|
|
3
3
|
import type { HTMLAttributes } from "astro/types";
|
|
4
4
|
import AtlasElement from "./AtlasElement.astro";
|
|
5
5
|
import { consentBanner } from "./consent-element.ts";
|
|
6
|
+
import type { ConsentVendors } from "./consent-vendors.ts";
|
|
6
7
|
|
|
7
|
-
type Props = HTMLAttributes<"div"
|
|
8
|
+
type Props = HTMLAttributes<"div"> & {
|
|
9
|
+
readonly vendors: ConsentVendors;
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
const { vendors, ...attrs } = Astro.props;
|
|
8
13
|
---
|
|
9
14
|
|
|
10
15
|
<script src="./consent-element.ts" />
|
|
11
16
|
|
|
12
17
|
<AtlasElement
|
|
13
18
|
of={consentBanner}
|
|
14
|
-
{...
|
|
19
|
+
{...attrs}
|
|
20
|
+
data-vendors={JSON.stringify(vendors)}
|
|
15
21
|
hidden
|
|
16
22
|
role="dialog"
|
|
17
23
|
aria-modal="false"
|
package/src/astro/Document.astro
CHANGED
|
@@ -6,9 +6,7 @@
|
|
|
6
6
|
* load ahead of the analytics goes there; `head` comes after.
|
|
7
7
|
*
|
|
8
8
|
* It also restores the scroll position on back and reload; see
|
|
9
|
-
* `docs/client-scripts.md`.
|
|
10
|
-
* `restoreScroll={false}`: the router keeps its own position in the same
|
|
11
|
-
* history state.
|
|
9
|
+
* `docs/client-scripts.md`. `restoreScroll={false}` leaves it to the browser.
|
|
12
10
|
*
|
|
13
11
|
* A site with a lead form passes `rememberCampaign`, so the `utm_*` tags a
|
|
14
12
|
* visit landed with survive to the page the form is on; see `attribution.ts`.
|
|
@@ -44,10 +42,10 @@ interface Props {
|
|
|
44
42
|
/** `lang` and `dir` are the page's locale's, from `meta`. */
|
|
45
43
|
readonly html?: Omit<HTMLAttributes<"html">, "lang" | "dir">;
|
|
46
44
|
readonly body?: HTMLAttributes<"body">;
|
|
47
|
-
/** Defaults to `true`; `false` for a site on `ClientRouter`. */
|
|
48
|
-
readonly restoreScroll?: boolean;
|
|
49
45
|
/** Off unless stated: a site with no lead form has nothing to keep them for. */
|
|
50
46
|
readonly rememberCampaign?: boolean;
|
|
47
|
+
/** Defaults to `true`; `false` leaves scroll restoration to the browser. */
|
|
48
|
+
readonly restoreScroll?: boolean;
|
|
51
49
|
/** Defaults to `true`. */
|
|
52
50
|
readonly rememberLocale?: boolean;
|
|
53
51
|
}
|
|
@@ -57,8 +55,8 @@ const {
|
|
|
57
55
|
fonts = {},
|
|
58
56
|
html,
|
|
59
57
|
body,
|
|
60
|
-
restoreScroll = true,
|
|
61
58
|
rememberCampaign = false,
|
|
59
|
+
restoreScroll = true,
|
|
62
60
|
rememberLocale = true,
|
|
63
61
|
} = Astro.props;
|
|
64
62
|
|
package/src/astro/axon-pixel.ts
CHANGED
|
@@ -1,8 +1,5 @@
|
|
|
1
1
|
import { appendScript } from "./append-script.ts";
|
|
2
2
|
import type { ConsentGated } from "./consent-gate.ts";
|
|
3
|
-
import { whenGranted } from "./consent-gate.ts";
|
|
4
|
-
import { element } from "./element.ts";
|
|
5
|
-
import { data } from "./ref.ts";
|
|
6
3
|
|
|
7
4
|
/** Axon's `axon`: a queue until its scripts arrive and replay it. */
|
|
8
5
|
interface Axon {
|
|
@@ -62,11 +59,3 @@ export function axonPixel(eventKey: string): ConsentGated {
|
|
|
62
59
|
},
|
|
63
60
|
};
|
|
64
61
|
}
|
|
65
|
-
|
|
66
|
-
/** Internal to `AxonPixel.astro`. */
|
|
67
|
-
export const axonPixelElement = element(
|
|
68
|
-
"atlas-axon-pixel",
|
|
69
|
-
({ root, signal }) => {
|
|
70
|
-
whenGranted("marketing", axonPixel(data(root, "event-key")), signal);
|
|
71
|
-
}
|
|
72
|
-
);
|
package/src/astro/clarity.ts
CHANGED
|
@@ -1,8 +1,5 @@
|
|
|
1
1
|
import { appendScript } from "./append-script.ts";
|
|
2
2
|
import type { ConsentChoices } from "./consent.ts";
|
|
3
|
-
import { withConsent } from "./consent-gate.ts";
|
|
4
|
-
import { element } from "./element.ts";
|
|
5
|
-
import { data } from "./ref.ts";
|
|
6
3
|
|
|
7
4
|
/** Clarity's `clarity`: a queue until its script arrives and replays it. */
|
|
8
5
|
interface Clarity {
|
|
@@ -39,22 +36,18 @@ const consentv2 = (choices: ConsentChoices) => ({
|
|
|
39
36
|
});
|
|
40
37
|
|
|
41
38
|
/**
|
|
42
|
-
*
|
|
39
|
+
* Clarity, loaded with the first answer passed to what this returns, which
|
|
40
|
+
* `startVendors` calls with the answer so far and then every new one. Stated
|
|
43
41
|
* even when denied: a Clarity project set to use cookies by default would
|
|
44
42
|
* otherwise set them before anyone was asked.
|
|
45
43
|
*/
|
|
46
|
-
export function clarity(projectId: string
|
|
44
|
+
export function clarity(projectId: string): (choices: ConsentChoices) => void {
|
|
47
45
|
let loaded = false;
|
|
48
|
-
|
|
46
|
+
return (choices) => {
|
|
49
47
|
const queue = defineClarity();
|
|
50
48
|
queue("consentv2", consentv2(choices));
|
|
51
49
|
if (loaded) return;
|
|
52
50
|
loaded = true;
|
|
53
51
|
appendScript(`https://www.clarity.ms/tag/${projectId}`);
|
|
54
|
-
}
|
|
52
|
+
};
|
|
55
53
|
}
|
|
56
|
-
|
|
57
|
-
/** Internal to `Clarity.astro`. */
|
|
58
|
-
export const clarityElement = element("atlas-clarity", ({ root, signal }) => {
|
|
59
|
-
clarity(data(root, "project-id"), signal);
|
|
60
|
-
});
|
|
@@ -7,6 +7,7 @@ import {
|
|
|
7
7
|
readConsent,
|
|
8
8
|
recordConsent,
|
|
9
9
|
} from "./consent.ts";
|
|
10
|
+
import { startVendors } from "./consent-vendors.ts";
|
|
10
11
|
import { element } from "./element.ts";
|
|
11
12
|
import { data, ref, refs } from "./ref.ts";
|
|
12
13
|
|
|
@@ -38,6 +39,8 @@ import { data, ref, refs } from "./ref.ts";
|
|
|
38
39
|
* withdraw.
|
|
39
40
|
*/
|
|
40
41
|
export const consentBanner = element(CONSENT_TAG, ({ root, signal }) => {
|
|
42
|
+
startVendors(JSON.parse(data(root, "vendors")), signal);
|
|
43
|
+
|
|
41
44
|
const boxes = refs<HTMLInputElement>(root, "[data-consent-category]").map(
|
|
42
45
|
(box) => {
|
|
43
46
|
const category = data(box, "consent-category");
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
import type { AnalyticsSettings } from "../analytics/index.ts";
|
|
2
|
+
import type { ConsentChoices } from "./consent.ts";
|
|
3
|
+
import { type ConsentGated, whenGranted, withConsent } from "./consent-gate.ts";
|
|
4
|
+
import { reportDevError } from "./dev-log.ts";
|
|
5
|
+
|
|
6
|
+
/** The vendors the banner starts: the analytics settings loaded in the browser. */
|
|
7
|
+
export type ConsentVendors = Pick<
|
|
8
|
+
AnalyticsSettings,
|
|
9
|
+
"meta" | "tiktok" | "snapchat" | "axon" | "drip" | "clarity"
|
|
10
|
+
>;
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* A vendor whose code is fetched on its first grant, not with the page. Calls
|
|
14
|
+
* wait on the one import, so they reach the vendor in the order they were made.
|
|
15
|
+
*/
|
|
16
|
+
function lazy(load: () => Promise<ConsentGated>): ConsentGated {
|
|
17
|
+
let vendor: Promise<ConsentGated | undefined> | undefined;
|
|
18
|
+
const loaded = () => {
|
|
19
|
+
vendor ??= load().catch((error) => {
|
|
20
|
+
reportDevError("consent vendors", error);
|
|
21
|
+
return undefined;
|
|
22
|
+
});
|
|
23
|
+
return vendor;
|
|
24
|
+
};
|
|
25
|
+
return {
|
|
26
|
+
grant: () => {
|
|
27
|
+
loaded().then((ready) => ready?.grant());
|
|
28
|
+
},
|
|
29
|
+
revoke: () => {
|
|
30
|
+
vendor?.then((ready) => ready?.revoke?.());
|
|
31
|
+
},
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Starts every configured vendor. Nothing vendor-specific is downloaded until
|
|
37
|
+
* its consent category is granted — except Clarity, which runs cookieless
|
|
38
|
+
* without consent and so loads with the page.
|
|
39
|
+
*/
|
|
40
|
+
export function startVendors(
|
|
41
|
+
vendors: ConsentVendors,
|
|
42
|
+
signal?: AbortSignal
|
|
43
|
+
): void {
|
|
44
|
+
const { meta, tiktok, snapchat, axon, drip, clarity } = vendors;
|
|
45
|
+
if (meta !== undefined) {
|
|
46
|
+
whenGranted(
|
|
47
|
+
"marketing",
|
|
48
|
+
lazy(async () =>
|
|
49
|
+
(await import("./meta-pixel.ts")).metaPixel(meta.pixelIds)
|
|
50
|
+
),
|
|
51
|
+
signal
|
|
52
|
+
);
|
|
53
|
+
}
|
|
54
|
+
if (tiktok !== undefined) {
|
|
55
|
+
whenGranted(
|
|
56
|
+
"marketing",
|
|
57
|
+
lazy(async () =>
|
|
58
|
+
(await import("./tiktok-pixel.ts")).tiktokPixel(tiktok.pixelIds)
|
|
59
|
+
),
|
|
60
|
+
signal
|
|
61
|
+
);
|
|
62
|
+
}
|
|
63
|
+
if (snapchat !== undefined) {
|
|
64
|
+
whenGranted(
|
|
65
|
+
"marketing",
|
|
66
|
+
lazy(async () =>
|
|
67
|
+
(await import("./snap-pixel.ts")).snapPixel(snapchat.pixelIds)
|
|
68
|
+
),
|
|
69
|
+
signal
|
|
70
|
+
);
|
|
71
|
+
}
|
|
72
|
+
if (axon !== undefined) {
|
|
73
|
+
whenGranted(
|
|
74
|
+
"marketing",
|
|
75
|
+
lazy(async () =>
|
|
76
|
+
(await import("./axon-pixel.ts")).axonPixel(axon.eventKey)
|
|
77
|
+
),
|
|
78
|
+
signal
|
|
79
|
+
);
|
|
80
|
+
}
|
|
81
|
+
if (drip !== undefined) {
|
|
82
|
+
whenGranted(
|
|
83
|
+
"marketing",
|
|
84
|
+
lazy(async () => (await import("./drip.ts")).drip(drip.accountId)),
|
|
85
|
+
signal
|
|
86
|
+
);
|
|
87
|
+
}
|
|
88
|
+
if (clarity !== undefined) {
|
|
89
|
+
let apply: Promise<((choices: ConsentChoices) => void) | undefined>;
|
|
90
|
+
withConsent((choices) => {
|
|
91
|
+
apply ??= import("./clarity.ts")
|
|
92
|
+
.then((module) => module.clarity(clarity.projectId))
|
|
93
|
+
.catch((error) => {
|
|
94
|
+
reportDevError("consent vendors", error);
|
|
95
|
+
return undefined;
|
|
96
|
+
});
|
|
97
|
+
apply.then((ready) => ready?.(choices));
|
|
98
|
+
}, signal);
|
|
99
|
+
}
|
|
100
|
+
}
|
package/src/astro/dev-log.ts
CHANGED
|
@@ -41,9 +41,9 @@ function panel(): HTMLElement {
|
|
|
41
41
|
const existing = document.getElementById(PANEL_ID);
|
|
42
42
|
if (existing) return existing;
|
|
43
43
|
|
|
44
|
-
// Either nothing has failed yet, or the
|
|
45
|
-
//
|
|
46
|
-
//
|
|
44
|
+
// Either nothing has failed yet, or the panel was removed from the page.
|
|
45
|
+
// Either way every line in `seen` is detached, and counting into one would
|
|
46
|
+
// report nothing.
|
|
47
47
|
seen.clear();
|
|
48
48
|
|
|
49
49
|
const created = document.createElement("div");
|
package/src/astro/drip.ts
CHANGED
|
@@ -1,8 +1,5 @@
|
|
|
1
1
|
import { appendScript } from "./append-script.ts";
|
|
2
2
|
import type { ConsentGated } from "./consent-gate.ts";
|
|
3
|
-
import { whenGranted } from "./consent-gate.ts";
|
|
4
|
-
import { element } from "./element.ts";
|
|
5
|
-
import { data } from "./ref.ts";
|
|
6
3
|
|
|
7
4
|
/** Drip's globals: a queue of calls, and the account its script reports to. */
|
|
8
5
|
interface DripWindow {
|
|
@@ -30,8 +27,3 @@ export function drip(accountId: string): ConsentGated {
|
|
|
30
27
|
},
|
|
31
28
|
};
|
|
32
29
|
}
|
|
33
|
-
|
|
34
|
-
/** Internal to `Drip.astro`. */
|
|
35
|
-
export const dripElement = element("atlas-drip", ({ root, signal }) => {
|
|
36
|
-
whenGranted("marketing", drip(data(root, "account-id")), signal);
|
|
37
|
-
});
|
package/src/astro/element.ts
CHANGED
|
@@ -48,8 +48,8 @@ export interface ElementTag<Tag extends string = string> {
|
|
|
48
48
|
* template imports the same file.
|
|
49
49
|
*
|
|
50
50
|
* - **An element can enter the page more than once.** Moving it runs the undo
|
|
51
|
-
* and `connect` again
|
|
52
|
-
*
|
|
51
|
+
* and `connect` again. State that must survive belongs in a `WeakMap` keyed
|
|
52
|
+
* by `root`.
|
|
53
53
|
* - **The page's script must stay a bundled module** (a plain `<script>`), so
|
|
54
54
|
* the element has its children when it upgrades.
|
|
55
55
|
* - **Failures show in dev.** A `connect` that throws leaves that one element
|
package/src/astro/filters.ts
CHANGED
|
@@ -336,8 +336,8 @@ export function filters<const F extends FieldMap, T>(
|
|
|
336
336
|
const query = parts.join("&");
|
|
337
337
|
// The bare path when nothing is left, rather than a trailing `?`.
|
|
338
338
|
const url = query === "" ? window.location.pathname : `?${query}`;
|
|
339
|
-
// `null` on a pushed step
|
|
340
|
-
//
|
|
339
|
+
// `null` on a pushed step; a replaced entry keeps whatever state it
|
|
340
|
+
// already had, such as the position `ScrollRestore` saved there.
|
|
341
341
|
if (history === "push") window.history.pushState(null, "", url);
|
|
342
342
|
else window.history.replaceState(window.history.state, "", url);
|
|
343
343
|
}
|
package/src/astro/meta-pixel.ts
CHANGED
|
@@ -1,8 +1,5 @@
|
|
|
1
1
|
import { appendScript } from "./append-script.ts";
|
|
2
2
|
import type { ConsentGated } from "./consent-gate.ts";
|
|
3
|
-
import { whenGranted } from "./consent-gate.ts";
|
|
4
|
-
import { element } from "./element.ts";
|
|
5
|
-
import { data } from "./ref.ts";
|
|
6
3
|
|
|
7
4
|
/** Meta's `fbq`: a queue until `fbevents.js` arrives and replays it. */
|
|
8
5
|
interface Fbq {
|
|
@@ -64,15 +61,3 @@ export function metaPixel(pixelIds: readonly string[]): ConsentGated {
|
|
|
64
61
|
},
|
|
65
62
|
};
|
|
66
63
|
}
|
|
67
|
-
|
|
68
|
-
/** Internal to `MetaPixel.astro`. */
|
|
69
|
-
export const metaPixelElement = element(
|
|
70
|
-
"atlas-meta-pixel",
|
|
71
|
-
({ root, signal }) => {
|
|
72
|
-
whenGranted(
|
|
73
|
-
"marketing",
|
|
74
|
-
metaPixel(JSON.parse(data(root, "pixel-ids"))),
|
|
75
|
-
signal
|
|
76
|
-
);
|
|
77
|
-
}
|
|
78
|
-
);
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import type { Plugin } from "vite";
|
|
2
|
+
|
|
3
|
+
/** What a site imports to turn Astro's `ClientRouter` on. */
|
|
4
|
+
const CLIENT_ROUTER = new Set([
|
|
5
|
+
"astro:transitions",
|
|
6
|
+
"astro/components/ClientRouter.astro",
|
|
7
|
+
]);
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Fails the build when a site imports `ClientRouter`.
|
|
11
|
+
*
|
|
12
|
+
* Atlas assumes a full page load per navigation: analytics count a page view
|
|
13
|
+
* per load, vendor scripts and widgets start once per page, and nothing tears
|
|
14
|
+
* them down. Under the router none of that holds, and nothing errors — the
|
|
15
|
+
* numbers and the widgets just go quietly wrong. The animation it gives is
|
|
16
|
+
* available without it: `@view-transition { navigation: auto; }` in CSS.
|
|
17
|
+
*
|
|
18
|
+
* Only the site's own imports are checked. Astro and other packages may name
|
|
19
|
+
* the module for their own reasons.
|
|
20
|
+
*/
|
|
21
|
+
export function noClientRouter(): Plugin {
|
|
22
|
+
return {
|
|
23
|
+
name: "atlas:no-client-router",
|
|
24
|
+
enforce: "pre",
|
|
25
|
+
resolveId(id, importer) {
|
|
26
|
+
if (!CLIENT_ROUTER.has(id)) return null;
|
|
27
|
+
if (importer === undefined || importer.includes("/node_modules/")) {
|
|
28
|
+
return null;
|
|
29
|
+
}
|
|
30
|
+
throw new Error(
|
|
31
|
+
`${importer} imports ${id}. Astro's ClientRouter is not supported: Atlas's analytics, consent and widgets expect a full page load per navigation. For the animation, use cross-document view transitions — \`@view-transition { navigation: auto; }\` in CSS.`
|
|
32
|
+
);
|
|
33
|
+
},
|
|
34
|
+
};
|
|
35
|
+
}
|
package/src/astro/site-routes.ts
CHANGED
|
@@ -11,6 +11,7 @@ import type { Sitemap } from "../sitemap.ts";
|
|
|
11
11
|
import type { HttpsUrl } from "../url.ts";
|
|
12
12
|
import { warn } from "../warn.ts";
|
|
13
13
|
import { buildCacheDir } from "./build-cache.ts";
|
|
14
|
+
import { noClientRouter } from "./no-client-router.ts";
|
|
14
15
|
|
|
15
16
|
/**
|
|
16
17
|
* What this integration needs of a site, and no more.
|
|
@@ -282,7 +283,12 @@ export function siteRoutes(options: SiteRoutesOptions): AstroIntegration {
|
|
|
282
283
|
},
|
|
283
284
|
cacheDir: buildCacheDir(),
|
|
284
285
|
};
|
|
285
|
-
|
|
286
|
+
// The plugin is kept out of `config`, which is logged below: a
|
|
287
|
+
// plugin prints as noise.
|
|
288
|
+
updateConfig({
|
|
289
|
+
...config,
|
|
290
|
+
vite: { plugins: [noClientRouter()] },
|
|
291
|
+
});
|
|
286
292
|
// Said out loud: a setting changed from under you is worth a
|
|
287
293
|
// line, and reading `astro.config.ts` would otherwise leave you
|
|
288
294
|
// to wonder why the output is not shaped the way its defaults
|
package/src/astro/snap-pixel.ts
CHANGED
|
@@ -1,8 +1,5 @@
|
|
|
1
1
|
import { appendScript } from "./append-script.ts";
|
|
2
2
|
import type { ConsentGated } from "./consent-gate.ts";
|
|
3
|
-
import { whenGranted } from "./consent-gate.ts";
|
|
4
|
-
import { element } from "./element.ts";
|
|
5
|
-
import { data } from "./ref.ts";
|
|
6
3
|
|
|
7
4
|
/** Snap's `snaptr`: a queue until `scevent.min.js` arrives and replays it. */
|
|
8
5
|
interface Snaptr {
|
|
@@ -55,15 +52,3 @@ export function snapPixel(pixelIds: readonly string[]): ConsentGated {
|
|
|
55
52
|
},
|
|
56
53
|
};
|
|
57
54
|
}
|
|
58
|
-
|
|
59
|
-
/** Internal to `SnapPixel.astro`. */
|
|
60
|
-
export const snapPixelElement = element(
|
|
61
|
-
"atlas-snap-pixel",
|
|
62
|
-
({ root, signal }) => {
|
|
63
|
-
whenGranted(
|
|
64
|
-
"marketing",
|
|
65
|
-
snapPixel(JSON.parse(data(root, "pixel-ids"))),
|
|
66
|
-
signal
|
|
67
|
-
);
|
|
68
|
-
}
|
|
69
|
-
);
|
|
@@ -1,8 +1,5 @@
|
|
|
1
1
|
import { appendScript } from "./append-script.ts";
|
|
2
2
|
import type { ConsentGated } from "./consent-gate.ts";
|
|
3
|
-
import { whenGranted } from "./consent-gate.ts";
|
|
4
|
-
import { element } from "./element.ts";
|
|
5
|
-
import { data } from "./ref.ts";
|
|
6
3
|
|
|
7
4
|
/** TikTok's `ttq`: a queue of calls until `events.js` arrives and replays it. */
|
|
8
5
|
interface Ttq extends Array<unknown> {
|
|
@@ -98,15 +95,3 @@ export function tiktokPixel(pixelIds: readonly string[]): ConsentGated {
|
|
|
98
95
|
},
|
|
99
96
|
};
|
|
100
97
|
}
|
|
101
|
-
|
|
102
|
-
/** Internal to `TikTokPixel.astro`. */
|
|
103
|
-
export const tiktokPixelElement = element(
|
|
104
|
-
"atlas-tiktok-pixel",
|
|
105
|
-
({ root, signal }) => {
|
|
106
|
-
whenGranted(
|
|
107
|
-
"marketing",
|
|
108
|
-
tiktokPixel(JSON.parse(data(root, "pixel-ids"))),
|
|
109
|
-
signal
|
|
110
|
-
);
|
|
111
|
-
}
|
|
112
|
-
);
|
|
@@ -1,18 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
/** Internal to `ConsentBanner.astro`, so the pixel's script ships only where it is configured. */
|
|
3
|
-
import type { AxonPixelSettings } from "../analytics/axon-pixel.ts";
|
|
4
|
-
import AtlasElement from "./AtlasElement.astro";
|
|
5
|
-
import { axonPixelElement } from "./axon-pixel.ts";
|
|
6
|
-
|
|
7
|
-
interface Props {
|
|
8
|
-
readonly settings: AxonPixelSettings;
|
|
9
|
-
}
|
|
10
|
-
---
|
|
11
|
-
|
|
12
|
-
<script src="./axon-pixel.ts" />
|
|
13
|
-
|
|
14
|
-
<AtlasElement
|
|
15
|
-
of={axonPixelElement}
|
|
16
|
-
data-event-key={Astro.props.settings.eventKey}
|
|
17
|
-
hidden
|
|
18
|
-
/>
|
package/src/astro/Clarity.astro
DELETED
|
@@ -1,18 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
/** Internal to `ConsentBanner.astro`, so Clarity's script ships only where it is configured. */
|
|
3
|
-
import type { ClaritySettings } from "../analytics/clarity.ts";
|
|
4
|
-
import AtlasElement from "./AtlasElement.astro";
|
|
5
|
-
import { clarityElement } from "./clarity.ts";
|
|
6
|
-
|
|
7
|
-
interface Props {
|
|
8
|
-
readonly settings: ClaritySettings;
|
|
9
|
-
}
|
|
10
|
-
---
|
|
11
|
-
|
|
12
|
-
<script src="./clarity.ts" />
|
|
13
|
-
|
|
14
|
-
<AtlasElement
|
|
15
|
-
of={clarityElement}
|
|
16
|
-
data-project-id={Astro.props.settings.projectId}
|
|
17
|
-
hidden
|
|
18
|
-
/>
|
package/src/astro/Drip.astro
DELETED
|
@@ -1,18 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
/** Internal to `ConsentBanner.astro`, so Drip's script ships only where it is configured. */
|
|
3
|
-
import type { DripSettings } from "../analytics/drip.ts";
|
|
4
|
-
import AtlasElement from "./AtlasElement.astro";
|
|
5
|
-
import { dripElement } from "./drip.ts";
|
|
6
|
-
|
|
7
|
-
interface Props {
|
|
8
|
-
readonly settings: DripSettings;
|
|
9
|
-
}
|
|
10
|
-
---
|
|
11
|
-
|
|
12
|
-
<script src="./drip.ts" />
|
|
13
|
-
|
|
14
|
-
<AtlasElement
|
|
15
|
-
of={dripElement}
|
|
16
|
-
data-account-id={Astro.props.settings.accountId}
|
|
17
|
-
hidden
|
|
18
|
-
/>
|
|
@@ -1,18 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
/** Internal to `ConsentBanner.astro`, so the pixel's script ships only where it is configured. */
|
|
3
|
-
import type { MetaPixelSettings } from "../analytics/meta-pixel.ts";
|
|
4
|
-
import AtlasElement from "./AtlasElement.astro";
|
|
5
|
-
import { metaPixelElement } from "./meta-pixel.ts";
|
|
6
|
-
|
|
7
|
-
interface Props {
|
|
8
|
-
readonly settings: MetaPixelSettings;
|
|
9
|
-
}
|
|
10
|
-
---
|
|
11
|
-
|
|
12
|
-
<script src="./meta-pixel.ts" />
|
|
13
|
-
|
|
14
|
-
<AtlasElement
|
|
15
|
-
of={metaPixelElement}
|
|
16
|
-
data-pixel-ids={JSON.stringify(Astro.props.settings.pixelIds)}
|
|
17
|
-
hidden
|
|
18
|
-
/>
|
|
@@ -1,18 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
/** Internal to `ConsentBanner.astro`, so the pixel's script ships only where it is configured. */
|
|
3
|
-
import type { SnapPixelSettings } from "../analytics/snap-pixel.ts";
|
|
4
|
-
import AtlasElement from "./AtlasElement.astro";
|
|
5
|
-
import { snapPixelElement } from "./snap-pixel.ts";
|
|
6
|
-
|
|
7
|
-
interface Props {
|
|
8
|
-
readonly settings: SnapPixelSettings;
|
|
9
|
-
}
|
|
10
|
-
---
|
|
11
|
-
|
|
12
|
-
<script src="./snap-pixel.ts" />
|
|
13
|
-
|
|
14
|
-
<AtlasElement
|
|
15
|
-
of={snapPixelElement}
|
|
16
|
-
data-pixel-ids={JSON.stringify(Astro.props.settings.pixelIds)}
|
|
17
|
-
hidden
|
|
18
|
-
/>
|
|
@@ -1,18 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
/** Internal to `ConsentBanner.astro`, so the pixel's script ships only where it is configured. */
|
|
3
|
-
import type { TikTokPixelSettings } from "../analytics/tiktok-pixel.ts";
|
|
4
|
-
import AtlasElement from "./AtlasElement.astro";
|
|
5
|
-
import { tiktokPixelElement } from "./tiktok-pixel.ts";
|
|
6
|
-
|
|
7
|
-
interface Props {
|
|
8
|
-
readonly settings: TikTokPixelSettings;
|
|
9
|
-
}
|
|
10
|
-
---
|
|
11
|
-
|
|
12
|
-
<script src="./tiktok-pixel.ts" />
|
|
13
|
-
|
|
14
|
-
<AtlasElement
|
|
15
|
-
of={tiktokPixelElement}
|
|
16
|
-
data-pixel-ids={JSON.stringify(Astro.props.settings.pixelIds)}
|
|
17
|
-
hidden
|
|
18
|
-
/>
|