Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
54 changes: 54 additions & 0 deletions .changeset/forty-zebras-enter.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
---
'astro': patch
---

**BREAKING CHANGE to the experimental Fonts API only**

Changes how font providers are implemented with updates to the `FontProvider` type

This is an implementation detail that changes how font providers are created. This process allows Astro to take more control rather than relying directly on `unifont` types. **All of Astro's built-in font providers have been updated to reflect this new type, and can be configured as before**. However, using third-party unifont providers that rely on `unifont` types will require an update to your project code.

Previously, an Astro `FontProvider` was made of a config and a runtime part. It relied directly on `unifont` types, which allowed a simple configuration for third-party unifont providers, but also coupled Astro's implementation to unifont, which was limiting.

Astro's font provider implementation is now only made of a config part with dedicated hooks. This allows for the separation of config and runtime, but requires you to create a font provider object in order to use custom font providers (e.g. third-party unifont providers, or private font registeries).

#### What should I do?

If you were using a 3rd-party `unifont` font provider, you will now need to write an Astro `FontProvider` using it under the hood. For example:

```diff
// astro.config.ts
import { defineConfig } from "astro/config";
import { acmeProvider, type AcmeOptions } from '@acme/unifont-provider'
+import type { FontProvider } from "astro";
+import type { InitializedProvider } from 'unifont';

+function acme(config?: AcmeOptions): FontProvider {
+ const provider = acmeProvider(config);
+ let initializedProvider: InitializedProvider | undefined;
+ return {
+ name: provider._name,
+ config,
+ async init(context) {
+ initializedProvider = await provider(context);
+ },
+ async resolveFont({ familyName, ...rest }) {
+ return await initializedProvider?.resolveFont(familyName, rest);
+ },
+ async listFonts() {
+ return await initializedProvider?.listFonts?.();
+ },
+ };
+}

export default defineConfig({
experimental: {
fonts: [{
- provider: acmeProvider({ /* ... */ }),
+ provider: acme({ /* ... */ }),
name: "Material Symbols Outlined",
cssVariable: "--font-material"
}]
}
});
```
1 change: 0 additions & 1 deletion packages/astro/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,6 @@
"./assets/endpoint/*": "./dist/assets/endpoint/*.js",
"./assets/services/sharp": "./dist/assets/services/sharp.js",
"./assets/services/noop": "./dist/assets/services/noop.js",
"./assets/fonts/providers/*": "./dist/assets/fonts/providers/entrypoints/*.js",
"./assets/fonts/runtime": "./dist/assets/fonts/runtime.js",
"./loaders": "./dist/content/loaders/index.js",
"./content/config": "./dist/content/config.js",
Expand Down
3 changes: 1 addition & 2 deletions packages/astro/src/assets/fonts/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,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 prepares [`unifont`](https://github.com/unjs/unifont) providers
- It initializes `unifont`
- 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
- It returns the data
Expand Down
18 changes: 12 additions & 6 deletions packages/astro/src/assets/fonts/config.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { z } from 'zod';
import { FONT_TYPES, LOCAL_PROVIDER_NAME } from './constants.js';
import type { FontProvider } from './types.js';

export const weightSchema = z.union([z.string(), z.number()]);
export const styleSchema = z.enum(['normal', 'italic', 'oblique']);
Expand Down Expand Up @@ -53,6 +54,16 @@ export const localFontFamilySchema = z
})
.strict();

export const fontProviderSchema = z
.object({
name: z.string(),
config: z.record(z.string(), z.any()).optional(),
init: z.custom<FontProvider['init']>((v) => typeof v === 'function').optional(),
resolveFont: z.custom<FontProvider['resolveFont']>((v) => typeof v === 'function'),
listFonts: z.custom<FontProvider['listFonts']>((v) => typeof v === 'function').optional(),
})
.strict();

export const remoteFontFamilySchema = z
.object({
...requiredFamilyAttributesSchema.shape,
Expand All @@ -61,12 +72,7 @@ export const remoteFontFamilySchema = z
weight: true,
style: true,
}).shape,
provider: z
.object({
entrypoint: entrypointSchema,
config: z.record(z.string(), z.any()).optional(),
})
.strict(),
provider: fontProviderSchema,
weights: z.array(weightSchema).nonempty().optional(),
styles: z.array(styleSchema).nonempty().optional(),
subsets: z.array(z.string()).nonempty().optional(),
Expand Down
35 changes: 13 additions & 22 deletions packages/astro/src/assets/fonts/core/resolve-families.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,5 @@
import { LOCAL_PROVIDER_NAME } from '../constants.js';
import type {
Hasher,
LocalProviderUrlResolver,
RemoteFontProviderResolver,
} from '../definitions.js';
import type { Hasher, LocalProviderUrlResolver } from '../definitions.js';
import type {
FontFamily,
LocalFontFamily,
Expand Down Expand Up @@ -38,17 +34,15 @@ function resolveVariants({
/**
* Dedupes properties if applicable and resolves entrypoints.
*/
export async function resolveFamily({
export function resolveFamily({
family,
hasher,
remoteFontProviderResolver,
localProviderUrlResolver,
}: {
family: FontFamily;
hasher: Hasher;
remoteFontProviderResolver: RemoteFontProviderResolver;
localProviderUrlResolver: LocalProviderUrlResolver;
}): Promise<ResolvedFontFamily> {
}): 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
Expand All @@ -75,26 +69,23 @@ export async function resolveFamily({
formats: family.formats ? dedupe(family.formats) : undefined,
fallbacks: family.fallbacks ? dedupe(family.fallbacks) : undefined,
unicodeRange: family.unicodeRange ? dedupe(family.unicodeRange) : undefined,
// This will be Astro specific eventually
provider: await remoteFontProviderResolver.resolve(family.provider),
};
}

/**
* A function for convenience. The actual logic lives in resolveFamily
*/
export async function resolveFamilies({
export function resolveFamilies({
families,
...dependencies
}: { families: Array<FontFamily> } & Omit<Parameters<typeof resolveFamily>[0], 'family'>): Promise<
Array<ResolvedFontFamily>
> {
return await Promise.all(
families.map((family) =>
resolveFamily({
family,
...dependencies,
}),
),
}: { families: Array<FontFamily> } & Omit<
Parameters<typeof resolveFamily>[0],
'family'
>): Array<ResolvedFontFamily> {
return families.map((family) =>
resolveFamily({
family,
...dependencies,
}),
);
}
10 changes: 0 additions & 10 deletions packages/astro/src/assets/fonts/definitions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,9 @@ import type { CollectedFontForMetrics } from './core/optimize-fallbacks.js';
import type {
FontFaceMetrics,
FontFileData,
FontProvider,
FontType,
GenericFallbackName,
PreloadData,
ResolvedFontProvider,
ResolveFontOptions,
Style,
} from './types.js';
Expand All @@ -17,14 +15,6 @@ export interface Hasher {
hashObject: (input: Record<string, any>) => string;
}

export interface RemoteFontProviderModResolver {
resolve: (id: string) => Promise<any>;
}

export interface RemoteFontProviderResolver {
resolve: (provider: FontProvider) => Promise<ResolvedFontProvider>;
}

export interface LocalProviderUrlResolver {
resolve: (input: string) => string;
}
Expand Down

This file was deleted.

This file was deleted.

This file was deleted.

Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { fileURLToPath } from 'node:url';
import { createRequire } from 'node:module';
import { fileURLToPath, pathToFileURL } from 'node:url';
import type { LocalProviderUrlResolver } from '../definitions.js';
import { resolveEntrypoint } from '../utils.js';

export class RequireLocalProviderUrlResolver implements LocalProviderUrlResolver {
readonly #root: URL;
Expand All @@ -18,10 +18,20 @@ export class RequireLocalProviderUrlResolver implements LocalProviderUrlResolver
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(resolveEntrypoint(this.#root, input));
const path = fileURLToPath(this.#resolveEntrypoint(this.#root, input));
this.#intercept?.(path);
return path;
}
Expand Down
20 changes: 17 additions & 3 deletions packages/astro/src/assets/fonts/infra/unifont-font-resolver.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import type { FontFaceData, Provider } from 'unifont';
import { createUnifont, type Unifont } 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 { ResolvedFontFamily, ResolveFontOptions } from '../types.js';
import type { FontProvider, ResolvedFontFamily, ResolveFontOptions } from '../types.js';

type NonEmptyProviders = [Provider, ...Array<Provider>];

Expand All @@ -13,6 +13,20 @@ export class UnifontFontResolver implements FontResolver {
this.#unifont = unifont;
}

static astroToUnifontProvider(astroProvider: FontProvider): Provider {
return defineFontProvider(astroProvider.name, async (_options: any, ctx) => {
await astroProvider?.init?.(ctx);
return {
async resolveFont(familyName, options) {
return await astroProvider.resolveFont({ familyName, ...options });
},
async listFonts() {
return astroProvider.listFonts?.();
},
};
})(astroProvider.config);
}

static extractUnifontProviders({
families,
hasher,
Expand All @@ -29,7 +43,7 @@ export class UnifontFontResolver implements FontResolver {
continue;
}

const unifontProvider = provider.provider(provider.config);
const unifontProvider = this.astroToUnifontProvider(provider);
const hash = hasher.hashObject({
name: unifontProvider._name,
...provider.config,
Expand Down
Loading