Skip to content
Merged
Show file tree
Hide file tree
Changes from 7 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
5 changes: 5 additions & 0 deletions .changeset/soft-memes-sin.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'astro': patch
---

Fixes the experimental fonts API to correctly take `config.base`, `config.build.assets` and `config.build.assetsPrefix` into account
3 changes: 1 addition & 2 deletions packages/astro/src/assets/fonts/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,7 @@ export const DEFAULTS: Defaults = {
export const VIRTUAL_MODULE_ID = 'virtual:astro:assets/fonts/internal';
export const RESOLVED_VIRTUAL_MODULE_ID = '\0' + VIRTUAL_MODULE_ID;

// Requires a trailing slash
export const URL_PREFIX = '/_astro/fonts/';
export const ASSETS_DIR = 'fonts';
export const CACHE_DIR = './fonts/';

export const FONT_TYPES = ['woff2', 'woff', 'otf', 'ttf', 'eot'] as const;
Expand Down
4 changes: 4 additions & 0 deletions packages/astro/src/assets/fonts/definitions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,10 @@ export interface UrlProxy {
) => string;
}

export interface UrlResolver {
resolve: (hash: string) => string;
}

export interface UrlProxyContentResolver {
resolve: (url: string) => string;
}
Expand Down
14 changes: 10 additions & 4 deletions packages/astro/src/assets/fonts/implementations/url-proxy.ts
Original file line number Diff line number Diff line change
@@ -1,20 +1,26 @@
import type { DataCollector, Hasher, UrlProxy, UrlProxyContentResolver } from '../definitions.js';
import type {
DataCollector,
Hasher,
UrlProxy,
UrlProxyContentResolver,
UrlResolver,
} from '../definitions.js';

export function createUrlProxy({
base,
contentResolver,
hasher,
dataCollector,
urlResolver,
}: {
base: string;
contentResolver: UrlProxyContentResolver;
hasher: Hasher;
dataCollector: DataCollector;
urlResolver: UrlResolver;
}): UrlProxy {
return {
proxy({ url: originalUrl, type, data, collectPreload, init }) {
const hash = `${hasher.hashString(contentResolver.resolve(originalUrl))}.${type}`;
const url = base + hash;
const url = urlResolver.resolve(hash);

dataCollector.collect({
url: originalUrl,
Expand Down
27 changes: 27 additions & 0 deletions packages/astro/src/assets/fonts/implementations/url-resolver.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import type { UrlResolver } from '../definitions.js';
import { fileExtension, joinPaths, prependForwardSlash } from '../../../core/path.js';
import type { AssetsPrefix } from '../../../types/public/index.js';
import { getAssetsPrefix } from '../../utils/getAssetsPrefix.js';

export function createDevUrlResolver({ base }: { base: string }): UrlResolver {
return {
resolve(hash) {
return prependForwardSlash(joinPaths(base, hash));
},
};
}

export function createBuildUrlResolver({
base,
assetsPrefix,
}: { base: string; assetsPrefix: AssetsPrefix }): UrlResolver {
return {
resolve(hash) {
const prefix = assetsPrefix ? getAssetsPrefix(fileExtension(hash), assetsPrefix) : undefined;
if (prefix) {
return joinPaths(prefix, base, hash);
}
return prependForwardSlash(joinPaths(base, hash));
},
};
}
26 changes: 18 additions & 8 deletions packages/astro/src/assets/fonts/vite-plugin-fonts.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ import { mkdirSync, writeFileSync } from 'node:fs';
import { readFile } from 'node:fs/promises';
import { isAbsolute } from 'node:path';
import { fileURLToPath } from 'node:url';
import { removeTrailingForwardSlash } from '@astrojs/internal-helpers/path';
import type { Plugin } from 'vite';
import { collectErrorMetadata } from '../../core/errors/dev/utils.js';
import { AstroError, AstroErrorData, isAstroError } from '../../core/errors/index.js';
Expand All @@ -11,17 +10,18 @@ import { formatErrorMessage } from '../../core/messages.js';
import { getClientOutputDirectory } from '../../prerender/utils.js';
import type { AstroSettings } from '../../types/astro.js';
import {
ASSETS_DIR,
CACHE_DIR,
DEFAULTS,
RESOLVED_VIRTUAL_MODULE_ID,
URL_PREFIX,
VIRTUAL_MODULE_ID,
} from './constants.js';
import type {
CssRenderer,
FontFetcher,
FontTypeExtractor,
RemoteFontProviderModResolver,
UrlResolver,
} from './definitions.js';
import { createMinifiableCssRenderer } from './implementations/css-renderer.js';
import { createDataCollector } from './implementations/data-collector.js';
Expand All @@ -46,6 +46,8 @@ import {
import { createUrlProxy } from './implementations/url-proxy.js';
import { orchestrate } from './orchestrate.js';
import type { ConsumableMap, FontFileDataMap } from './types.js';
import { appendForwardSlash, joinPaths, prependForwardSlash } from '../../core/path.js';
import { createBuildUrlResolver, createDevUrlResolver } from './implementations/url-resolver.js';

interface Options {
settings: AstroSettings;
Expand Down Expand Up @@ -78,7 +80,10 @@ export function fontsPlugin({ settings, sync, logger }: Options): Plugin {
// We don't need to take the trailing slash and build output configuration options
// into account because we only serve (dev) or write (build) static assets (equivalent
// to trailingSlash: never)
const baseUrl = removeTrailingForwardSlash(settings.config.base) + URL_PREFIX;
const assetsDir = prependForwardSlash(
appendForwardSlash(joinPaths(settings.config.build.assets, ASSETS_DIR)),
);
const baseUrl = joinPaths(settings.config.base, assetsDir);
Comment on lines +82 to +85
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why don't we remove the trailing slash anymore? Also, the comment must be updated. Line 82 still talks about trailing slash

Copy link
Member Author

@florian-lefebvre florian-lefebvre May 12, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've updated the comment thanks! We don't need to care about the trailing slash here anymore because this is handled by each UrlResolver (which use joinPaths() under the hood)


let fontFileDataMap: FontFileDataMap | null = null;
let consumableMap: ConsumableMap | null = null;
Expand All @@ -96,10 +101,12 @@ export function fontsPlugin({ settings, sync, logger }: Options): Plugin {
cacheDir,
modResolver,
cssRenderer,
urlResolver,
}: {
cacheDir: URL;
modResolver: RemoteFontProviderModResolver;
cssRenderer: CssRenderer;
urlResolver: UrlResolver;
}) {
const { root } = settings.config;
// Dependencies. Once extracted to a dedicated vite plugin, those may be passed as
Expand Down Expand Up @@ -153,7 +160,7 @@ export function fontsPlugin({ settings, sync, logger }: Options): Plugin {
? createLocalUrlProxyContentResolver({ errorHandler })
: createRemoteUrlProxyContentResolver();
return createUrlProxy({
base: baseUrl,
urlResolver,
contentResolver,
hasher,
dataCollector,
Expand All @@ -178,6 +185,10 @@ export function fontsPlugin({ settings, sync, logger }: Options): Plugin {
cacheDir: new URL(CACHE_DIR, settings.config.cacheDir),
modResolver: createBuildRemoteFontProviderModResolver(),
cssRenderer: createMinifiableCssRenderer({ minify: true }),
urlResolver: createBuildUrlResolver({
base: baseUrl,
assetsPrefix: settings.config.build.assetsPrefix,
}),
});
}
},
Expand All @@ -187,6 +198,7 @@ export function fontsPlugin({ settings, sync, logger }: Options): Plugin {
cacheDir: new URL(CACHE_DIR, settings.dotAstroDir),
modResolver: createDevServerRemoteFontProviderModResolver({ server }),
cssRenderer: createMinifiableCssRenderer({ minify: false }),
urlResolver: createDevUrlResolver({ base: baseUrl }),
});
// The map is always defined at this point. Its values contains urls from remote providers
// as well as local paths for the local provider. We filter them to only keep the filepaths
Expand All @@ -209,9 +221,7 @@ export function fontsPlugin({ settings, sync, logger }: Options): Plugin {
}
});

// Base is taken into account by default. The prefix contains a traling slash,
// so it matches correctly any hash, eg. /_astro/fonts/abc.woff => abc.woff
server.middlewares.use(URL_PREFIX, async (req, res, next) => {
server.middlewares.use(assetsDir, async (req, res, next) => {
if (!req.url) {
return next();
}
Expand Down Expand Up @@ -269,7 +279,7 @@ export function fontsPlugin({ settings, sync, logger }: Options): Plugin {

try {
const dir = getClientOutputDirectory(settings);
const fontsDir = new URL('.' + baseUrl, dir);
const fontsDir = new URL(`.${assetsDir}`, dir);
try {
mkdirSync(fontsDir, { recursive: true });
} catch (cause) {
Expand Down
Loading
Loading