diff --git a/packages/astro/components/Font.astro b/packages/astro/components/Font.astro
index 40d1ed6942d3..13f05a44c803 100644
--- a/packages/astro/components/Font.astro
+++ b/packages/astro/components/Font.astro
@@ -4,8 +4,8 @@ import { filterPreloads } from 'astro/assets/fonts/runtime';
import { AstroError, AstroErrorData } from '../dist/core/errors/index.js';
// TODO: remove check when fonts are stabilized
-const { internalConsumableMap } = mod;
-if (!internalConsumableMap) {
+const { componentDataByCssVariable } = mod;
+if (!componentDataByCssVariable) {
throw new AstroError(AstroErrorData.ExperimentalFontsNotEnabled);
}
@@ -17,7 +17,7 @@ interface Props {
}
const { cssVariable, preload = false } = Astro.props as Props;
-const data = internalConsumableMap.get(cssVariable);
+const data = componentDataByCssVariable.get(cssVariable);
if (!data) {
throw new AstroError({
...AstroErrorData.FontFamilyNotFound,
@@ -25,7 +25,7 @@ if (!data) {
});
}
-const filteredPreloadData = filterPreloads(data.preloadData, preload);
+const filteredPreloadData = filterPreloads(data.preloads, preload);
---
diff --git a/packages/astro/dev-only.d.ts b/packages/astro/dev-only.d.ts
index 94f4dbb89cbd..dd7d43618921 100644
--- a/packages/astro/dev-only.d.ts
+++ b/packages/astro/dev-only.d.ts
@@ -7,8 +7,8 @@ declare module 'virtual:astro:env/internal' {
}
declare module 'virtual:astro:assets/fonts/internal' {
- export const internalConsumableMap: import('./src/assets/fonts/types.js').InternalConsumableMap;
- export const consumableMap: import('./src/assets/fonts/types.js').ConsumableMap;
+ export const componentDataByCssVariable: import('./src/assets/fonts/types.js').ComponentDataByCssVariable;
+ export const fontDataByCssVariable: import('./src/assets/fonts/types.js').FontDataByCssVariable;
}
declare module 'virtual:astro:adapter-config/client' {
diff --git a/packages/astro/src/assets/fonts/README.md b/packages/astro/src/assets/fonts/README.md
index 7326407f6fbb..f95ba8eb8327 100644
--- a/packages/astro/src/assets/fonts/README.md
+++ b/packages/astro/src/assets/fonts/README.md
@@ -3,7 +3,7 @@
Here is an overview of the architecture of the fonts in Astro:
- [`orchestrate()`](./orchestrate.ts) combines sub steps and takes care of getting useful data from the config
- - It resolves font families (eg. import remote font providers)
+ - It resolves font families (eg. deduplication)
- It initializes the font resolver
- For each family, it resolves fonts data and normalizes them
- For each family, optimized fallbacks (and related CSS) are generated if applicable
diff --git a/packages/astro/src/assets/fonts/config.ts b/packages/astro/src/assets/fonts/config.ts
index 88925e1e2f7e..fcfa1ca1e867 100644
--- a/packages/astro/src/assets/fonts/config.ts
+++ b/packages/astro/src/assets/fonts/config.ts
@@ -1,5 +1,5 @@
import { z } from 'zod';
-import { FONT_TYPES, LOCAL_PROVIDER_NAME } from './constants.js';
+import { FONT_TYPES } from './constants.js';
import type { FontProvider } from './types.js';
export const weightSchema = z.union([z.string(), z.number()]);
@@ -26,34 +26,6 @@ const requiredFamilyAttributesSchema = z.object({
cssVariable: z.string(),
});
-const entrypointSchema = z.union([z.string(), z.instanceof(URL)]);
-
-export const localFontFamilySchema = z
- .object({
- ...requiredFamilyAttributesSchema.shape,
- ...fallbacksSchema.shape,
- provider: z.literal(LOCAL_PROVIDER_NAME),
- variants: z
- .array(
- z
- .object({
- ...familyPropertiesSchema.shape,
- src: z
- .array(
- z.union([
- entrypointSchema,
- z.object({ url: entrypointSchema, tech: z.string().optional() }).strict(),
- ]),
- )
- .nonempty(),
- // TODO: find a way to support subsets (through fontkit?)
- })
- .strict(),
- )
- .nonempty(),
- })
- .strict();
-
export const fontProviderSchema = z
.object({
name: z.string(),
@@ -64,7 +36,7 @@ export const fontProviderSchema = z
})
.strict();
-export const remoteFontFamilySchema = z
+export const fontFamilySchema = z
.object({
...requiredFamilyAttributesSchema.shape,
...fallbacksSchema.shape,
diff --git a/packages/astro/src/assets/fonts/constants.ts b/packages/astro/src/assets/fonts/constants.ts
index 110fd102fd5c..0ee81c01ef4d 100644
--- a/packages/astro/src/assets/fonts/constants.ts
+++ b/packages/astro/src/assets/fonts/constants.ts
@@ -1,7 +1,5 @@
import type { Defaults, FontType } from './types.js';
-export const LOCAL_PROVIDER_NAME = 'local';
-
export const DEFAULTS: Defaults = {
weights: ['400'],
styles: ['normal', 'italic'],
diff --git a/packages/astro/src/assets/fonts/core/collect-component-data.ts b/packages/astro/src/assets/fonts/core/collect-component-data.ts
new file mode 100644
index 000000000000..a70e73abf57e
--- /dev/null
+++ b/packages/astro/src/assets/fonts/core/collect-component-data.ts
@@ -0,0 +1,72 @@
+import type { CssRenderer } from '../definitions.js';
+import type {
+ Collaborator,
+ ComponentDataByCssVariable,
+ Defaults,
+ FontFamilyAssets,
+} from '../types.js';
+import { unifontFontFaceDataToProperties } from '../utils.js';
+import type { optimizeFallbacks as _optimizeFallbacks } from './optimize-fallbacks.js';
+
+export async function collectComponentData({
+ fontFamilyAssets,
+ cssRenderer,
+ defaults,
+ optimizeFallbacks,
+}: {
+ fontFamilyAssets: Array;
+ cssRenderer: CssRenderer;
+ defaults: Pick;
+ optimizeFallbacks: Collaborator<
+ typeof _optimizeFallbacks,
+ 'family' | 'fallbacks' | 'collectedFonts'
+ >;
+}) {
+ const componentDataByCssVariable: ComponentDataByCssVariable = new Map();
+
+ for (const { family, fonts, collectedFontsForMetricsByUniqueKey, preloads } of fontFamilyAssets) {
+ let css = '';
+
+ for (const data of fonts) {
+ css += cssRenderer.generateFontFace(
+ family.uniqueName,
+ unifontFontFaceDataToProperties({
+ src: data.src,
+ weight: data.weight,
+ style: data.style,
+ // User settings override the generated font settings
+ display: data.display ?? family.display,
+ unicodeRange: data.unicodeRange ?? family.unicodeRange,
+ stretch: data.stretch ?? family.stretch,
+ featureSettings: data.featureSettings ?? family.featureSettings,
+ variationSettings: data.variationSettings ?? family.variationSettings,
+ }),
+ );
+ }
+
+ const fallbacks = family.fallbacks ?? defaults.fallbacks;
+ const cssVarValues = [family.uniqueName];
+ const optimizeFallbacksResult =
+ (family.optimizedFallbacks ?? defaults.optimizedFallbacks)
+ ? await optimizeFallbacks({
+ family,
+ fallbacks,
+ collectedFonts: Array.from(collectedFontsForMetricsByUniqueKey.values()),
+ })
+ : null;
+
+ if (optimizeFallbacksResult) {
+ css += optimizeFallbacksResult.css;
+ cssVarValues.push(...optimizeFallbacksResult.fallbacks);
+ } else {
+ // If there are no optimized fallbacks, we pass the provided fallbacks as is.
+ cssVarValues.push(...fallbacks);
+ }
+
+ css += cssRenderer.generateCssVariable(family.cssVariable, cssVarValues);
+
+ componentDataByCssVariable.set(family.cssVariable, { preloads, css });
+ }
+
+ return componentDataByCssVariable;
+}
diff --git a/packages/astro/src/assets/fonts/core/collect-font-assets-from-faces.ts b/packages/astro/src/assets/fonts/core/collect-font-assets-from-faces.ts
new file mode 100644
index 000000000000..db2056007ad6
--- /dev/null
+++ b/packages/astro/src/assets/fonts/core/collect-font-assets-from-faces.ts
@@ -0,0 +1,96 @@
+import type * as unifont from 'unifont';
+import { FONT_FORMATS } from '../constants.js';
+import type { FontFileIdGenerator, Hasher } from '../definitions.js';
+import type { Defaults, FontFileById, PreloadData, ResolvedFontFamily } from '../types.js';
+import { renderFontWeight } from '../utils.js';
+import type { CollectedFontForMetrics } from './optimize-fallbacks.js';
+
+export function collectFontAssetsFromFaces({
+ fonts,
+ fontFileIdGenerator,
+ family,
+ fontFilesIds,
+ collectedFontsIds,
+ hasher,
+ defaults,
+}: {
+ fonts: Array;
+ fontFileIdGenerator: FontFileIdGenerator;
+ family: Pick;
+ fontFilesIds: Set;
+ collectedFontsIds: Set;
+ hasher: Hasher;
+ defaults: Pick;
+}) {
+ const fontFileById: FontFileById = new Map();
+ const collectedFontsForMetricsByUniqueKey = new Map();
+ const preloads: Array = [];
+
+ for (const font of fonts) {
+ // The index keeps track of encountered URLs. We can't use a regular for loop
+ // below because it may contain sources without urls, which would prevent preloading completely
+ let index = 0;
+ for (const source of font.src) {
+ if ('name' in source) {
+ continue;
+ }
+ const format = FONT_FORMATS.find((e) => e.format === source.format)!;
+ const originalUrl = source.originalURL!;
+ const id = fontFileIdGenerator.generate({
+ cssVariable: family.cssVariable,
+ font,
+ originalUrl,
+ type: format.type,
+ });
+
+ if (!fontFilesIds.has(id) && !fontFileById.has(id)) {
+ fontFileById.set(id, { url: source.url, init: font.meta?.init });
+ // We only collect the first URL to avoid preloading fallback sources (eg. we only
+ // preload woff2 if woff is available)
+ if (index === 0) {
+ preloads.push({
+ style: font.style,
+ subset: font.meta?.subset,
+ type: format.type,
+ url: source.url,
+ weight: renderFontWeight(font.weight),
+ });
+ }
+ }
+
+ const collected: CollectedFontForMetrics = {
+ hash: id,
+ url: originalUrl,
+ init: font.meta?.init,
+ data: {
+ weight: font.weight,
+ style: font.style,
+ meta: {
+ subset: font.meta?.subset,
+ },
+ },
+ };
+ const collectedKey = hasher.hashObject(collected.data);
+ const fallbacks = family.fallbacks ?? defaults.fallbacks;
+ if (
+ fallbacks.length > 0 &&
+ // If the same data has already been sent for this family, we don't want to have
+ // duplicated fallbacks. Such scenario can occur with unicode ranges.
+ !collectedFontsIds.has(collectedKey) &&
+ !collectedFontsForMetricsByUniqueKey.has(collectedKey)
+ ) {
+ // If a family has fallbacks, we store the first url we get that may
+ // be used for the fallback generation.
+ collectedFontsForMetricsByUniqueKey.set(collectedKey, collected);
+ }
+
+ index++;
+ }
+ }
+
+ return {
+ fontFileById,
+ preloads,
+ collectedFontsForMetricsByUniqueKey,
+ };
+}
diff --git a/packages/astro/src/assets/fonts/core/collect-font-data.ts b/packages/astro/src/assets/fonts/core/collect-font-data.ts
new file mode 100644
index 000000000000..689805409255
--- /dev/null
+++ b/packages/astro/src/assets/fonts/core/collect-font-data.ts
@@ -0,0 +1,31 @@
+import type { FontData, FontDataByCssVariable, FontFamilyAssets } from '../types.js';
+import { renderFontWeight } from '../utils.js';
+
+export function collectFontData(
+ fontFamilyAssets: Array<
+ Pick & { family: Pick }
+ >,
+) {
+ const fontDataByCssVariable: FontDataByCssVariable = new Map();
+
+ for (const { family, fonts } of fontFamilyAssets) {
+ const fontData: Array = [];
+ for (const data of fonts) {
+ fontData.push({
+ weight: renderFontWeight(data.weight),
+ style: data.style,
+ src: data.src
+ .filter((src) => 'url' in src)
+ .map((src) => ({
+ url: src.url,
+ format: src.format,
+ tech: src.tech,
+ })),
+ });
+ }
+
+ fontDataByCssVariable.set(family.cssVariable, fontData);
+ }
+
+ return fontDataByCssVariable;
+}
diff --git a/packages/astro/src/assets/fonts/core/compute-font-families-assets.ts b/packages/astro/src/assets/fonts/core/compute-font-families-assets.ts
new file mode 100644
index 000000000000..6164656e06a0
--- /dev/null
+++ b/packages/astro/src/assets/fonts/core/compute-font-families-assets.ts
@@ -0,0 +1,116 @@
+import type { Logger } from '../../../core/logger/core.js';
+import type { FontResolver, StringMatcher } from '../definitions.js';
+import type {
+ Collaborator,
+ Defaults,
+ FontFamilyAssetsByUniqueKey,
+ FontFileById,
+ ResolvedFontFamily,
+} from '../types.js';
+import type { collectFontAssetsFromFaces as _collectFontAssetsFromFaces } from './collect-font-assets-from-faces.js';
+import type { filterAndTransformFontFaces as _filterAndTransformFontFaces } from './filter-and-transform-font-faces.js';
+import type { getOrCreateFontFamilyAssets as _getOrCreateFontFamilyAssets } from './get-or-create-font-family-assets.js';
+
+export async function computeFontFamiliesAssets({
+ resolvedFamilies,
+ fontResolver,
+ logger,
+ bold,
+ defaults,
+ stringMatcher,
+ getOrCreateFontFamilyAssets,
+ collectFontAssetsFromFaces,
+ filterAndTransformFontFaces,
+}: {
+ resolvedFamilies: Array;
+ fontResolver: FontResolver;
+ logger: Logger;
+ bold: (input: string) => string;
+ defaults: Defaults;
+ stringMatcher: StringMatcher;
+ getOrCreateFontFamilyAssets: Collaborator<
+ typeof _getOrCreateFontFamilyAssets,
+ 'family' | 'fontFamilyAssetsByUniqueKey'
+ >;
+ filterAndTransformFontFaces: Collaborator<
+ typeof _filterAndTransformFontFaces,
+ 'family' | 'fonts'
+ >;
+ collectFontAssetsFromFaces: Collaborator<
+ typeof _collectFontAssetsFromFaces,
+ 'family' | 'fonts' | 'collectedFontsIds' | 'fontFilesIds'
+ >;
+}) {
+ /**
+ * Holds family data by a key, to allow merging families
+ */
+ const fontFamilyAssetsByUniqueKey: FontFamilyAssetsByUniqueKey = new Map();
+
+ /**
+ * Holds associations of hash and original font file URLs, so they can be
+ * downloaded whenever the hash is requested.
+ */
+ const fontFileById: FontFileById = new Map();
+
+ // First loop: we try to merge families. This is useful for advanced cases, where eg. you want
+ // 500, 600, 700 as normal but also 500 as italic. That requires 2 families
+ for (const family of resolvedFamilies) {
+ const fontAssets = getOrCreateFontFamilyAssets({
+ fontFamilyAssetsByUniqueKey,
+ family,
+ });
+
+ const _fonts = await fontResolver.resolveFont({
+ familyName: family.name,
+ provider: family.provider.name,
+ // We do not merge the defaults, we only provide defaults as a fallback
+ weights: family.weights ?? defaults.weights,
+ styles: family.styles ?? defaults.styles,
+ subsets: family.subsets ?? defaults.subsets,
+ formats: family.formats ?? defaults.formats,
+ options: family.options,
+ });
+ if (_fonts.length === 0) {
+ logger.warn(
+ 'assets',
+ `No data found for font family ${bold(family.name)}. Review your configuration`,
+ );
+ const availableFamilies = await fontResolver.listFonts({ provider: family.provider.name });
+ if (
+ availableFamilies &&
+ availableFamilies.length > 0 &&
+ !availableFamilies.includes(family.name)
+ ) {
+ logger.warn(
+ 'assets',
+ `${bold(family.name)} font family cannot be retrieved by the provider. Did you mean ${bold(stringMatcher.getClosestMatch(family.name, availableFamilies))}?`,
+ );
+ }
+ continue;
+ }
+ // The data returned by the provider contains original URLs. We proxy them.
+ // TODO: dedupe?
+ fontAssets.fonts.push(
+ ...filterAndTransformFontFaces({
+ fonts: _fonts,
+ family,
+ }),
+ );
+
+ const result = collectFontAssetsFromFaces({
+ fonts: fontAssets.fonts,
+ family,
+ fontFilesIds: new Set(fontFileById.keys()),
+ collectedFontsIds: new Set(fontAssets.collectedFontsForMetricsByUniqueKey.keys()),
+ });
+ for (const [key, value] of result.fontFileById.entries()) {
+ fontFileById.set(key, value);
+ }
+ for (const [key, value] of result.collectedFontsForMetricsByUniqueKey.entries()) {
+ fontAssets.collectedFontsForMetricsByUniqueKey.set(key, value);
+ }
+ fontAssets.preloads.push(...result.preloads);
+ }
+
+ return { fontFamilyAssets: Array.from(fontFamilyAssetsByUniqueKey.values()), fontFileById };
+}
diff --git a/packages/astro/src/assets/fonts/core/filter-and-transform-font-faces.ts b/packages/astro/src/assets/fonts/core/filter-and-transform-font-faces.ts
new file mode 100644
index 000000000000..6bd915de9f91
--- /dev/null
+++ b/packages/astro/src/assets/fonts/core/filter-and-transform-font-faces.ts
@@ -0,0 +1,55 @@
+import type * as unifont from 'unifont';
+import { FONT_FORMATS } from '../constants.js';
+import type { FontFileIdGenerator, FontTypeExtractor, UrlResolver } from '../definitions.js';
+import type { ResolvedFontFamily } from '../types.js';
+
+export function filterAndTransformFontFaces({
+ fonts,
+ fontTypeExtractor,
+ fontFileIdGenerator,
+ urlResolver,
+ family,
+}: {
+ fonts: Array;
+ fontTypeExtractor: FontTypeExtractor;
+ fontFileIdGenerator: FontFileIdGenerator;
+ urlResolver: UrlResolver;
+ family: Pick;
+}) {
+ return (
+ fonts
+ // Avoid getting too much font files
+ .filter((font) => (typeof font.meta?.priority === 'number' ? font.meta.priority <= 1 : true))
+ // Collect URLs
+ .map((font) => ({
+ ...font,
+ src: font.src.map((source) => {
+ if ('name' in source) {
+ return source;
+ }
+ // We handle protocol relative URLs here, otherwise they're considered absolute by the font
+ // fetcher which will try to read them from the file system
+ const originalUrl = source.url.startsWith('//') ? `https:${source.url}` : source.url;
+ let format = FONT_FORMATS.find((e) => e.format === source.format);
+ if (!format) {
+ format = FONT_FORMATS.find((e) => e.type === fontTypeExtractor.extract(source.url))!;
+ }
+ const id = fontFileIdGenerator.generate({
+ cssVariable: family.cssVariable,
+ font,
+ originalUrl,
+ type: format.type,
+ });
+ const url = urlResolver.resolve(id);
+
+ const newSource: unifont.RemoteFontSource = {
+ originalURL: originalUrl,
+ url,
+ format: format.format,
+ tech: source.tech,
+ };
+ return newSource;
+ }),
+ }))
+ );
+}
diff --git a/packages/astro/src/assets/fonts/core/get-or-create-font-family-assets.ts b/packages/astro/src/assets/fonts/core/get-or-create-font-family-assets.ts
new file mode 100644
index 000000000000..589a8c4671c5
--- /dev/null
+++ b/packages/astro/src/assets/fonts/core/get-or-create-font-family-assets.ts
@@ -0,0 +1,41 @@
+import type { Logger } from '../../../core/logger/core.js';
+import type { FontFamilyAssetsByUniqueKey, ResolvedFontFamily } from '../types.js';
+
+export function getOrCreateFontFamilyAssets({
+ fontFamilyAssetsByUniqueKey,
+ logger,
+ bold,
+ family,
+}: {
+ fontFamilyAssetsByUniqueKey: FontFamilyAssetsByUniqueKey;
+ logger: Logger;
+ bold: (input: string) => string;
+ family: ResolvedFontFamily;
+}) {
+ const key = `${family.cssVariable}:${family.name}:${family.provider.name}`;
+ let fontAssets = fontFamilyAssetsByUniqueKey.get(key);
+ if (!fontAssets) {
+ if (
+ Array.from(fontFamilyAssetsByUniqueKey.keys()).find((k) =>
+ k.startsWith(`${family.cssVariable}:`),
+ )
+ ) {
+ logger.warn(
+ 'assets',
+ `Several font families have been registered for the ${bold(family.cssVariable)} cssVariable but they do not share the same name and provider.`,
+ );
+ logger.warn(
+ 'assets',
+ 'These families will not be merged together. The last occurrence will override previous families for this cssVariable. Review your Astro configuration.',
+ );
+ }
+ fontAssets = {
+ family,
+ fonts: [],
+ collectedFontsForMetricsByUniqueKey: new Map(),
+ preloads: [],
+ };
+ fontFamilyAssetsByUniqueKey.set(key, fontAssets);
+ }
+ return fontAssets;
+}
diff --git a/packages/astro/src/assets/fonts/core/normalize-remote-font-faces.ts b/packages/astro/src/assets/fonts/core/normalize-remote-font-faces.ts
deleted file mode 100644
index 818104396ea6..000000000000
--- a/packages/astro/src/assets/fonts/core/normalize-remote-font-faces.ts
+++ /dev/null
@@ -1,57 +0,0 @@
-import type * as unifont from 'unifont';
-import { FONT_FORMATS } from '../constants.js';
-import type { FontTypeExtractor, UrlProxy } from '../definitions.js';
-
-export function normalizeRemoteFontFaces({
- fonts,
- urlProxy,
- fontTypeExtractor,
-}: {
- fonts: Array;
- urlProxy: UrlProxy;
- fontTypeExtractor: FontTypeExtractor;
-}): Array {
- return (
- fonts
- // Avoid getting too much font files
- .filter((font) => (typeof font.meta?.priority === 'number' ? font.meta.priority <= 1 : true))
- // Collect URLs
- .map((font) => {
- // The index keeps track of encountered URLs. We can't use the index on font.src.map
- // below because it may contain sources without urls, which would prevent preloading completely
- let index = 0;
- return {
- ...font,
- src: font.src.map((source) => {
- if ('name' in source) {
- return source;
- }
- // We handle protocol relative URLs here, otherwise they're considered absolute by the font
- // fetcher which will try to read them from the file system
- const url = source.url.startsWith('//') ? `https:${source.url}` : source.url;
- const proxied = {
- ...source,
- originalURL: url,
- url: urlProxy.proxy({
- url,
- type:
- FONT_FORMATS.find((e) => e.format === source.format)?.type ??
- fontTypeExtractor.extract(source.url),
- // We only collect the first URL to avoid preloading fallback sources (eg. we only
- // preload woff2 if woff is available)
- collectPreload: index === 0,
- data: {
- weight: font.weight,
- style: font.style,
- subset: font.meta?.subset,
- },
- init: font.meta?.init ?? null,
- }),
- };
- index++;
- return proxied;
- }),
- };
- })
- );
-}
diff --git a/packages/astro/src/assets/fonts/core/optimize-fallbacks.ts b/packages/astro/src/assets/fonts/core/optimize-fallbacks.ts
index 5e97680524b0..a706a19d281e 100644
--- a/packages/astro/src/assets/fonts/core/optimize-fallbacks.ts
+++ b/packages/astro/src/assets/fonts/core/optimize-fallbacks.ts
@@ -11,14 +11,12 @@ export async function optimizeFallbacks({
family,
fallbacks: _fallbacks,
collectedFonts,
- enabled,
systemFallbacksProvider,
fontMetricsResolver,
}: {
- family: Pick;
+ family: Pick;
fallbacks: Array;
collectedFonts: Array;
- enabled: boolean;
systemFallbacksProvider: SystemFallbacksProvider;
fontMetricsResolver: FontMetricsResolver;
}): Promise ({
font,
// We mustn't wrap in quote because that's handled by the CSS renderer
- name: `${family.nameWithHash} fallback: ${font}`,
+ name: `${family.uniqueName} fallback: ${font}`,
}));
// We prepend the fallbacks with the local fonts and we dedupe in case a local font is already provided
diff --git a/packages/astro/src/assets/fonts/core/resolve-families.ts b/packages/astro/src/assets/fonts/core/resolve-families.ts
deleted file mode 100644
index 8e6141fbfa07..000000000000
--- a/packages/astro/src/assets/fonts/core/resolve-families.ts
+++ /dev/null
@@ -1,91 +0,0 @@
-import { LOCAL_PROVIDER_NAME } from '../constants.js';
-import type { Hasher, LocalProviderUrlResolver } from '../definitions.js';
-import type {
- FontFamily,
- LocalFontFamily,
- ResolvedFontFamily,
- ResolvedLocalFontFamily,
-} from '../types.js';
-import { dedupe, withoutQuotes } from '../utils.js';
-
-function resolveVariants({
- variants,
- localProviderUrlResolver,
-}: {
- variants: LocalFontFamily['variants'];
- localProviderUrlResolver: LocalProviderUrlResolver;
-}): ResolvedLocalFontFamily['variants'] {
- return variants.map((variant) => ({
- ...variant,
- weight: variant.weight?.toString(),
- src: variant.src.map((value) => {
- // A src can be a string or an object, we extract the value accordingly.
- const isValue = typeof value === 'string' || value instanceof URL;
- const url = (isValue ? value : value.url).toString();
- const tech = isValue ? undefined : value.tech;
- return {
- url: localProviderUrlResolver.resolve(url),
- tech,
- };
- }),
- }));
-}
-
-/**
- * Dedupes properties if applicable and resolves entrypoints.
- */
-export function resolveFamily({
- family,
- hasher,
- localProviderUrlResolver,
-}: {
- family: FontFamily;
- hasher: Hasher;
- localProviderUrlResolver: LocalProviderUrlResolver;
-}): ResolvedFontFamily {
- // We remove quotes from the name so they can be properly resolved by providers.
- const name = withoutQuotes(family.name);
- // This will be used in CSS font faces. Quotes are added by the CSS renderer if
- // this value contains a space.
- const nameWithHash = `${name}-${hasher.hashObject(family)}`;
-
- if (family.provider === LOCAL_PROVIDER_NAME) {
- return {
- ...family,
- name,
- nameWithHash,
- variants: resolveVariants({ variants: family.variants, localProviderUrlResolver }),
- fallbacks: family.fallbacks ? dedupe(family.fallbacks) : undefined,
- };
- }
-
- return {
- ...family,
- name,
- nameWithHash,
- weights: family.weights ? dedupe(family.weights.map((weight) => weight.toString())) : undefined,
- styles: family.styles ? dedupe(family.styles) : undefined,
- subsets: family.subsets ? dedupe(family.subsets) : undefined,
- formats: family.formats ? dedupe(family.formats) : undefined,
- fallbacks: family.fallbacks ? dedupe(family.fallbacks) : undefined,
- unicodeRange: family.unicodeRange ? dedupe(family.unicodeRange) : undefined,
- };
-}
-
-/**
- * A function for convenience. The actual logic lives in resolveFamily
- */
-export function resolveFamilies({
- families,
- ...dependencies
-}: { families: Array } & Omit<
- Parameters[0],
- 'family'
->): Array {
- return families.map((family) =>
- resolveFamily({
- family,
- ...dependencies,
- }),
- );
-}
diff --git a/packages/astro/src/assets/fonts/core/resolve-family.ts b/packages/astro/src/assets/fonts/core/resolve-family.ts
new file mode 100644
index 000000000000..e1e5e41b017a
--- /dev/null
+++ b/packages/astro/src/assets/fonts/core/resolve-family.ts
@@ -0,0 +1,28 @@
+import type { Hasher } from '../definitions.js';
+import type { FontFamily, ResolvedFontFamily } from '../types.js';
+import { dedupe, withoutQuotes } from '../utils.js';
+
+export function resolveFamily({
+ family,
+ hasher,
+}: {
+ family: FontFamily;
+ hasher: Hasher;
+}): ResolvedFontFamily {
+ // We remove quotes from the name so they can be properly resolved by providers.
+ const name = withoutQuotes(family.name);
+
+ return {
+ ...family,
+ name,
+ // This will be used in CSS font faces. Quotes are added by the CSS renderer if
+ // this value contains a space.
+ uniqueName: `${name}-${hasher.hashObject(family)}`,
+ weights: family.weights ? dedupe(family.weights.map((weight) => weight.toString())) : undefined,
+ styles: family.styles ? dedupe(family.styles) : undefined,
+ subsets: family.subsets ? dedupe(family.subsets) : undefined,
+ formats: family.formats ? dedupe(family.formats) : undefined,
+ fallbacks: family.fallbacks ? dedupe(family.fallbacks) : undefined,
+ unicodeRange: family.unicodeRange ? dedupe(family.unicodeRange) : undefined,
+ };
+}
diff --git a/packages/astro/src/assets/fonts/definitions.ts b/packages/astro/src/assets/fonts/definitions.ts
index e80e5e262f94..00c0cb68fc8b 100644
--- a/packages/astro/src/assets/fonts/definitions.ts
+++ b/packages/astro/src/assets/fonts/definitions.ts
@@ -1,11 +1,11 @@
import type * as unifont from 'unifont';
import type { CollectedFontForMetrics } from './core/optimize-fallbacks.js';
import type {
+ CssProperties,
FontFaceMetrics,
FontFileData,
FontType,
GenericFallbackName,
- PreloadData,
ResolveFontOptions,
Style,
} from './types.js';
@@ -15,46 +15,15 @@ export interface Hasher {
hashObject: (input: Record) => string;
}
-export interface LocalProviderUrlResolver {
- resolve: (input: string) => string;
-}
-
-export interface ProxyData {
- weight: unifont.FontFaceData['weight'];
- style: unifont.FontFaceData['style'];
- subset: NonNullable['subset'];
-}
-
-export interface UrlProxy {
- proxy: (
- input: Pick & {
- type: FontType;
- collectPreload: boolean;
- data: ProxyData;
- },
- ) => string;
-}
-
export interface UrlResolver {
resolve: (hash: string) => string;
readonly cspResources: Array;
}
-export interface UrlProxyContentResolver {
+export interface FontFileContentResolver {
resolve: (url: string) => string;
}
-export interface DataCollector {
- collect: (
- input: FontFileData & {
- data: ProxyData;
- preload: PreloadData | null;
- },
- ) => void;
-}
-
-export type CssProperties = Record;
-
export interface CssRenderer {
generateFontFace: (family: string, properties: CssProperties) => string;
generateCssVariable: (key: string, values: Array) => string;
@@ -91,12 +60,12 @@ export interface FontFileReader {
};
}
-export interface UrlProxyHashResolver {
- resolve: (input: {
+export interface FontFileIdGenerator {
+ generate: (input: {
originalUrl: string;
type: FontType;
cssVariable: string;
- data: ProxyData;
+ font: unifont.FontFaceData;
}) => string;
}
diff --git a/packages/astro/src/assets/fonts/infra/build-font-file-id-generator.ts b/packages/astro/src/assets/fonts/infra/build-font-file-id-generator.ts
new file mode 100644
index 000000000000..0d445e29e422
--- /dev/null
+++ b/packages/astro/src/assets/fonts/infra/build-font-file-id-generator.ts
@@ -0,0 +1,22 @@
+import type { FontFileContentResolver, FontFileIdGenerator, Hasher } from '../definitions.js';
+import type { FontType } from '../types.js';
+
+export class BuildFontFileIdGenerator implements FontFileIdGenerator {
+ readonly #hasher: Hasher;
+ readonly #contentResolver: FontFileContentResolver;
+
+ constructor({
+ hasher,
+ contentResolver,
+ }: {
+ hasher: Hasher;
+ contentResolver: FontFileContentResolver;
+ }) {
+ this.#hasher = hasher;
+ this.#contentResolver = contentResolver;
+ }
+
+ generate({ originalUrl, type }: { originalUrl: string; type: FontType }): string {
+ return `${this.#hasher.hashString(this.#contentResolver.resolve(originalUrl))}.${type}`;
+ }
+}
diff --git a/packages/astro/src/assets/fonts/infra/build-url-proxy-hash-resolver.ts b/packages/astro/src/assets/fonts/infra/build-url-proxy-hash-resolver.ts
deleted file mode 100644
index 6012bac16d41..000000000000
--- a/packages/astro/src/assets/fonts/infra/build-url-proxy-hash-resolver.ts
+++ /dev/null
@@ -1,35 +0,0 @@
-import type {
- Hasher,
- ProxyData,
- UrlProxyContentResolver,
- UrlProxyHashResolver,
-} from '../definitions.js';
-import type { FontType } from '../types.js';
-
-export class BuildUrlProxyHashResolver implements UrlProxyHashResolver {
- readonly #hasher: Hasher;
- readonly #contentResolver: UrlProxyContentResolver;
-
- constructor({
- hasher,
- contentResolver,
- }: {
- hasher: Hasher;
- contentResolver: UrlProxyContentResolver;
- }) {
- this.#hasher = hasher;
- this.#contentResolver = contentResolver;
- }
-
- resolve({
- originalUrl,
- type,
- }: {
- originalUrl: string;
- type: FontType;
- cssVariable: string;
- data: ProxyData;
- }): string {
- return `${this.#hasher.hashString(this.#contentResolver.resolve(originalUrl))}.${type}`;
- }
-}
diff --git a/packages/astro/src/assets/fonts/infra/cached-font-fetcher.ts b/packages/astro/src/assets/fonts/infra/cached-font-fetcher.ts
index afaee2ff72ed..a7b8a5c9e507 100644
--- a/packages/astro/src/assets/fonts/infra/cached-font-fetcher.ts
+++ b/packages/astro/src/assets/fonts/infra/cached-font-fetcher.ts
@@ -2,7 +2,6 @@ import { isAbsolute } from 'node:path';
import { AstroError, AstroErrorData } from '../../../core/errors/index.js';
import type { FontFetcher, Storage } from '../definitions.js';
import type { FontFileData } from '../types.js';
-import { cache } from '../utils.js';
export class CachedFontFetcher implements FontFetcher {
readonly #storage: Storage;
@@ -23,8 +22,18 @@ export class CachedFontFetcher implements FontFetcher {
this.#readFile = readFile;
}
+ async #cache(storage: Storage, key: string, cb: () => Promise): Promise {
+ const existing = await storage.getItemRaw(key);
+ if (existing) {
+ return existing;
+ }
+ const data = await cb();
+ await storage.setItemRaw(key, data);
+ return data;
+ }
+
async fetch({ hash, url, init }: FontFileData): Promise {
- return await cache(this.#storage, hash, async () => {
+ return await this.#cache(this.#storage, hash, async () => {
try {
if (isAbsolute(url)) {
return await this.#readFile(url);
diff --git a/packages/astro/src/assets/fonts/infra/capsize-font-metrics-resolver.ts b/packages/astro/src/assets/fonts/infra/capsize-font-metrics-resolver.ts
index 9a27a2bf499a..f3f5eae49019 100644
--- a/packages/astro/src/assets/fonts/infra/capsize-font-metrics-resolver.ts
+++ b/packages/astro/src/assets/fonts/infra/capsize-font-metrics-resolver.ts
@@ -1,12 +1,7 @@
import { type Font, fromBuffer } from '@capsizecss/unpack';
import type { CollectedFontForMetrics } from '../core/optimize-fallbacks.js';
-import type {
- CssProperties,
- CssRenderer,
- FontFetcher,
- FontMetricsResolver,
-} from '../definitions.js';
-import type { FontFaceMetrics } from '../types.js';
+import type { CssRenderer, FontFetcher, FontMetricsResolver } from '../definitions.js';
+import type { CssProperties, FontFaceMetrics } from '../types.js';
import { renderFontSrc } from '../utils.js';
// Source: https://github.com/unjs/fontaine/blob/main/src/metrics.ts
diff --git a/packages/astro/src/assets/fonts/infra/data-collector.ts b/packages/astro/src/assets/fonts/infra/data-collector.ts
deleted file mode 100644
index decef6fc9eec..000000000000
--- a/packages/astro/src/assets/fonts/infra/data-collector.ts
+++ /dev/null
@@ -1,38 +0,0 @@
-import type { DataCollector, ProxyData } from '../definitions.js';
-import type { CreateUrlProxyParams, FontFileData, PreloadData } from '../types.js';
-
-// TODO: investigate converting to core logic
-export class RealDataCollector implements DataCollector {
- readonly #hasUrl: CreateUrlProxyParams['hasUrl'];
- readonly #saveUrl: CreateUrlProxyParams['saveUrl'];
- readonly #savePreload: CreateUrlProxyParams['savePreload'];
- readonly #saveFontData: CreateUrlProxyParams['saveFontData'];
-
- constructor({
- hasUrl,
- saveUrl,
- savePreload,
- saveFontData,
- }: Pick) {
- this.#hasUrl = hasUrl;
- this.#saveUrl = saveUrl;
- this.#savePreload = savePreload;
- this.#saveFontData = saveFontData;
- }
-
- collect({
- hash,
- url,
- init,
- preload,
- data,
- }: FontFileData & { data: ProxyData; preload: PreloadData | null }): void {
- if (!this.#hasUrl(hash)) {
- this.#saveUrl({ hash, url, init });
- if (preload) {
- this.#savePreload(preload);
- }
- }
- this.#saveFontData({ hash, url, data, init });
- }
-}
diff --git a/packages/astro/src/assets/fonts/infra/dev-url-proxy-hash-resolver.ts b/packages/astro/src/assets/fonts/infra/dev-font-file-id-generator.ts
similarity index 57%
rename from packages/astro/src/assets/fonts/infra/dev-url-proxy-hash-resolver.ts
rename to packages/astro/src/assets/fonts/infra/dev-font-file-id-generator.ts
index b94ac60e3ba8..96b58d49bdc4 100644
--- a/packages/astro/src/assets/fonts/infra/dev-url-proxy-hash-resolver.ts
+++ b/packages/astro/src/assets/fonts/infra/dev-font-file-id-generator.ts
@@ -1,29 +1,23 @@
-import type {
- Hasher,
- ProxyData,
- UrlProxyContentResolver,
- UrlProxyHashResolver,
-} from '../definitions.js';
+import type * as unifont from 'unifont';
+import type { FontFileContentResolver, FontFileIdGenerator, Hasher } from '../definitions.js';
import type { FontType } from '../types.js';
-export class DevUrlProxyHashResolver implements UrlProxyHashResolver {
+export class DevFontFileIdGenerator implements FontFileIdGenerator {
readonly #hasher: Hasher;
- readonly #contentResolver: UrlProxyContentResolver;
+ readonly #contentResolver: FontFileContentResolver;
constructor({
hasher,
contentResolver,
}: {
hasher: Hasher;
- contentResolver: UrlProxyContentResolver;
+ contentResolver: FontFileContentResolver;
}) {
this.#hasher = hasher;
this.#contentResolver = contentResolver;
}
- #formatWeight(
- weight: Parameters[0]['data']['weight'],
- ): string | undefined {
+ #formatWeight(weight: unifont.FontFaceData['weight']): string | undefined {
if (Array.isArray(weight)) {
return weight.join('-');
}
@@ -33,22 +27,22 @@ export class DevUrlProxyHashResolver implements UrlProxyHashResolver {
return weight?.replace(/\s+/g, '-');
}
- resolve({
+ generate({
cssVariable,
- data,
originalUrl,
type,
+ font,
}: {
originalUrl: string;
type: FontType;
cssVariable: string;
- data: ProxyData;
+ font: unifont.FontFaceData;
}): string {
return [
cssVariable.slice(2),
- this.#formatWeight(data.weight),
- data.style,
- data.subset,
+ this.#formatWeight(font.weight),
+ font.style,
+ font.meta?.subset,
`${this.#hasher.hashString(this.#contentResolver.resolve(originalUrl))}.${type}`,
]
.filter(Boolean)
diff --git a/packages/astro/src/assets/fonts/infra/fs-font-file-content-resolver.ts b/packages/astro/src/assets/fonts/infra/fs-font-file-content-resolver.ts
new file mode 100644
index 000000000000..338478c362bf
--- /dev/null
+++ b/packages/astro/src/assets/fonts/infra/fs-font-file-content-resolver.ts
@@ -0,0 +1,28 @@
+import { isAbsolute } from 'node:path';
+import { AstroError, AstroErrorData } from '../../../core/errors/index.js';
+import type { FontFileContentResolver } from '../definitions.js';
+
+type ReadFileSync = (path: string) => string;
+
+export class FsFontFileContentResolver implements FontFileContentResolver {
+ #readFileSync: ReadFileSync;
+
+ constructor({ readFileSync }: { readFileSync: ReadFileSync }) {
+ this.#readFileSync = readFileSync;
+ }
+
+ resolve(url: string): string {
+ if (!isAbsolute(url)) {
+ // HTTP URLs are enough
+ return url;
+ }
+ try {
+ // We use the url and the file content for the hash generation because:
+ // - The URL is not hashed unlike remote providers
+ // - A font file can renamed and swapped so we would incorrectly cache it
+ return url + this.#readFileSync(url);
+ } catch (cause) {
+ throw new AstroError(AstroErrorData.UnknownFilesystemError, { cause });
+ }
+ }
+}
diff --git a/packages/astro/src/assets/fonts/infra/local-url-proxy-content-resolver.ts b/packages/astro/src/assets/fonts/infra/local-url-proxy-content-resolver.ts
deleted file mode 100644
index 0cac797ab161..000000000000
--- a/packages/astro/src/assets/fonts/infra/local-url-proxy-content-resolver.ts
+++ /dev/null
@@ -1,16 +0,0 @@
-import { readFileSync } from 'node:fs';
-import { AstroError, AstroErrorData } from '../../../core/errors/index.js';
-import type { UrlProxyContentResolver } from '../definitions.js';
-
-export class LocalUrlProxyContentResolver implements UrlProxyContentResolver {
- resolve(url: string): string {
- try {
- // We use the url and the file content for the hash generation because:
- // - The URL is not hashed unlike remote providers
- // - A font file can renamed and swapped so we would incorrectly cache it
- return url + readFileSync(url, 'utf-8');
- } catch (cause) {
- throw new AstroError(AstroErrorData.UnknownFilesystemError, { cause });
- }
- }
-}
diff --git a/packages/astro/src/assets/fonts/infra/minifiable-css-renderer.ts b/packages/astro/src/assets/fonts/infra/minifiable-css-renderer.ts
index 0b9cedb3a671..24f2d6ac8a23 100644
--- a/packages/astro/src/assets/fonts/infra/minifiable-css-renderer.ts
+++ b/packages/astro/src/assets/fonts/infra/minifiable-css-renderer.ts
@@ -1,4 +1,5 @@
-import type { CssProperties, CssRenderer } from '../definitions.js';
+import type { CssRenderer } from '../definitions.js';
+import type { CssProperties } from '../types.js';
// TODO: consider making these public methods
diff --git a/packages/astro/src/assets/fonts/infra/font-type-extractor.ts b/packages/astro/src/assets/fonts/infra/node-font-type-extractor.ts
similarity index 87%
rename from packages/astro/src/assets/fonts/infra/font-type-extractor.ts
rename to packages/astro/src/assets/fonts/infra/node-font-type-extractor.ts
index 201f816f30de..6867fcf143e2 100644
--- a/packages/astro/src/assets/fonts/infra/font-type-extractor.ts
+++ b/packages/astro/src/assets/fonts/infra/node-font-type-extractor.ts
@@ -4,8 +4,7 @@ import type { FontTypeExtractor } from '../definitions.js';
import type { FontType } from '../types.js';
import { isFontType } from '../utils.js';
-// TODO: find better name
-export class RealFontTypeExtractor implements FontTypeExtractor {
+export class NodeFontTypeExtractor implements FontTypeExtractor {
extract(url: string): FontType {
const extension = extname(url).slice(1);
if (!isFontType(extension)) {
diff --git a/packages/astro/src/assets/fonts/infra/remote-url-proxy-content-resolver.ts b/packages/astro/src/assets/fonts/infra/remote-url-proxy-content-resolver.ts
deleted file mode 100644
index 885b36e2ed3d..000000000000
--- a/packages/astro/src/assets/fonts/infra/remote-url-proxy-content-resolver.ts
+++ /dev/null
@@ -1,8 +0,0 @@
-import type { UrlProxyContentResolver } from '../definitions.js';
-
-export class RemoteUrlProxyContentResolver implements UrlProxyContentResolver {
- // Passthrough, the remote provider URL is enough
- resolve(url: string): string {
- return url;
- }
-}
diff --git a/packages/astro/src/assets/fonts/infra/require-local-provider-url-resolver.ts b/packages/astro/src/assets/fonts/infra/require-local-provider-url-resolver.ts
deleted file mode 100644
index 89992b59063d..000000000000
--- a/packages/astro/src/assets/fonts/infra/require-local-provider-url-resolver.ts
+++ /dev/null
@@ -1,38 +0,0 @@
-import { createRequire } from 'node:module';
-import { fileURLToPath, pathToFileURL } from 'node:url';
-import type { LocalProviderUrlResolver } from '../definitions.js';
-
-export class RequireLocalProviderUrlResolver implements LocalProviderUrlResolver {
- readonly #root: URL;
- // TODO: remove when stabilizing
- readonly #intercept: ((path: string) => void) | undefined;
-
- constructor({
- root,
- intercept,
- }: {
- root: URL;
- intercept?: ((path: string) => void) | undefined;
- }) {
- this.#root = root;
- this.#intercept = intercept;
- }
-
- #resolveEntrypoint(root: URL, entrypoint: string): URL {
- const require = createRequire(root);
-
- try {
- return pathToFileURL(require.resolve(entrypoint));
- } catch {
- return new URL(entrypoint, root);
- }
- }
-
- resolve(input: string): string {
- // fileURLToPath is important so that the file can be read
- // by createLocalUrlProxyContentResolver
- const path = fileURLToPath(this.#resolveEntrypoint(this.#root, input));
- this.#intercept?.(path);
- return path;
- }
-}
diff --git a/packages/astro/src/assets/fonts/infra/unifont-font-resolver.ts b/packages/astro/src/assets/fonts/infra/unifont-font-resolver.ts
index d93b646513dc..5a190dfb145c 100644
--- a/packages/astro/src/assets/fonts/infra/unifont-font-resolver.ts
+++ b/packages/astro/src/assets/fonts/infra/unifont-font-resolver.ts
@@ -1,6 +1,5 @@
import type { FontFaceData, Provider } from 'unifont';
import { createUnifont, defineFontProvider, type Unifont } from 'unifont';
-import { LOCAL_PROVIDER_NAME } from '../constants.js';
import type { FontResolver, Hasher, Storage } from '../definitions.js';
import type { FontProvider, ResolvedFontFamily, ResolveFontOptions } from '../types.js';
@@ -16,9 +15,9 @@ export class UnifontFontResolver implements FontResolver {
this.#unifont = unifont;
}
- static astroToUnifontProvider(astroProvider: FontProvider): Provider {
+ static astroToUnifontProvider(astroProvider: FontProvider, root: URL): Provider {
return defineFontProvider(astroProvider.name, async (_options: any, ctx) => {
- await astroProvider?.init?.(ctx);
+ await astroProvider?.init?.({ ...ctx, root });
return {
async resolveFont(familyName, { options, ...rest }) {
return await astroProvider.resolveFont({ familyName, options, ...rest });
@@ -33,20 +32,17 @@ export class UnifontFontResolver implements FontResolver {
static extractUnifontProviders({
families,
hasher,
+ root,
}: {
families: Array;
hasher: Hasher;
+ root: URL;
}) {
const hashes = new Set();
const providers: Array = [];
for (const { provider } of families) {
- // The local provider logic happens outside of unifont
- if (provider === LOCAL_PROVIDER_NAME) {
- continue;
- }
-
- const unifontProvider = this.astroToUnifontProvider(provider);
+ const unifontProvider = this.astroToUnifontProvider(provider, root);
const hash = hasher.hashObject({
name: unifontProvider._name,
...provider.config,
@@ -75,13 +71,15 @@ export class UnifontFontResolver implements FontResolver {
families,
hasher,
storage,
+ root,
}: {
families: Array;
hasher: Hasher;
storage: Storage;
+ root: URL;
}) {
return new UnifontFontResolver({
- unifont: await createUnifont(this.extractUnifontProviders({ families, hasher }), {
+ unifont: await createUnifont(this.extractUnifontProviders({ families, hasher, root }), {
storage,
// TODO: consider enabling, would require new astro errors
throwOnError: false,
diff --git a/packages/astro/src/assets/fonts/infra/url-proxy.ts b/packages/astro/src/assets/fonts/infra/url-proxy.ts
deleted file mode 100644
index 21853364ed3a..000000000000
--- a/packages/astro/src/assets/fonts/infra/url-proxy.ts
+++ /dev/null
@@ -1,72 +0,0 @@
-import type {
- DataCollector,
- ProxyData,
- UrlProxy,
- UrlProxyHashResolver,
- UrlResolver,
-} from '../definitions.js';
-import type { FontFileData, FontType } from '../types.js';
-import { renderFontWeight } from '../utils.js';
-
-// TODO: find a better name
-export class RealUrlProxy implements UrlProxy {
- readonly #hashResolver: UrlProxyHashResolver;
- readonly #dataCollector: DataCollector;
- readonly #urlResolver: UrlResolver;
- readonly #cssVariable: string;
-
- constructor({
- hashResolver,
- dataCollector,
- urlResolver,
- cssVariable,
- }: {
- hashResolver: UrlProxyHashResolver;
- dataCollector: DataCollector;
- urlResolver: UrlResolver;
- cssVariable: string;
- }) {
- this.#hashResolver = hashResolver;
- this.#dataCollector = dataCollector;
- this.#urlResolver = urlResolver;
- this.#cssVariable = cssVariable;
- }
-
- proxy({
- url: originalUrl,
- type,
- data,
- collectPreload,
- init,
- }: Pick & {
- type: FontType;
- collectPreload: boolean;
- data: ProxyData;
- }): string {
- const hash = this.#hashResolver.resolve({
- cssVariable: this.#cssVariable,
- data,
- originalUrl,
- type,
- });
- const url = this.#urlResolver.resolve(hash);
-
- this.#dataCollector.collect({
- url: originalUrl,
- hash,
- preload: collectPreload
- ? {
- url,
- type,
- weight: renderFontWeight(data.weight),
- style: data.style,
- subset: data.subset,
- }
- : null,
- data,
- init,
- });
-
- return url;
- }
-}
diff --git a/packages/astro/src/assets/fonts/orchestrate.ts b/packages/astro/src/assets/fonts/orchestrate.ts
deleted file mode 100644
index 04e1d5653ad1..000000000000
--- a/packages/astro/src/assets/fonts/orchestrate.ts
+++ /dev/null
@@ -1,295 +0,0 @@
-import type * as unifont from 'unifont';
-import type { Logger } from '../../core/logger/core.js';
-import { LOCAL_PROVIDER_NAME } from './constants.js';
-import { normalizeRemoteFontFaces } from './core/normalize-remote-font-faces.js';
-import { type CollectedFontForMetrics, optimizeFallbacks } from './core/optimize-fallbacks.js';
-import { resolveFamilies } from './core/resolve-families.js';
-import type {
- CssRenderer,
- FontFileReader,
- FontMetricsResolver,
- FontResolver,
- FontTypeExtractor,
- Hasher,
- LocalProviderUrlResolver,
- StringMatcher,
- SystemFallbacksProvider,
- UrlProxy,
-} from './definitions.js';
-import { resolveLocalFont } from './providers/local.js';
-import type {
- ConsumableMap,
- CreateUrlProxyParams,
- Defaults,
- FontData,
- FontFamily,
- FontFileDataMap,
- InternalConsumableMap,
- PreloadData,
- ResolvedFontFamily,
-} from './types.js';
-import {
- pickFontFaceProperty,
- renderFontWeight,
- unifontFontFaceDataToProperties,
-} from './utils.js';
-
-/**
- * Manages how fonts are resolved:
- *
- * - families are resolved
- * - font resolver is initialized
- *
- * For each family:
- * - We create a URL proxy
- * - We resolve the font and normalize the result
- *
- * For each resolved font:
- * - We generate the CSS font face
- * - We generate optimized fallbacks if applicable
- * - We generate CSS variables
- *
- * Once that's done, the collected data is returned
- */
-export async function orchestrate({
- families,
- hasher,
- localProviderUrlResolver,
- cssRenderer,
- systemFallbacksProvider,
- fontMetricsResolver,
- fontTypeExtractor,
- fontFileReader,
- logger,
- createUrlProxy,
- defaults,
- bold,
- stringMatcher,
- createFontResolver,
-}: {
- families: Array;
- hasher: Hasher;
- localProviderUrlResolver: LocalProviderUrlResolver;
- cssRenderer: CssRenderer;
- systemFallbacksProvider: SystemFallbacksProvider;
- fontMetricsResolver: FontMetricsResolver;
- fontTypeExtractor: FontTypeExtractor;
- fontFileReader: FontFileReader;
- logger: Logger;
- createUrlProxy: (params: CreateUrlProxyParams) => UrlProxy;
- defaults: Defaults;
- bold: (input: string) => string;
- stringMatcher: StringMatcher;
- createFontResolver: (params: { families: Array }) => Promise;
-}): Promise<{
- fontFileDataMap: FontFileDataMap;
- internalConsumableMap: InternalConsumableMap;
- consumableMap: ConsumableMap;
-}> {
- const resolvedFamilies = resolveFamilies({
- families,
- hasher,
- localProviderUrlResolver,
- });
-
- const fontResolver = await createFontResolver({ families: resolvedFamilies });
-
- /**
- * Holds associations of hash and original font file URLs, so they can be
- * downloaded whenever the hash is requested.
- */
- const fontFileDataMap: FontFileDataMap = new Map();
- /**
- * Holds associations of CSS variables and preloadData/css to be passed to the internal virtual module.
- */
- const internalConsumableMap: InternalConsumableMap = new Map();
- /**
- * Holds associations of CSS variables and font data to be exposed via virtual module.
- */
- const consumableMap: ConsumableMap = new Map();
-
- /**
- * Holds family data by a key, to allow merging families
- */
- const resolvedFamiliesMap = new Map<
- string,
- {
- family: ResolvedFontFamily;
- fonts: Array;
- fallbacks: Array;
- /**
- * Holds a list of font files to be used for optimized fallbacks generation
- */
- collectedFonts: Array;
- preloadData: Array;
- }
- >();
-
- // First loop: we try to merge families. This is useful for advanced cases, where eg. you want
- // 500, 600, 700 as normal but also 500 as italic. That requires 2 families
- for (const family of resolvedFamilies) {
- const key = `${family.cssVariable}:${family.name}:${typeof family.provider === 'string' ? family.provider : family.provider.name}`;
- let resolvedFamily = resolvedFamiliesMap.get(key);
- if (!resolvedFamily) {
- if (
- Array.from(resolvedFamiliesMap.keys()).find((k) => k.startsWith(`${family.cssVariable}:`))
- ) {
- logger.warn(
- 'assets',
- `Several font families have been registered for the ${bold(family.cssVariable)} cssVariable but they do not share the same name and provider.`,
- );
- logger.warn(
- 'assets',
- 'These families will not be merged together. The last occurrence will override previous families for this cssVariable. Review your Astro configuration.',
- );
- }
- resolvedFamily = {
- family,
- fonts: [],
- fallbacks: family.fallbacks ?? defaults.fallbacks ?? [],
- collectedFonts: [],
- preloadData: [],
- };
- resolvedFamiliesMap.set(key, resolvedFamily);
- }
-
- /**
- * Allows collecting and transforming original URLs from providers, so the Vite
- * plugin has control over URLs.
- */
- const urlProxy = createUrlProxy({
- local: family.provider === LOCAL_PROVIDER_NAME,
- hasUrl: (hash) => fontFileDataMap.has(hash),
- saveUrl: ({ hash, url, init }) => {
- fontFileDataMap.set(hash, { url, init });
- },
- savePreload: (preload) => {
- resolvedFamily.preloadData.push(preload);
- },
- saveFontData: (collected) => {
- if (
- resolvedFamily.fallbacks &&
- resolvedFamily.fallbacks.length > 0 &&
- // If the same data has already been sent for this family, we don't want to have
- // duplicated fallbacks. Such scenario can occur with unicode ranges.
- !resolvedFamily.collectedFonts.some(
- (f) => JSON.stringify(f.data) === JSON.stringify(collected.data),
- )
- ) {
- // If a family has fallbacks, we store the first url we get that may
- // be used for the fallback generation.
- resolvedFamily.collectedFonts.push(collected);
- }
- },
- cssVariable: family.cssVariable,
- });
-
- if (family.provider === LOCAL_PROVIDER_NAME) {
- const fonts = resolveLocalFont({
- family,
- urlProxy,
- fontTypeExtractor,
- fontFileReader,
- });
- // URLs are already proxied at this point so no further processing is required
- resolvedFamily.fonts.push(...fonts);
- } else {
- const fonts = await fontResolver.resolveFont({
- familyName: family.name,
- provider: family.provider.name,
- // We do not merge the defaults, we only provide defaults as a fallback
- weights: family.weights ?? defaults.weights,
- styles: family.styles ?? defaults.styles,
- subsets: family.subsets ?? defaults.subsets,
- formats: family.formats ?? defaults.formats,
- options: family.options,
- });
- if (fonts.length === 0) {
- logger.warn(
- 'assets',
- `No data found for font family ${bold(family.name)}. Review your configuration`,
- );
- const availableFamilies = await fontResolver.listFonts({ provider: family.provider.name });
- if (
- availableFamilies &&
- availableFamilies.length > 0 &&
- !availableFamilies.includes(family.name)
- ) {
- logger.warn(
- 'assets',
- `${bold(family.name)} font family cannot be retrieved by the provider. Did you mean ${bold(stringMatcher.getClosestMatch(family.name, availableFamilies))}?`,
- );
- }
- }
- // The data returned by the remote provider contains original URLs. We proxy them.
- resolvedFamily.fonts = normalizeRemoteFontFaces({ fonts, urlProxy, fontTypeExtractor });
- }
- }
-
- // We know about all the families, let's generate css, fallbacks and more
- for (const {
- family,
- fonts,
- fallbacks,
- collectedFonts,
- preloadData,
- } of resolvedFamiliesMap.values()) {
- const consumableMapValue: Array = [];
- let css = '';
-
- for (const data of fonts) {
- css += cssRenderer.generateFontFace(
- family.nameWithHash,
- unifontFontFaceDataToProperties({
- src: data.src,
- weight: data.weight,
- style: data.style,
- // User settings override the generated font settings. We use a helper function
- // because local and remote providers store this data in different places.
- display: pickFontFaceProperty('display', { data, family }),
- unicodeRange: pickFontFaceProperty('unicodeRange', { data, family }),
- stretch: pickFontFaceProperty('stretch', { data, family }),
- featureSettings: pickFontFaceProperty('featureSettings', { data, family }),
- variationSettings: pickFontFaceProperty('variationSettings', { data, family }),
- }),
- );
-
- consumableMapValue.push({
- weight: renderFontWeight(data.weight),
- style: data.style,
- src: data.src
- .filter((src) => 'url' in src)
- .map((src) => ({
- url: src.url,
- format: src.format,
- tech: src.tech,
- })),
- });
- }
-
- const cssVarValues = [family.nameWithHash];
- const optimizeFallbacksResult = await optimizeFallbacks({
- family,
- fallbacks,
- collectedFonts,
- enabled: family.optimizedFallbacks ?? defaults.optimizedFallbacks ?? false,
- systemFallbacksProvider,
- fontMetricsResolver,
- });
-
- if (optimizeFallbacksResult) {
- css += optimizeFallbacksResult.css;
- cssVarValues.push(...optimizeFallbacksResult.fallbacks);
- } else {
- // If there are no optimized fallbacks, we pass the provided fallbacks as is.
- cssVarValues.push(...fallbacks);
- }
-
- css += cssRenderer.generateCssVariable(family.cssVariable, cssVarValues);
-
- internalConsumableMap.set(family.cssVariable, { preloadData, css });
- consumableMap.set(family.cssVariable, consumableMapValue);
- }
-
- return { fontFileDataMap, internalConsumableMap, consumableMap };
-}
diff --git a/packages/astro/src/assets/fonts/providers/index.ts b/packages/astro/src/assets/fonts/providers/index.ts
index f9707adf91a4..c5134a8e5a81 100644
--- a/packages/astro/src/assets/fonts/providers/index.ts
+++ b/packages/astro/src/assets/fonts/providers/index.ts
@@ -5,7 +5,9 @@ import {
type InitializedProvider,
providers,
} from 'unifont';
+import { FontaceFontFileReader } from '../infra/fontace-font-file-reader.js';
import type { FontProvider } from '../types.js';
+import { type LocalFamilyOptions, LocalFontProvider } from './local.js';
/** [Adobe](https://fonts.adobe.com/) */
function adobe(config: AdobeProviderOptions): FontProvider {
@@ -116,14 +118,22 @@ function googleicons(): FontProvider {
};
}
+/** TODO: */
+function local(): FontProvider {
+ return new LocalFontProvider({
+ fontFileReader: new FontaceFontFileReader(),
+ });
+}
+
/**
- * Astro re-exports most [unifont](https://github.com/unjs/unifont/) providers:
+ * Astro exports a few built-in providers:
* - [Adobe](https://fonts.adobe.com/)
* - [Bunny](https://fonts.bunny.net/)
* - [Fontshare](https://www.fontshare.com/)
* - [Fontsource](https://fontsource.org/)
* - [Google](https://fonts.google.com/)
* - [Google Icons](https://fonts.google.com/icons)
+ * - Local
*/
export const fontProviders = {
adobe,
@@ -132,4 +142,5 @@ export const fontProviders = {
fontsource,
google,
googleicons,
+ local,
};
diff --git a/packages/astro/src/assets/fonts/providers/local.ts b/packages/astro/src/assets/fonts/providers/local.ts
index 09e13f22827d..8478d1adefc9 100644
--- a/packages/astro/src/assets/fonts/providers/local.ts
+++ b/packages/astro/src/assets/fonts/providers/local.ts
@@ -1,67 +1,134 @@
+import { createRequire } from 'node:module';
+import { fileURLToPath, pathToFileURL } from 'node:url';
import type * as unifont from 'unifont';
-import { FONT_FORMATS } from '../constants.js';
-import type { FontFileReader, FontTypeExtractor, UrlProxy } from '../definitions.js';
-import type { ResolvedLocalFontFamily } from '../types.js';
+import type { FontFileReader } from '../definitions.js';
+import type {
+ FamilyProperties,
+ FontProvider,
+ FontProviderInitContext,
+ ResolveFontOptions,
+ Style,
+ Weight,
+} from '../types.js';
-interface Options {
- family: ResolvedLocalFontFamily;
- urlProxy: UrlProxy;
- fontTypeExtractor: FontTypeExtractor;
- fontFileReader: FontFileReader;
+interface NormalizedSource {
+ url: string;
+ tech: string | undefined;
}
-export function resolveLocalFont({
- family,
- urlProxy,
- fontTypeExtractor,
- fontFileReader,
-}: Options): Array {
- return family.variants.map((variant) => {
- const shouldInfer = variant.weight === undefined || variant.style === undefined;
+type RawSource =
+ | string
+ | URL
+ | {
+ url: string | URL;
+ tech?: string | undefined;
+ };
- // We prepare the data
- const data: unifont.FontFaceData = {
- // If it should be inferred, we don't want to set the value
- weight: variant.weight,
- style: variant.style,
- src: [],
- unicodeRange: variant.unicodeRange,
- display: variant.display,
- stretch: variant.stretch,
- featureSettings: variant.featureSettings,
- variationSettings: variant.variationSettings,
+interface Variant extends FamilyProperties {
+ /**
+ * Font [sources](https://developer.mozilla.org/en-US/docs/Web/CSS/@font-face/src). It can be a path relative to the root, a package import or a URL. URLs are particularly useful if you inject local fonts through an integration.
+ */
+ src: [RawSource, ...Array];
+ /**
+ * A [font weight](https://developer.mozilla.org/en-US/docs/Web/CSS/font-weight). If the associated font is a [variable font](https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_fonts/Variable_fonts_guide), you can specify a range of weights:
+ *
+ * ```js
+ * weight: "100 900"
+ * ```
+ */
+ weight?: Weight | undefined;
+ /**
+ * A [font style](https://developer.mozilla.org/en-US/docs/Web/CSS/font-style).
+ */
+ style?: Style | undefined;
+}
+
+export interface LocalFamilyOptions {
+ /**
+ * Each variant represents a [`@font-face` declaration](https://developer.mozilla.org/en-US/docs/Web/CSS/@font-face/).
+ */
+ variants: [Variant, ...Array];
+}
+
+export class LocalFontProvider implements FontProvider {
+ name = 'local';
+ config?: Record | undefined;
+
+ #fontFileReader: FontFileReader;
+ #root: URL | undefined;
+
+ constructor({
+ fontFileReader,
+ }: {
+ fontFileReader: FontFileReader;
+ }) {
+ this.config = undefined;
+ this.#fontFileReader = fontFileReader;
+ this.#root = undefined;
+ }
+
+ init(context: Pick): void {
+ this.#root = context.root;
+ }
+
+ #resolveEntrypoint(root: URL, entrypoint: string): URL {
+ const require = createRequire(root);
+
+ try {
+ return pathToFileURL(require.resolve(entrypoint));
+ } catch {
+ return new URL(entrypoint, root);
+ }
+ }
+
+ #normalizeSource(value: RawSource): NormalizedSource {
+ const isValue = typeof value === 'string' || value instanceof URL;
+ const url = (isValue ? value : value.url).toString();
+ const tech = isValue ? undefined : value.tech;
+ return {
+ url: fileURLToPath(this.#resolveEntrypoint(this.#root ?? new URL(import.meta.url), url)),
+ tech,
};
- // We proxy each source
- data.src = variant.src.map((source, index) => {
- // We only try to infer for the first source. Indeed if it doesn't work, the function
- // call will throw an error so that will be interrupted anyways
- if (shouldInfer && index === 0) {
- const result = fontFileReader.extract({ family: family.name, url: source.url });
- if (variant.weight === undefined) data.weight = result.weight;
- if (variant.style === undefined) data.style = result.style;
- }
+ }
+
+ resolveFont(options: ResolveFontOptions): {
+ fonts: Array;
+ } {
+ return {
+ fonts:
+ options.options?.variants.map((variant) => {
+ const shouldInfer = variant.weight === undefined || variant.style === undefined;
- const type = fontTypeExtractor.extract(source.url);
+ // We prepare the data
+ const data: unifont.FontFaceData = {
+ // If it should be inferred, we don't want to set the value
+ weight: variant.weight,
+ style: variant.style,
+ src: [],
+ unicodeRange: variant.unicodeRange,
+ display: variant.display,
+ stretch: variant.stretch,
+ featureSettings: variant.featureSettings,
+ variationSettings: variant.variationSettings,
+ };
+ // We proxy each source
+ data.src = variant.src.map((rawSource, index) => {
+ const source = this.#normalizeSource(rawSource);
+ // We only try to infer for the first source. Indeed if it doesn't work, the function
+ // call will throw an error so that will be interrupted anyways
+ if (shouldInfer && index === 0) {
+ const result = this.#fontFileReader.extract({
+ family: options.familyName,
+ url: source.url,
+ });
+ if (variant.weight === undefined) data.weight = result.weight;
+ if (variant.style === undefined) data.style = result.style;
+ }
- return {
- originalURL: source.url,
- url: urlProxy.proxy({
- url: source.url,
- type,
- // We only use the first source for preloading. For example if woff2 and woff
- // are available, we only keep woff2.
- collectPreload: index === 0,
- data: {
- weight: data.weight,
- style: data.style,
- subset: undefined,
- },
- init: null,
- }),
- format: FONT_FORMATS.find((e) => e.type === type)?.format,
- tech: source.tech,
- };
- });
- return data;
- });
+ return source;
+ });
+ return data;
+ }) ?? [],
+ };
+ }
}
diff --git a/packages/astro/src/assets/fonts/runtime.ts b/packages/astro/src/assets/fonts/runtime.ts
index d0d3943eeb3d..4f66e6c57ee5 100644
--- a/packages/astro/src/assets/fonts/runtime.ts
+++ b/packages/astro/src/assets/fonts/runtime.ts
@@ -1,13 +1,17 @@
import { AstroError, AstroErrorData } from '../../core/errors/index.js';
-import type { ConsumableMap, PreloadData, PreloadFilter } from './types.js';
+import type { FontDataByCssVariable, PreloadData, PreloadFilter } from './types.js';
-export function createGetFontData({ consumableMap }: { consumableMap?: ConsumableMap }) {
+export function createGetFontData({
+ fontDataByCssVariable,
+}: {
+ fontDataByCssVariable?: FontDataByCssVariable;
+}) {
return function getFontData(cssVariable: string) {
// TODO: remove once fonts are stabilized
- if (!consumableMap) {
+ if (!fontDataByCssVariable) {
throw new AstroError(AstroErrorData.ExperimentalFontsNotEnabled);
}
- const data = consumableMap.get(cssVariable);
+ const data = fontDataByCssVariable.get(cssVariable);
if (!data) {
throw new AstroError({
...AstroErrorData.FontFamilyNotFound,
diff --git a/packages/astro/src/assets/fonts/types.ts b/packages/astro/src/assets/fonts/types.ts
index 9e9c3dc35385..96312fa95b8b 100644
--- a/packages/astro/src/assets/fonts/types.ts
+++ b/packages/astro/src/assets/fonts/types.ts
@@ -2,13 +2,12 @@ import type { Font } from '@capsizecss/unpack';
import type * as unifont from 'unifont';
import type { z } from 'zod';
import type { displaySchema, styleSchema, weightSchema } from './config.js';
-import type { FONT_TYPES, GENERIC_FALLBACK_NAMES, LOCAL_PROVIDER_NAME } from './constants.js';
+import type { FONT_TYPES, GENERIC_FALLBACK_NAMES } from './constants.js';
import type { CollectedFontForMetrics } from './core/optimize-fallbacks.js';
-type Weight = z.infer;
+export type Weight = z.infer;
type Display = z.infer;
-/** @lintignore */
export interface FontProviderInitContext {
storage: {
getItem: {
@@ -17,6 +16,7 @@ export interface FontProviderInitContext {
};
setItem: (key: string, value: unknown) => Awaitable;
};
+ root: URL;
}
type Awaitable = T | Promise;
@@ -51,58 +51,7 @@ export interface FontProvider<
listFonts?: (() => Awaitable | undefined>) | undefined;
}
-interface RequiredFamilyAttributes {
- /**
- * The font family name, as identified by your font provider.
- */
- name: string;
- /**
- * A valid [ident](https://developer.mozilla.org/en-US/docs/Web/CSS/ident) in the form of a CSS variable (i.e. starting with `--`).
- */
- cssVariable: string;
-}
-
-interface Fallbacks {
- /**
- * @default `["sans-serif"]`
- *
- * An array of fonts to use when your chosen font is unavailable, or loading. Fallback fonts will be chosen in the order listed. The first available font will be used:
- *
- * ```js
- * fallbacks: ["CustomFont", "serif"]
- * ```
- *
- * To disable fallback fonts completely, configure an empty array:
- *
- * ```js
- * fallbacks: []
- * ```
- *
-
- * If the last font in the `fallbacks` array is a [generic family name](https://developer.mozilla.org/en-US/docs/Web/CSS/font-family#generic-name), Astro will attempt to generate [optimized fallbacks](https://developer.chrome.com/blog/font-fallbacks) using font metrics will be generated. To disable this optimization, set `optimizedFallbacks` to false.
- */
- fallbacks?: Array | undefined;
- /**
- * @default `true`
- *
- * Whether or not to enable optimized fallback generation. You may disable this default optimization to have full control over `fallbacks`.
- */
- optimizedFallbacks?: boolean | undefined;
-}
-
-interface FamilyProperties {
- /**
- * A [font weight](https://developer.mozilla.org/en-US/docs/Web/CSS/font-weight). If the associated font is a [variable font](https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_fonts/Variable_fonts_guide), you can specify a range of weights:
- *
- * ```js
- * weight: "100 900"
- * ```
- */
- weight?: Weight | undefined;
- /**
- * A [font style](https://developer.mozilla.org/en-US/docs/Web/CSS/font-style).
- */
- style?: Style | undefined;
+export interface FamilyProperties {
/**
* @default `"swap"`
*
@@ -127,47 +76,6 @@ interface FamilyProperties {
unicodeRange?: [string, ...Array] | undefined;
}
-type Src =
- | string
- | URL
- | {
- url: string | URL;
- tech?: string | undefined;
- };
-
-interface Variant extends FamilyProperties {
- /**
- * Font [sources](https://developer.mozilla.org/en-US/docs/Web/CSS/@font-face/src). It can be a path relative to the root, a package import or a URL. URLs are particularly useful if you inject local fonts through an integration.
- */
- src: [Src, ...Array];
-}
-
-export interface LocalFontFamily extends RequiredFamilyAttributes, Fallbacks {
- /**
- * The source of your font files. Set to `"local"` to use local font files.
- */
- provider: typeof LOCAL_PROVIDER_NAME;
- /**
- * Each variant represents a [`@font-face` declaration](https://developer.mozilla.org/en-US/docs/Web/CSS/@font-face/).
- */
- variants: [Variant, ...Array];
-}
-
-interface ResolvedFontFamilyAttributes {
- nameWithHash: string;
-}
-
-export interface ResolvedLocalFontFamily
- extends ResolvedFontFamilyAttributes,
- Omit {
- variants: Array<
- Omit & {
- weight?: string;
- src: Array<{ url: string; tech?: string }>;
- }
- >;
-}
-
type WithOptions = TFontProvider extends FontProvider<
infer TFamilyOptions
>
@@ -198,57 +106,80 @@ type WithOptions = TFontProvider extends Fon
options?: undefined;
};
-export type RemoteFontFamily =
- RequiredFamilyAttributes &
- Omit &
- Fallbacks &
- WithOptions> & {
- /**
- * The source of your font files. You can use a built-in provider or write your own custom provider.
- */
- provider: TFontProvider;
- /**
- * @default `[400]`
- *
- * An array of [font weights](https://developer.mozilla.org/en-US/docs/Web/CSS/font-weight). If the associated font is a [variable font](https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_fonts/Variable_fonts_guide), you can specify a range of weights:
- *
- * ```js
- * weight: "100 900"
- * ```
- */
- weights?: [Weight, ...Array] | undefined;
- /**
- * @default `["normal", "italic"]`
- *
- * An array of [font styles](https://developer.mozilla.org/en-US/docs/Web/CSS/font-style).
- */
- styles?: [Style, ...Array