@iterant/site-runtime 3.7.0 → 3.8.1
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/runtime-contract.md +186 -4
- package/package.json +3 -1
- package/src/config/preset.ts +112 -2
- package/src/fonts/catalog.json +361 -0
- package/src/fonts/catalog.ts +88 -0
- package/src/index.ts +1 -0
- package/src/integrations/dev-restart-state.mjs +46 -0
- package/src/integrations/dev-server-signals.mjs +406 -0
- package/src/integrations/preview-error-shell.mjs +105 -25
- package/src/integrations/site-config-watch.mjs +38 -0
- package/src/layouts/LayoutCore.astro +43 -0
- package/src/layouts/layout-core.ts +56 -0
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
---
|
|
2
|
+
import { Font, fontData } from "astro:assets";
|
|
2
3
|
import { getCollection, getEntry } from "astro:content";
|
|
3
4
|
import type { AstroComponentFactory } from "astro/runtime/server/index.js";
|
|
4
5
|
import RelatedLinks from "../components/RelatedLinks.astro";
|
|
5
6
|
import { SEO, type PageType } from "../components/seo";
|
|
6
7
|
import type { SeoJsonSchema } from "../components/seo-json";
|
|
8
|
+
import { fontCssVariable } from "../fonts/catalog";
|
|
7
9
|
import type { HreflangAlternate } from "../lib/hreflang";
|
|
8
10
|
import { DEFAULT_LOCALE, localeFromPath } from "../lib/locales";
|
|
9
11
|
import { SITE_RUNTIME_VERSION } from "../version";
|
|
@@ -14,6 +16,7 @@ import {
|
|
|
14
16
|
resolveAbsoluteUrl,
|
|
15
17
|
resolveStructuredData,
|
|
16
18
|
routePathFromPathname,
|
|
19
|
+
selectFontFamilies,
|
|
17
20
|
type LayoutSiteConfig,
|
|
18
21
|
type SiteShell,
|
|
19
22
|
} from "./layout-core";
|
|
@@ -151,6 +154,41 @@ const shellId = shell ?? pageEntry?.data.shell;
|
|
|
151
154
|
const linksEntry = await getEntry("links", "links");
|
|
152
155
|
const linksSlice = linksEntry?.data.routes[routePath];
|
|
153
156
|
|
|
157
|
+
// Web fonts (3.8.0): a `@font-face` block per family the site names in
|
|
158
|
+
// SITE_CONFIG.fonts, and a preload link for each. Never the whole catalog:
|
|
159
|
+
// every family rendered here inlines its faces into EVERY page.
|
|
160
|
+
//
|
|
161
|
+
// `fontData` is Astro's own runtime view of the resolved font config, keyed by
|
|
162
|
+
// CSS variable, so it is what decides whether a family can render. The rule and
|
|
163
|
+
// the fallback live in selectFontFamilies, which is testable without a render.
|
|
164
|
+
const fonts = selectFontFamilies(siteConfig.fonts, (cssVariable) =>
|
|
165
|
+
Object.hasOwn(fontData, cssVariable),
|
|
166
|
+
);
|
|
167
|
+
|
|
168
|
+
// A named family nothing declared is a repo whose astro.config and site config
|
|
169
|
+
// disagree, which no gate catches and no render shows. Said once, in dev, where
|
|
170
|
+
// the author is looking.
|
|
171
|
+
if (import.meta.env.DEV && fonts.undeclared.length > 0) {
|
|
172
|
+
console.warn(
|
|
173
|
+
`[site-runtime] SITE_CONFIG.fonts names ${fonts.undeclared.join(", ")}, which astro.config did not declare. Pass the same list to iterantStarter({ fonts }).`,
|
|
174
|
+
);
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
// The cast is Astro's own type: once a repo configures fonts, `astro sync` narrows
|
|
178
|
+
// CssVariable to the exact literals in that config. Every variable here is one
|
|
179
|
+
// fontData carries, which is the same set, but the compiler cannot see that
|
|
180
|
+
// through a runtime string.
|
|
181
|
+
const fontVariables = fonts.families.map((family) =>
|
|
182
|
+
fontCssVariable(family),
|
|
183
|
+
) as Array<import("astro:assets").CssVariable>;
|
|
184
|
+
|
|
185
|
+
// Preload the upright latin face of each named family and nothing else. Every
|
|
186
|
+
// face stays in the @font-face block, so the browser still fetches a slanted or
|
|
187
|
+
// extended-latin one the moment a glyph needs it. Preloading every face instead
|
|
188
|
+
// measured 8 files and 436K of blocking requests for two families, on a page
|
|
189
|
+
// that renders four words of plain latin text.
|
|
190
|
+
const fontPreload = fonts.named ? [{ style: "normal", subset: "latin" }] : false;
|
|
191
|
+
|
|
154
192
|
// Version identity: the installed package version IS the site's runtime
|
|
155
193
|
// version. `it-astro-starter-version` keeps emitting the same value while the
|
|
156
194
|
// plugin loader and the platform's page indexer still read that name, and
|
|
@@ -176,6 +214,11 @@ const linksSlice = linksEntry?.data.routes[routePath];
|
|
|
176
214
|
<meta name="generator" content={Astro.generator} />
|
|
177
215
|
<meta name="it-site-runtime" content={SITE_RUNTIME_VERSION} />
|
|
178
216
|
<meta name="it-astro-starter-version" content={SITE_RUNTIME_VERSION} />
|
|
217
|
+
{
|
|
218
|
+
fontVariables.map((cssVariable) => (
|
|
219
|
+
<Font cssVariable={cssVariable} preload={fontPreload} />
|
|
220
|
+
))
|
|
221
|
+
}
|
|
179
222
|
<SEO
|
|
180
223
|
title={title}
|
|
181
224
|
description={description}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { PageType } from "../components/seo";
|
|
2
|
+
import { DEFAULT_FONT_FAMILIES, fontCssVariable } from "../fonts/catalog";
|
|
2
3
|
import type { ContentProps } from "../lib/content-values";
|
|
3
4
|
|
|
4
5
|
// The pure half of LayoutCore.astro: URL resolution, chrome lookup, and the
|
|
@@ -16,6 +17,14 @@ export interface LayoutSiteConfig {
|
|
|
16
17
|
description: string;
|
|
17
18
|
logo?: string;
|
|
18
19
|
sameAs?: string[];
|
|
20
|
+
/**
|
|
21
|
+
* Font families this site uses, by catalog name (`["Fraunces", "Inter"]`).
|
|
22
|
+
* The head carries a `@font-face` block per family, so this is the list of
|
|
23
|
+
* families the site actually sets somewhere, not everything it may choose
|
|
24
|
+
* from. The repo passes the same array to `iterantStarter({ fonts })`; a
|
|
25
|
+
* family missing there has no CSS variable to render and is skipped.
|
|
26
|
+
*/
|
|
27
|
+
fonts?: string[];
|
|
19
28
|
}
|
|
20
29
|
|
|
21
30
|
/**
|
|
@@ -141,3 +150,50 @@ export function resolveStructuredData(
|
|
|
141
150
|
}
|
|
142
151
|
|
|
143
152
|
export { LAYOUT_CONTRACT } from "./layout-contract";
|
|
153
|
+
|
|
154
|
+
/** What the head renders for web fonts, decided off the site's list. */
|
|
155
|
+
export interface FontSelection {
|
|
156
|
+
/** Families to render a `<Font>` for, in order, deduped. */
|
|
157
|
+
families: string[];
|
|
158
|
+
/** Whether the families are the site's own list rather than the fallback. */
|
|
159
|
+
named: boolean;
|
|
160
|
+
/** Families the site named that no font config declared. */
|
|
161
|
+
undeclared: string[];
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
/**
|
|
165
|
+
* The families the head renders, and whether to preload them.
|
|
166
|
+
*
|
|
167
|
+
* `isDeclared` is the authority, not the catalog: Astro's `<Font>` THROWS
|
|
168
|
+
* (`FontFamilyNotFound`) on a CSS variable no family registered, so a repo
|
|
169
|
+
* whose astro.config never got the site's list would render an error page
|
|
170
|
+
* rather than a missing face. Filtering through what Astro actually resolved
|
|
171
|
+
* makes that structurally impossible, and it also lets a diverged repo declare
|
|
172
|
+
* a family of its own and have it render.
|
|
173
|
+
*
|
|
174
|
+
* A site that names nothing falls back to the default set: a repo's globals.css
|
|
175
|
+
* is written against the CSS variables, so a stylesheet already saying
|
|
176
|
+
* `var(--font-fraunces)` would otherwise get the system stack, silently. The
|
|
177
|
+
* fallback is reported through `named`, because a page that has not said which
|
|
178
|
+
* families it uses must not spend a preload on each of eleven guesses.
|
|
179
|
+
*/
|
|
180
|
+
export function selectFontFamilies(
|
|
181
|
+
fonts: readonly string[] | undefined,
|
|
182
|
+
isDeclared: (cssVariable: string) => boolean,
|
|
183
|
+
): FontSelection {
|
|
184
|
+
const wanted = [...new Set(fonts ?? [])];
|
|
185
|
+
const declared = wanted.filter((family) =>
|
|
186
|
+
isDeclared(fontCssVariable(family)),
|
|
187
|
+
);
|
|
188
|
+
const undeclared = wanted.filter((family) => !declared.includes(family));
|
|
189
|
+
if (declared.length > 0) {
|
|
190
|
+
return { families: declared, named: true, undeclared };
|
|
191
|
+
}
|
|
192
|
+
return {
|
|
193
|
+
families: DEFAULT_FONT_FAMILIES.filter((family) =>
|
|
194
|
+
isDeclared(fontCssVariable(family)),
|
|
195
|
+
),
|
|
196
|
+
named: false,
|
|
197
|
+
undeclared,
|
|
198
|
+
};
|
|
199
|
+
}
|