Skip to content

Commit 47ae148

Browse files
feat(fonts)!: family options (#15175)
Co-authored-by: Sarah Rainsberger <5098874+sarah11918@users.noreply.github.com>
1 parent c0595b3 commit 47ae148

File tree

16 files changed

+404
-80
lines changed

16 files changed

+404
-80
lines changed

.changeset/afraid-moose-agree.md

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
---
2+
'astro': patch
3+
---
4+
5+
Allows experimental Font providers to specify family options
6+
7+
Previously, an Astro `FontProvider` could only accept options at the provider level when called. That could result in weird data structures for family-specific options.
8+
9+
Astro `FontProvider`s can now declare family-specific options, by specifying a generic:
10+
11+
```diff
12+
// font-provider.ts
13+
import type { FontProvider } from "astro";
14+
import { retrieveFonts, type Fonts } from "./utils.js",
15+
16+
interface Config {
17+
token: string;
18+
}
19+
20+
+interface FamilyOptions {
21+
+ minimal?: boolean;
22+
+}
23+
24+
-export function registryFontProvider(config: Config): FontProvider {
25+
+export function registryFontProvider(config: Config): FontProvider<FamilyOptions> {
26+
let data: Fonts = {}
27+
28+
return {
29+
name: "registry",
30+
config,
31+
init: async () => {
32+
data = await retrieveFonts(token);
33+
},
34+
listFonts: () => {
35+
return Object.keys(data);
36+
},
37+
- resolveFont: ({ familyName, ...rest }) => {
38+
+ // options is typed as FamilyOptions
39+
+ resolveFont: ({ familyName, options, ...rest }) => {
40+
const fonts = data[familyName];
41+
if (fonts) {
42+
return { fonts };
43+
}
44+
return undefined;
45+
},
46+
};
47+
}
48+
```
49+
50+
Once the font provider is registered in the Astro config, types are automatically inferred:
51+
52+
```diff
53+
// astro.config.ts
54+
import { defineConfig } from "astro/config";
55+
import { registryFontProvider } from "./font-provider";
56+
57+
export default defineConfig({
58+
experimental: {
59+
fonts: [{
60+
provider: registryFontProvider({
61+
token: "..."
62+
}),
63+
name: "Custom",
64+
cssVariable: "--font-custom",
65+
+ options: {
66+
+ minimal: true
67+
+ }
68+
}]
69+
}
70+
});
71+
```

.changeset/big-carpets-brake.md

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
---
2+
'astro': patch
3+
---
4+
5+
**BREAKING CHANGE to the experimental Fonts API only**
6+
7+
Updates how options are passed to the Google and Google Icons font providers when using the experimental Fonts API
8+
9+
Previously, the Google and Google Icons font providers accepted options that were specific to given font families.
10+
11+
These options must now be set using the `options` property instead. For example using the Google provider:
12+
13+
```diff
14+
import { defineConfig, fontProviders } from "astro/config";
15+
16+
export default defineConfig({
17+
experimental: {
18+
fonts: [{
19+
name: 'Inter',
20+
cssVariable: '--astro-font-inter',
21+
weights: ['300 900'],
22+
- provider: fontProviders.google({
23+
- experimental: {
24+
- variableAxis: {
25+
- Inter: { opsz: ['14..32'] }
26+
- }
27+
- }
28+
- }),
29+
+ provider: fontProviders.google(),
30+
+ options: {
31+
+ experimental: {
32+
+ variableAxis: { opsz: ['14..32'] }
33+
+ }
34+
+ }
35+
}]
36+
}
37+
})
38+
```

packages/astro/src/assets/fonts/config.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,7 @@ export const remoteFontFamilySchema = z
7373
style: true,
7474
}).shape,
7575
provider: fontProviderSchema,
76+
options: z.record(z.string(), z.any()).optional(),
7677
weights: z.array(weightSchema).nonempty().optional(),
7778
styles: z.array(styleSchema).nonempty().optional(),
7879
subsets: z.array(z.string()).nonempty().optional(),

packages/astro/src/assets/fonts/definitions.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -114,7 +114,7 @@ export interface Storage {
114114

115115
export interface FontResolver {
116116
resolveFont: (
117-
options: ResolveFontOptions & { provider: FontProvider },
117+
options: ResolveFontOptions<Record<string, any>> & { provider: FontProvider },
118118
) => Promise<Array<unifont.FontFaceData>>;
119119
listFonts: (options: { provider: FontProvider }) => Promise<string[] | undefined>;
120120
}

packages/astro/src/assets/fonts/infra/unifont-font-resolver.ts

Lines changed: 27 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,10 @@ import { LOCAL_PROVIDER_NAME } from '../constants.js';
44
import type { FontResolver, Hasher, Storage } from '../definitions.js';
55
import type { FontProvider, ResolvedFontFamily, ResolveFontOptions } from '../types.js';
66

7-
type NonEmptyProviders = [Provider, ...Array<Provider>];
7+
type NonEmptyProviders = [
8+
Provider<string, Record<string, any>>,
9+
...Array<Provider<string, Record<string, any>>>,
10+
];
811

912
export class UnifontFontResolver implements FontResolver {
1013
readonly #unifont: Unifont<NonEmptyProviders>;
@@ -30,8 +33,8 @@ export class UnifontFontResolver implements FontResolver {
3033
return defineFontProvider(astroProvider.name, async (_options: any, ctx) => {
3134
await astroProvider?.init?.(ctx);
3235
return {
33-
async resolveFont(familyName, options) {
34-
return await astroProvider.resolveFont({ familyName, ...options });
36+
async resolveFont(familyName, { options, ...rest }) {
37+
return await astroProvider.resolveFont({ familyName, options, ...rest });
3538
},
3639
async listFonts() {
3740
return astroProvider.listFonts?.();
@@ -93,14 +96,28 @@ export class UnifontFontResolver implements FontResolver {
9396
async resolveFont({
9497
familyName,
9598
provider,
99+
options,
96100
...rest
97-
}: ResolveFontOptions & { provider: FontProvider }): Promise<Array<FontFaceData>> {
98-
const { fonts } = await this.#unifont.resolveFont(familyName, rest, [
99-
UnifontFontResolver.idFromProvider({
100-
hasher: this.#hasher,
101-
provider,
102-
}),
103-
]);
101+
}: ResolveFontOptions<Record<string, any>> & { provider: FontProvider }): Promise<
102+
Array<FontFaceData>
103+
> {
104+
const { fonts } = await this.#unifont.resolveFont(
105+
familyName,
106+
{
107+
// Options are currently namespaced by provider name, it may change in
108+
// https://github.com/unjs/unifont/pull/287
109+
options: {
110+
[provider.name]: options,
111+
},
112+
...rest,
113+
},
114+
[
115+
UnifontFontResolver.idFromProvider({
116+
hasher: this.#hasher,
117+
provider,
118+
}),
119+
],
120+
);
104121
return fonts;
105122
}
106123

packages/astro/src/assets/fonts/orchestrate.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -202,6 +202,7 @@ export async function orchestrate({
202202
styles: family.styles ?? defaults.styles,
203203
subsets: family.subsets ?? defaults.subsets,
204204
formats: family.formats ?? defaults.formats,
205+
options: family.options,
205206
});
206207
if (fonts.length === 0) {
207208
logger.warn(

packages/astro/src/assets/fonts/providers/index.ts

Lines changed: 8 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import {
22
type AdobeProviderOptions,
3-
type GoogleiconsOptions,
4-
type GoogleOptions,
3+
type GoogleFamilyOptions,
4+
type GoogleiconsFamilyOptions,
55
type InitializedProvider,
66
providers,
77
} from 'unifont';
@@ -81,12 +81,11 @@ function fontsource(): FontProvider {
8181
}
8282

8383
/** [Google](https://fonts.google.com/) */
84-
function google(config?: GoogleOptions): FontProvider {
85-
const provider = providers.google(config);
86-
let initializedProvider: InitializedProvider | undefined;
84+
function google(): FontProvider<GoogleFamilyOptions | undefined> {
85+
const provider = providers.google();
86+
let initializedProvider: InitializedProvider<GoogleFamilyOptions> | undefined;
8787
return {
8888
name: provider._name,
89-
config,
9089
async init(context) {
9190
initializedProvider = await provider(context);
9291
},
@@ -100,12 +99,11 @@ function google(config?: GoogleOptions): FontProvider {
10099
}
101100

102101
/** [Google Icons](https://fonts.google.com/icons) */
103-
function googleicons(config?: GoogleiconsOptions): FontProvider {
104-
const provider = providers.googleicons(config);
105-
let initializedProvider: InitializedProvider | undefined;
102+
function googleicons(): FontProvider<GoogleiconsFamilyOptions | undefined> {
103+
const provider = providers.googleicons();
104+
let initializedProvider: InitializedProvider<GoogleiconsFamilyOptions> | undefined;
106105
return {
107106
name: provider._name,
108-
config,
109107
async init(context) {
110108
initializedProvider = await provider(context);
111109
},

packages/astro/src/assets/fonts/types.ts

Lines changed: 79 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,9 @@ export interface FontProviderInitContext {
2121

2222
type Awaitable<T> = T | Promise<T>;
2323

24-
export interface FontProvider {
24+
export interface FontProvider<
25+
TFamilyOptions extends Record<string, any> | undefined | never = never,
26+
> {
2527
/**
2628
* The font provider name, used for display and deduplication.
2729
*/
@@ -37,7 +39,7 @@ export interface FontProvider {
3739
/**
3840
* Required callback, used to retrieve and return font face data based on the given options.
3941
*/
40-
resolveFont: (options: ResolveFontOptions) => Awaitable<
42+
resolveFont: (options: ResolveFontOptions<TFamilyOptions>) => Awaitable<
4143
| {
4244
fonts: Array<unifont.FontFaceData>;
4345
}
@@ -166,43 +168,74 @@ export interface ResolvedLocalFontFamily
166168
>;
167169
}
168170

169-
export interface RemoteFontFamily
170-
extends RequiredFamilyAttributes,
171-
Omit<FamilyProperties, 'weight' | 'style' | 'subsets' | 'formats'>,
172-
Fallbacks {
173-
/**
174-
* The source of your font files. You can use a built-in provider or write your own custom provider.
175-
*/
176-
provider: FontProvider;
177-
/**
178-
* @default `[400]`
179-
*
180-
* 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:
181-
*
182-
* ```js
183-
* weight: "100 900"
184-
* ```
185-
*/
186-
weights?: [Weight, ...Array<Weight>] | undefined;
187-
/**
188-
* @default `["normal", "italic"]`
189-
*
190-
* An array of [font styles](https://developer.mozilla.org/en-US/docs/Web/CSS/font-style).
191-
*/
192-
styles?: [Style, ...Array<Style>] | undefined;
193-
/**
194-
* @default `["latin"]`
195-
*
196-
* An array of [font subsets](https://knaap.dev/posts/font-subsetting/):
197-
*/
198-
subsets?: [string, ...Array<string>] | undefined;
199-
/**
200-
* @default `["woff2"]`
201-
*
202-
* An array of [font formats](https://developer.mozilla.org/en-US/docs/Web/CSS/Reference/At-rules/@font-face/src#font_formats).
203-
*/
204-
formats?: [FontType, ...Array<FontType>] | undefined;
205-
}
171+
type WithOptions<TFontProvider extends FontProvider> = TFontProvider extends FontProvider<
172+
infer TFamilyOptions
173+
>
174+
? [TFamilyOptions] extends [never]
175+
? {
176+
/**
177+
* Options forwarded to the font provider while resolving this font family.
178+
*/
179+
options?: undefined;
180+
}
181+
: undefined extends TFamilyOptions
182+
? {
183+
/**
184+
* Options forwarded to the font provider while resolving this font family.
185+
*/
186+
options?: TFamilyOptions;
187+
}
188+
: {
189+
/**
190+
* Options forwarded to the font provider while resolving this font family.
191+
*/
192+
options: TFamilyOptions;
193+
}
194+
: {
195+
/**
196+
* Options forwarded to the font provider while resolving this font family.
197+
*/
198+
options?: undefined;
199+
};
200+
201+
export type RemoteFontFamily<TFontProvider extends FontProvider = FontProvider> =
202+
RequiredFamilyAttributes &
203+
Omit<FamilyProperties, 'weight' | 'style' | 'subsets' | 'formats'> &
204+
Fallbacks &
205+
WithOptions<NoInfer<TFontProvider>> & {
206+
/**
207+
* The source of your font files. You can use a built-in provider or write your own custom provider.
208+
*/
209+
provider: TFontProvider;
210+
/**
211+
* @default `[400]`
212+
*
213+
* 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:
214+
*
215+
* ```js
216+
* weight: "100 900"
217+
* ```
218+
*/
219+
weights?: [Weight, ...Array<Weight>] | undefined;
220+
/**
221+
* @default `["normal", "italic"]`
222+
*
223+
* An array of [font styles](https://developer.mozilla.org/en-US/docs/Web/CSS/font-style).
224+
*/
225+
styles?: [Style, ...Array<Style>] | undefined;
226+
/**
227+
* @default `["latin"]`
228+
*
229+
* An array of [font subsets](https://knaap.dev/posts/font-subsetting/):
230+
*/
231+
subsets?: [string, ...Array<string>] | undefined;
232+
/**
233+
* @default `["woff2"]`
234+
*
235+
* An array of [font formats](https://developer.mozilla.org/en-US/docs/Web/CSS/Reference/At-rules/@font-face/src#font_formats).
236+
*/
237+
formats?: [FontType, ...Array<FontType>] | undefined;
238+
};
206239

207240
/** @lintignore somehow required by pickFontFaceProperty in utils */
208241
export interface ResolvedRemoteFontFamily
@@ -211,7 +244,9 @@ export interface ResolvedRemoteFontFamily
211244
weights?: Array<string>;
212245
}
213246

214-
export type FontFamily = LocalFontFamily | RemoteFontFamily;
247+
export type FontFamily<TFontProvider extends FontProvider = FontProvider> =
248+
| LocalFontFamily
249+
| RemoteFontFamily<TFontProvider>;
215250
export type ResolvedFontFamily = ResolvedLocalFontFamily | ResolvedRemoteFontFamily;
216251

217252
export type FontType = (typeof FONT_TYPES)[number];
@@ -290,10 +325,13 @@ export type PreloadFilter =
290325
| boolean
291326
| Array<{ weight?: string | number; style?: string; subset?: string }>;
292327

293-
export interface ResolveFontOptions {
328+
export interface ResolveFontOptions<
329+
FamilyOptions extends Record<string, any> | undefined | never = never,
330+
> {
294331
familyName: string;
295332
weights: string[];
296333
styles: Style[];
297334
subsets: string[];
298335
formats: FontType[];
336+
options: [FamilyOptions] extends [never] ? undefined : FamilyOptions | undefined;
299337
}

0 commit comments

Comments
 (0)