@escape-game-over/atlas 0.1.61 → 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.
@@ -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. A
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
- **The trap this exists for is view transitions.** A bundled `<script src>` is an
106
- ES module, cached by URL, so it executes once per session — not once per
107
- navigation. Bind at module scope with `ClientRouter` on and the incoming page
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@escape-game-over/atlas",
3
- "version": "0.1.61",
3
+ "version": "0.1.62",
4
4
  "type": "module",
5
5
  "description": "Typed, data-driven machinery for static multi-locale, multi-deployment Astro sites.",
6
6
  "private": false,
@@ -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`. A site on Astro's `ClientRouter` passes
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
 
@@ -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 document this lived in was swapped
45
- // out from under it — a client router replacing `<body>`. Either way every
46
- // line in `seen` is detached, and counting into one would report nothing.
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");
@@ -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; Astro's `ClientRouter` does so on every navigation.
52
- * State that must survive belongs in a `WeakMap` keyed by `root`.
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
@@ -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, so Astro's `ClientRouter` leaves its popstate
340
- // to this module; a replaced entry keeps whatever state it already had.
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
  }
@@ -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
+ }
@@ -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
- updateConfig(config);
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