full site update

This commit is contained in:
2025-07-24 18:46:24 +02:00
parent bfe2b90d8d
commit 37a6e0ab31
6912 changed files with 540482 additions and 361712 deletions

View File

@@ -1,8 +1,7 @@
import type PQueue from 'p-queue';
import type { AstroConfig } from '../../@types/astro.js';
import type { BuildPipeline } from '../../core/build/pipeline.js';
import type { Logger } from '../../core/logger/core.js';
import type { MapValue } from '../../type-utils.js';
import type { AstroConfig } from '../../types/public/config.js';
import type { AssetsGlobalStaticImagesList } from '../types.js';
type AssetEnv = {
logger: Logger;
@@ -19,6 +18,6 @@ type AssetEnv = {
assetsFolder: AstroConfig['build']['assets'];
};
export declare function prepareAssetsGenerationEnv(pipeline: BuildPipeline, totalCount: number): Promise<AssetEnv>;
export declare function generateImagesForPath(originalFilePath: string, transformsAndPath: MapValue<AssetsGlobalStaticImagesList>, env: AssetEnv, queue: PQueue): Promise<void>;
export declare function generateImagesForPath(originalFilePath: string, transformsAndPath: MapValue<AssetsGlobalStaticImagesList>, env: AssetEnv): Promise<void>;
export declare function getStaticImageList(): AssetsGlobalStaticImagesList;
export {};

View File

@@ -6,12 +6,11 @@ import { getTimeStat } from "../../core/build/util.js";
import { AstroError } from "../../core/errors/errors.js";
import { AstroErrorData } from "../../core/errors/index.js";
import { isRemotePath, removeLeadingForwardSlash } from "../../core/path.js";
import { isServerLikeOutput } from "../../core/util.js";
import { getConfiguredImageService } from "../internal.js";
import { isESMImportedImage } from "../utils/imageKind.js";
import { loadRemoteImage } from "./remote.js";
import { loadRemoteImage, revalidateRemoteImage } from "./remote.js";
async function prepareAssetsGenerationEnv(pipeline, totalCount) {
const { config, logger } = pipeline;
const { config, logger, settings } = pipeline;
let useCache = true;
const assetsCacheDir = new URL("assets/", config.cacheDir);
const count = { total: totalCount, current: 1 };
@@ -24,8 +23,9 @@ async function prepareAssetsGenerationEnv(pipeline, totalCount) {
);
useCache = false;
}
const isServerOutput = settings.buildOutput === "server";
let serverRoot, clientRoot;
if (isServerLikeOutput(config)) {
if (isServerOutput) {
serverRoot = config.build.server;
clientRoot = config.build.client;
} else {
@@ -34,7 +34,7 @@ async function prepareAssetsGenerationEnv(pipeline, totalCount) {
}
return {
logger,
isSSR: isServerLikeOutput(config),
isSSR: isServerOutput,
count,
useCache,
assetsCacheDir,
@@ -47,12 +47,10 @@ async function prepareAssetsGenerationEnv(pipeline, totalCount) {
function getFullImagePath(originalFilePath, env) {
return new URL(removeLeadingForwardSlash(originalFilePath), env.serverRoot);
}
async function generateImagesForPath(originalFilePath, transformsAndPath, env, queue) {
async function generateImagesForPath(originalFilePath, transformsAndPath, env) {
let originalImage;
for (const [_, transform] of transformsAndPath.transforms) {
await queue.add(async () => generateImage(transform.finalPath, transform.transform)).catch((e) => {
throw e;
});
await generateImage(transform.finalPath, transform.transform);
}
if (!env.isSSR && transformsAndPath.originalSrcPath && !globalThis.astroAsset.referencedImages?.has(transformsAndPath.originalSrcPath)) {
try {
@@ -72,7 +70,7 @@ async function generateImagesForPath(originalFilePath, transformsAndPath, env, q
const timeEnd = performance.now();
const timeChange = getTimeStat(timeStart, timeEnd);
const timeIncrease = `(+${timeChange})`;
const statsText = generationData.cached ? `(reused cache entry)` : `(before: ${generationData.weight.before}kB, after: ${generationData.weight.after}kB)`;
const statsText = generationData.cached !== "miss" ? generationData.cached === "hit" ? `(reused cache entry)` : `(revalidated cache entry)` : `(before: ${generationData.weight.before}kB, after: ${generationData.weight.after}kB)`;
const count = `(${env.count.current}/${env.count.total})`;
env.logger.info(
null,
@@ -85,30 +83,69 @@ async function generateImagesForPath(originalFilePath, transformsAndPath, env, q
const finalFileURL = new URL("." + filepath, env.clientRoot);
const finalFolderURL = new URL("./", finalFileURL);
await fs.promises.mkdir(finalFolderURL, { recursive: true });
const cacheFile = basename(filepath) + (isLocalImage ? "" : ".json");
const cacheFile = basename(filepath);
const cachedFileURL = new URL(cacheFile, env.assetsCacheDir);
const cacheMetaFile = cacheFile + ".json";
const cachedMetaFileURL = new URL(cacheMetaFile, env.assetsCacheDir);
try {
if (isLocalImage) {
await fs.promises.copyFile(cachedFileURL, finalFileURL, fs.constants.COPYFILE_FICLONE);
return {
cached: true
cached: "hit"
};
} else {
const JSONData = JSON.parse(readFileSync(cachedFileURL, "utf-8"));
if (!JSONData.data || !JSONData.expires) {
await fs.promises.unlink(cachedFileURL);
const JSONData = JSON.parse(readFileSync(cachedMetaFileURL, "utf-8"));
if (!JSONData.expires) {
try {
await fs.promises.unlink(cachedFileURL);
} catch {
}
await fs.promises.unlink(cachedMetaFileURL);
throw new Error(
`Malformed cache entry for ${filepath}, cache will be regenerated for this file.`
);
}
if (JSONData.expires > Date.now()) {
await fs.promises.writeFile(finalFileURL, Buffer.from(JSONData.data, "base64"));
return {
cached: true
};
} else {
await fs.promises.unlink(cachedFileURL);
if (JSONData.data) {
const { data, ...meta } = JSONData;
await Promise.all([
fs.promises.writeFile(cachedFileURL, Buffer.from(data, "base64")),
writeCacheMetaFile(cachedMetaFileURL, meta, env)
]);
}
if (JSONData.expires > Date.now()) {
await fs.promises.copyFile(cachedFileURL, finalFileURL, fs.constants.COPYFILE_FICLONE);
return {
cached: "hit"
};
}
if (JSONData.etag || JSONData.lastModified) {
try {
const revalidatedData = await revalidateRemoteImage(options.src, {
etag: JSONData.etag,
lastModified: JSONData.lastModified
});
if (revalidatedData.data.length) {
originalImage = revalidatedData;
} else {
await writeCacheMetaFile(cachedMetaFileURL, revalidatedData, env);
await fs.promises.copyFile(
cachedFileURL,
finalFileURL,
fs.constants.COPYFILE_FICLONE
);
return { cached: "revalidated" };
}
} catch (e) {
env.logger.warn(
null,
`An error was encountered while revalidating a cached remote asset. Proceeding with stale cache. ${e}`
);
await fs.promises.copyFile(cachedFileURL, finalFileURL, fs.constants.COPYFILE_FICLONE);
return { cached: "hit" };
}
}
await fs.promises.unlink(cachedFileURL);
await fs.promises.unlink(cachedMetaFileURL);
}
} catch (e) {
if (e.code !== "ENOENT") {
@@ -121,7 +158,9 @@ async function generateImagesForPath(originalFilePath, transformsAndPath, env, q
}
let resultData = {
data: void 0,
expires: originalImage.expires
expires: originalImage.expires,
etag: originalImage.etag,
lastModified: originalImage.lastModified
};
const imageService = await getConfiguredImageService();
try {
@@ -131,6 +170,9 @@ async function generateImagesForPath(originalFilePath, transformsAndPath, env, q
env.imageConfig
)).data;
} catch (e) {
if (AstroError.is(e)) {
throw e;
}
const error = new AstroError(
{
...AstroErrorData.CouldNotTransformImage,
@@ -145,13 +187,10 @@ async function generateImagesForPath(originalFilePath, transformsAndPath, env, q
if (isLocalImage) {
await fs.promises.writeFile(cachedFileURL, resultData.data);
} else {
await fs.promises.writeFile(
cachedFileURL,
JSON.stringify({
data: Buffer.from(resultData.data).toString("base64"),
expires: resultData.expires
})
);
await Promise.all([
fs.promises.writeFile(cachedFileURL, resultData.data),
writeCacheMetaFile(cachedMetaFileURL, resultData, env)
]);
}
}
} catch (e) {
@@ -163,7 +202,7 @@ async function generateImagesForPath(originalFilePath, transformsAndPath, env, q
await fs.promises.writeFile(finalFileURL, resultData.data);
}
return {
cached: false,
cached: "miss",
weight: {
// Divide by 1024 to get size in kilobytes
before: Math.trunc(originalImage.data.byteLength / 1024),
@@ -172,6 +211,23 @@ async function generateImagesForPath(originalFilePath, transformsAndPath, env, q
};
}
}
async function writeCacheMetaFile(cachedMetaFileURL, resultData, env) {
try {
return await fs.promises.writeFile(
cachedMetaFileURL,
JSON.stringify({
expires: resultData.expires,
etag: resultData.etag,
lastModified: resultData.lastModified
})
);
} catch (e) {
env.logger.warn(
null,
`An error was encountered while writing the cache file for a remote asset. Proceeding without caching this asset. Error: ${e}`
);
}
}
function getStaticImageList() {
if (!globalThis?.astroAsset?.staticImages) {
return /* @__PURE__ */ new Map();
@@ -180,11 +236,7 @@ function getStaticImageList() {
}
async function loadImage(path, env) {
if (isRemotePath(path)) {
const remoteImage = await loadRemoteImage(path);
return {
data: remoteImage.data,
expires: remoteImage.expires
};
return await loadRemoteImage(path);
}
return {
data: await fs.promises.readFile(getFullImagePath(path, env)),

View File

@@ -1,8 +1,30 @@
export type RemoteCacheEntry = {
data: string;
data?: string;
expires: number;
etag?: string;
lastModified?: string;
};
export declare function loadRemoteImage(src: string): Promise<{
data: Buffer;
expires: number;
etag: string | undefined;
lastModified: string | undefined;
}>;
/**
* Revalidate a cached remote asset using its entity-tag or modified date.
* Uses the [If-None-Match](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/If-None-Match) and [If-Modified-Since](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/If-Modified-Since)
* headers to check with the remote server if the cached version of a remote asset is still up to date.
* The remote server may respond that the cached asset is still up-to-date if the entity-tag or modification time matches (304 Not Modified), or respond with an updated asset (200 OK)
* @param src - url to remote asset
* @param revalidationData - an object containing the stored Entity-Tag of the cached asset and/or the Last Modified time
* @returns An ImageData object containing the asset data, a new expiry time, and the asset's etag. The data buffer will be empty if the asset was not modified.
*/
export declare function revalidateRemoteImage(src: string, revalidationData: {
etag?: string;
lastModified?: string;
}): Promise<{
data: Buffer;
expires: number;
etag: string | undefined;
lastModified: string | undefined;
}>;

View File

@@ -11,7 +11,41 @@ async function loadRemoteImage(src) {
const expires = policy.storable() ? policy.timeToLive() : 0;
return {
data: Buffer.from(await res.arrayBuffer()),
expires: Date.now() + expires
expires: Date.now() + expires,
etag: res.headers.get("Etag") ?? void 0,
lastModified: res.headers.get("Last-Modified") ?? void 0
};
}
async function revalidateRemoteImage(src, revalidationData) {
const headers = {
...revalidationData.etag && { "If-None-Match": revalidationData.etag },
...revalidationData.lastModified && { "If-Modified-Since": revalidationData.lastModified }
};
const req = new Request(src, { headers });
const res = await fetch(req);
if (!res.ok && res.status !== 304) {
throw new Error(
`Failed to revalidate cached remote image ${src}. The request did not return a 200 OK / 304 NOT MODIFIED response. (received ${res.status} ${res.statusText})`
);
}
const data = Buffer.from(await res.arrayBuffer());
if (res.ok && !data.length) {
return await loadRemoteImage(src);
}
const policy = new CachePolicy(
webToCachePolicyRequest(req),
webToCachePolicyResponse(
res.ok ? res : new Response(null, { status: 200, headers: res.headers })
)
// 304 responses themselves are not cacheable, so just pretend to get the refreshed TTL
);
const expires = policy.storable() ? policy.timeToLive() : 0;
return {
data,
expires: Date.now() + expires,
// While servers should respond with the same headers as a 200 response, if they don't we should reuse the stored value
etag: res.headers.get("Etag") ?? (res.ok ? void 0 : revalidationData.etag),
lastModified: res.headers.get("Last-Modified") ?? (res.ok ? void 0 : revalidationData.lastModified)
};
}
function webToCachePolicyRequest({ url, method, headers: _headers }) {
@@ -38,5 +72,6 @@ function webToCachePolicyResponse({ status, headers: _headers }) {
};
}
export {
loadRemoteImage
loadRemoteImage,
revalidateRemoteImage
};

View File

@@ -22,7 +22,15 @@ const VALID_SUPPORTED_FORMATS = [
];
const DEFAULT_OUTPUT_FORMAT = "webp";
const VALID_OUTPUT_FORMATS = ["avif", "png", "webp", "jpeg", "jpg", "svg"];
const DEFAULT_HASH_PROPS = ["src", "width", "height", "format", "quality"];
const DEFAULT_HASH_PROPS = [
"src",
"width",
"height",
"format",
"quality",
"fit",
"position"
];
export {
DEFAULT_HASH_PROPS,
DEFAULT_OUTPUT_FORMAT,

View File

@@ -1,2 +1,2 @@
import type { AstroSettings } from '../../@types/astro.js';
export declare function injectImageEndpoint(settings: AstroSettings, mode: 'dev' | 'build'): AstroSettings;
import type { AstroSettings, RoutesList } from '../../types/astro.js';
export declare function injectImageEndpoint(settings: AstroSettings, manifest: RoutesList, mode: 'dev' | 'build', cwd?: string): void;

View File

@@ -1,11 +1,39 @@
function injectImageEndpoint(settings, mode) {
const endpointEntrypoint = settings.config.image.endpoint ?? (mode === "dev" ? "astro/assets/endpoint/node" : "astro/assets/endpoint/generic");
settings.injectedRoutes.push({
pattern: "/_image",
entrypoint: endpointEntrypoint,
prerender: false
});
return settings;
import {
removeLeadingForwardSlash,
removeTrailingForwardSlash
} from "@astrojs/internal-helpers/path";
import { resolveInjectedRoute } from "../../core/routing/manifest/create.js";
import { getPattern } from "../../core/routing/manifest/pattern.js";
function injectImageEndpoint(settings, manifest, mode, cwd) {
manifest.routes.unshift(getImageEndpointData(settings, mode, cwd));
}
function getImageEndpointData(settings, mode, cwd) {
const endpointEntrypoint = settings.config.image.endpoint.entrypoint === void 0 ? mode === "dev" ? "astro/assets/endpoint/node" : "astro/assets/endpoint/generic" : settings.config.image.endpoint.entrypoint;
const segments = [
[
{
content: removeTrailingForwardSlash(
removeLeadingForwardSlash(settings.config.image.endpoint.route)
),
dynamic: false,
spread: false
}
]
];
return {
type: "endpoint",
isIndex: false,
route: settings.config.image.endpoint.route,
pattern: getPattern(segments, settings.config.base, settings.config.trailingSlash),
segments,
params: [],
component: resolveInjectedRoute(endpointEntrypoint, settings.config.root, cwd).component,
generate: () => "",
pathname: settings.config.image.endpoint.route,
prerender: false,
fallbackRoutes: [],
origin: "internal"
};
}
export {
injectImageEndpoint

View File

@@ -1,4 +1,4 @@
import type { APIRoute } from '../../@types/astro.js';
import type { APIRoute } from '../../types/public/common.js';
/**
* Endpoint used in dev and SSR to serve optimized images by the base image services
*/

View File

@@ -1,9 +1,9 @@
import { imageConfig } from "astro:assets";
import { isRemotePath } from "@astrojs/internal-helpers/path";
import { isRemoteAllowed } from "@astrojs/internal-helpers/remote";
import * as mime from "mrmime";
import { getConfiguredImageService } from "../internal.js";
import { etag } from "../utils/etag.js";
import { isRemoteAllowed } from "../utils/remotePattern.js";
async function loadRemoteImage(src, headers) {
try {
const res = await fetch(src, {

View File

@@ -1,4 +1,4 @@
import type { APIRoute } from '../../@types/astro.js';
import type { APIRoute } from '../../types/public/common.js';
/**
* Endpoint used in dev and SSR to serve optimized images by the base image services
*/

View File

@@ -1,13 +1,13 @@
import { assetsDir, imageConfig, outDir } from "astro:assets";
import { readFile } from "node:fs/promises";
import os from "node:os";
import { isAbsolute } from "node:path";
import { fileURLToPath, pathToFileURL } from "node:url";
import { assetsDir, imageConfig, outDir } from "astro:assets";
import { isRemotePath, removeQueryString } from "@astrojs/internal-helpers/path";
import { isRemoteAllowed } from "@astrojs/internal-helpers/remote";
import * as mime from "mrmime";
import { getConfiguredImageService } from "../internal.js";
import { etag } from "../utils/etag.js";
import { isRemoteAllowed } from "../utils/remotePattern.js";
function replaceFileSystemReferences(src) {
return os.platform().includes("win32") ? src.replace(/^\/@fs\//, "") : src.replace(/^\/@fs/, "");
}

363
node_modules/astro/dist/assets/fonts/config.d.ts generated vendored Normal file
View File

@@ -0,0 +1,363 @@
import { z } from 'zod';
export declare const styleSchema: z.ZodEnum<["normal", "italic", "oblique"]>;
export declare const fontProviderSchema: z.ZodObject<{
/**
* URL, path relative to the root or package import.
*/
entrypoint: z.ZodUnion<[z.ZodString, z.ZodType<URL, z.ZodTypeDef, URL>]>;
/**
* Optional serializable object passed to the unifont provider.
*/
config: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodAny>>;
}, "strict", z.ZodTypeAny, {
entrypoint: string | URL;
config?: Record<string, any> | undefined;
}, {
entrypoint: string | URL;
config?: Record<string, any> | undefined;
}>;
export declare const localFontFamilySchema: z.ZodObject<z.objectUtil.extendShape<z.objectUtil.extendShape<{
/**
* The font family name, as identified by your font provider.
*/
name: z.ZodString;
/**
* 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: z.ZodString;
}, {
/**
* @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: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
/**
* @default `true`
*
* Whether or not to enable optimized fallback generation. You may disable this default optimization to have full control over `fallbacks`.
*/
optimizedFallbacks: z.ZodOptional<z.ZodBoolean>;
}>, {
/**
* The source of your font files. Set to `"local"` to use local font files.
*/
provider: z.ZodLiteral<"local">;
/**
* Each variant represents a [`@font-face` declaration](https://developer.mozilla.org/en-US/docs/Web/CSS/@font-face/).
*/
variants: z.ZodArray<z.ZodObject<z.objectUtil.extendShape<{
/**
* 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: z.ZodOptional<z.ZodUnion<[z.ZodString, z.ZodNumber]>>;
/**
* A [font style](https://developer.mozilla.org/en-US/docs/Web/CSS/font-style).
*/
style: z.ZodOptional<z.ZodEnum<["normal", "italic", "oblique"]>>;
/**
* @default `"swap"`
*
* A [font display](https://developer.mozilla.org/en-US/docs/Web/CSS/@font-face/font-display).
*/
display: z.ZodOptional<z.ZodEnum<["auto", "block", "swap", "fallback", "optional"]>>;
/**
* A [font stretch](https://developer.mozilla.org/en-US/docs/Web/CSS/@font-face/font-stretch).
*/
stretch: z.ZodOptional<z.ZodString>;
/**
* Font [feature settings](https://developer.mozilla.org/en-US/docs/Web/CSS/@font-face/font-feature-settings).
*/
featureSettings: z.ZodOptional<z.ZodString>;
/**
* Font [variation settings](https://developer.mozilla.org/en-US/docs/Web/CSS/@font-face/font-variation-settings).
*/
variationSettings: z.ZodOptional<z.ZodString>;
}, {
/**
* 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: z.ZodArray<z.ZodUnion<[z.ZodUnion<[z.ZodString, z.ZodType<URL, z.ZodTypeDef, URL>]>, z.ZodObject<{
url: z.ZodUnion<[z.ZodString, z.ZodType<URL, z.ZodTypeDef, URL>]>;
tech: z.ZodOptional<z.ZodString>;
}, "strict", z.ZodTypeAny, {
url: string | URL;
tech?: string | undefined;
}, {
url: string | URL;
tech?: string | undefined;
}>]>, "atleastone">;
/**
* A [unicode range](https://developer.mozilla.org/en-US/docs/Web/CSS/@font-face/unicode-range).
*/
unicodeRange: z.ZodOptional<z.ZodArray<z.ZodString, "atleastone">>;
}>, "strict", z.ZodTypeAny, {
src: [string | URL | {
url: string | URL;
tech?: string | undefined;
}, ...(string | URL | {
url: string | URL;
tech?: string | undefined;
})[]];
weight?: string | number | undefined;
style?: "normal" | "italic" | "oblique" | undefined;
display?: "auto" | "block" | "swap" | "fallback" | "optional" | undefined;
stretch?: string | undefined;
featureSettings?: string | undefined;
variationSettings?: string | undefined;
unicodeRange?: [string, ...string[]] | undefined;
}, {
src: [string | URL | {
url: string | URL;
tech?: string | undefined;
}, ...(string | URL | {
url: string | URL;
tech?: string | undefined;
})[]];
weight?: string | number | undefined;
style?: "normal" | "italic" | "oblique" | undefined;
display?: "auto" | "block" | "swap" | "fallback" | "optional" | undefined;
stretch?: string | undefined;
featureSettings?: string | undefined;
variationSettings?: string | undefined;
unicodeRange?: [string, ...string[]] | undefined;
}>, "atleastone">;
}>, "strict", z.ZodTypeAny, {
name: string;
cssVariable: string;
provider: "local";
variants: [{
src: [string | URL | {
url: string | URL;
tech?: string | undefined;
}, ...(string | URL | {
url: string | URL;
tech?: string | undefined;
})[]];
weight?: string | number | undefined;
style?: "normal" | "italic" | "oblique" | undefined;
display?: "auto" | "block" | "swap" | "fallback" | "optional" | undefined;
stretch?: string | undefined;
featureSettings?: string | undefined;
variationSettings?: string | undefined;
unicodeRange?: [string, ...string[]] | undefined;
}, ...{
src: [string | URL | {
url: string | URL;
tech?: string | undefined;
}, ...(string | URL | {
url: string | URL;
tech?: string | undefined;
})[]];
weight?: string | number | undefined;
style?: "normal" | "italic" | "oblique" | undefined;
display?: "auto" | "block" | "swap" | "fallback" | "optional" | undefined;
stretch?: string | undefined;
featureSettings?: string | undefined;
variationSettings?: string | undefined;
unicodeRange?: [string, ...string[]] | undefined;
}[]];
fallbacks?: string[] | undefined;
optimizedFallbacks?: boolean | undefined;
}, {
name: string;
cssVariable: string;
provider: "local";
variants: [{
src: [string | URL | {
url: string | URL;
tech?: string | undefined;
}, ...(string | URL | {
url: string | URL;
tech?: string | undefined;
})[]];
weight?: string | number | undefined;
style?: "normal" | "italic" | "oblique" | undefined;
display?: "auto" | "block" | "swap" | "fallback" | "optional" | undefined;
stretch?: string | undefined;
featureSettings?: string | undefined;
variationSettings?: string | undefined;
unicodeRange?: [string, ...string[]] | undefined;
}, ...{
src: [string | URL | {
url: string | URL;
tech?: string | undefined;
}, ...(string | URL | {
url: string | URL;
tech?: string | undefined;
})[]];
weight?: string | number | undefined;
style?: "normal" | "italic" | "oblique" | undefined;
display?: "auto" | "block" | "swap" | "fallback" | "optional" | undefined;
stretch?: string | undefined;
featureSettings?: string | undefined;
variationSettings?: string | undefined;
unicodeRange?: [string, ...string[]] | undefined;
}[]];
fallbacks?: string[] | undefined;
optimizedFallbacks?: boolean | undefined;
}>;
export declare const remoteFontFamilySchema: z.ZodObject<z.objectUtil.extendShape<z.objectUtil.extendShape<z.objectUtil.extendShape<{
/**
* The font family name, as identified by your font provider.
*/
name: z.ZodString;
/**
* 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: z.ZodString;
}, Omit<{
/**
* 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: z.ZodOptional<z.ZodUnion<[z.ZodString, z.ZodNumber]>>;
/**
* A [font style](https://developer.mozilla.org/en-US/docs/Web/CSS/font-style).
*/
style: z.ZodOptional<z.ZodEnum<["normal", "italic", "oblique"]>>;
/**
* @default `"swap"`
*
* A [font display](https://developer.mozilla.org/en-US/docs/Web/CSS/@font-face/font-display).
*/
display: z.ZodOptional<z.ZodEnum<["auto", "block", "swap", "fallback", "optional"]>>;
/**
* A [font stretch](https://developer.mozilla.org/en-US/docs/Web/CSS/@font-face/font-stretch).
*/
stretch: z.ZodOptional<z.ZodString>;
/**
* Font [feature settings](https://developer.mozilla.org/en-US/docs/Web/CSS/@font-face/font-feature-settings).
*/
featureSettings: z.ZodOptional<z.ZodString>;
/**
* Font [variation settings](https://developer.mozilla.org/en-US/docs/Web/CSS/@font-face/font-variation-settings).
*/
variationSettings: z.ZodOptional<z.ZodString>;
}, "weight" | "style">>, {
/**
* @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: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
/**
* @default `true`
*
* Whether or not to enable optimized fallback generation. You may disable this default optimization to have full control over `fallbacks`.
*/
optimizedFallbacks: z.ZodOptional<z.ZodBoolean>;
}>, {
/**
* The source of your font files. You can use a built-in provider or write your own custom provider.
*/
provider: z.ZodObject<{
/**
* URL, path relative to the root or package import.
*/
entrypoint: z.ZodUnion<[z.ZodString, z.ZodType<URL, z.ZodTypeDef, URL>]>;
/**
* Optional serializable object passed to the unifont provider.
*/
config: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodAny>>;
}, "strict", z.ZodTypeAny, {
entrypoint: string | URL;
config?: Record<string, any> | undefined;
}, {
entrypoint: string | URL;
config?: Record<string, any> | undefined;
}>;
/**
* @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: z.ZodOptional<z.ZodArray<z.ZodUnion<[z.ZodString, z.ZodNumber]>, "atleastone">>;
/**
* @default `["normal", "italic"]`
*
* An array of [font styles](https://developer.mozilla.org/en-US/docs/Web/CSS/font-style).
*/
styles: z.ZodOptional<z.ZodArray<z.ZodEnum<["normal", "italic", "oblique"]>, "atleastone">>;
/**
* @default `["cyrillic-ext", "cyrillic", "greek-ext", "greek", "vietnamese", "latin-ext", "latin"]`
*
* An array of [font subsets](https://knaap.dev/posts/font-subsetting/):
*/
subsets: z.ZodOptional<z.ZodArray<z.ZodString, "atleastone">>;
/**
* A [unicode range](https://developer.mozilla.org/en-US/docs/Web/CSS/@font-face/unicode-range).
*/
unicodeRange: z.ZodOptional<z.ZodArray<z.ZodString, "atleastone">>;
}>, "strict", z.ZodTypeAny, {
name: string;
cssVariable: string;
provider: {
entrypoint: string | URL;
config?: Record<string, any> | undefined;
};
weights?: [string | number, ...(string | number)[]] | undefined;
styles?: ["normal" | "italic" | "oblique", ...("normal" | "italic" | "oblique")[]] | undefined;
subsets?: [string, ...string[]] | undefined;
fallbacks?: string[] | undefined;
optimizedFallbacks?: boolean | undefined;
display?: "auto" | "block" | "swap" | "fallback" | "optional" | undefined;
stretch?: string | undefined;
featureSettings?: string | undefined;
variationSettings?: string | undefined;
unicodeRange?: [string, ...string[]] | undefined;
}, {
name: string;
cssVariable: string;
provider: {
entrypoint: string | URL;
config?: Record<string, any> | undefined;
};
weights?: [string | number, ...(string | number)[]] | undefined;
styles?: ["normal" | "italic" | "oblique", ...("normal" | "italic" | "oblique")[]] | undefined;
subsets?: [string, ...string[]] | undefined;
fallbacks?: string[] | undefined;
optimizedFallbacks?: boolean | undefined;
display?: "auto" | "block" | "swap" | "fallback" | "optional" | undefined;
stretch?: string | undefined;
featureSettings?: string | undefined;
variationSettings?: string | undefined;
unicodeRange?: [string, ...string[]] | undefined;
}>;

161
node_modules/astro/dist/assets/fonts/config.js generated vendored Normal file
View File

@@ -0,0 +1,161 @@
import { z } from "zod";
import { LOCAL_PROVIDER_NAME } from "./constants.js";
const weightSchema = z.union([z.string(), z.number()]);
const styleSchema = z.enum(["normal", "italic", "oblique"]);
const unicodeRangeSchema = z.array(z.string()).nonempty();
const familyPropertiesSchema = z.object({
/**
* 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: weightSchema.optional(),
/**
* A [font style](https://developer.mozilla.org/en-US/docs/Web/CSS/font-style).
*/
style: styleSchema.optional(),
/**
* @default `"swap"`
*
* A [font display](https://developer.mozilla.org/en-US/docs/Web/CSS/@font-face/font-display).
*/
display: z.enum(["auto", "block", "swap", "fallback", "optional"]).optional(),
/**
* A [font stretch](https://developer.mozilla.org/en-US/docs/Web/CSS/@font-face/font-stretch).
*/
stretch: z.string().optional(),
/**
* Font [feature settings](https://developer.mozilla.org/en-US/docs/Web/CSS/@font-face/font-feature-settings).
*/
featureSettings: z.string().optional(),
/**
* Font [variation settings](https://developer.mozilla.org/en-US/docs/Web/CSS/@font-face/font-variation-settings).
*/
variationSettings: z.string().optional()
});
const fallbacksSchema = z.object({
/**
* @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: z.array(z.string()).optional(),
/**
* @default `true`
*
* Whether or not to enable optimized fallback generation. You may disable this default optimization to have full control over `fallbacks`.
*/
optimizedFallbacks: z.boolean().optional()
});
const requiredFamilyAttributesSchema = z.object({
/**
* The font family name, as identified by your font provider.
*/
name: z.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: z.string()
});
const entrypointSchema = z.union([z.string(), z.instanceof(URL)]);
const fontProviderSchema = z.object({
/**
* URL, path relative to the root or package import.
*/
entrypoint: entrypointSchema,
/**
* Optional serializable object passed to the unifont provider.
*/
config: z.record(z.string(), z.any()).optional()
}).strict();
const localFontFamilySchema = requiredFamilyAttributesSchema.merge(fallbacksSchema).merge(
z.object({
/**
* The source of your font files. Set to `"local"` to use local font files.
*/
provider: z.literal(LOCAL_PROVIDER_NAME),
/**
* Each variant represents a [`@font-face` declaration](https://developer.mozilla.org/en-US/docs/Web/CSS/@font-face/).
*/
variants: z.array(
familyPropertiesSchema.merge(
z.object({
/**
* 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: z.array(
z.union([
entrypointSchema,
z.object({ url: entrypointSchema, tech: z.string().optional() }).strict()
])
).nonempty(),
/**
* A [unicode range](https://developer.mozilla.org/en-US/docs/Web/CSS/@font-face/unicode-range).
*/
unicodeRange: unicodeRangeSchema.optional()
// TODO: find a way to support subsets (through fontkit?)
}).strict()
)
).nonempty()
})
).strict();
const remoteFontFamilySchema = requiredFamilyAttributesSchema.merge(
familyPropertiesSchema.omit({
weight: true,
style: true
})
).merge(fallbacksSchema).merge(
z.object({
/**
* The source of your font files. You can use a built-in provider or write your own custom provider.
*/
provider: fontProviderSchema,
/**
* @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: z.array(weightSchema).nonempty().optional(),
/**
* @default `["normal", "italic"]`
*
* An array of [font styles](https://developer.mozilla.org/en-US/docs/Web/CSS/font-style).
*/
styles: z.array(styleSchema).nonempty().optional(),
/**
* @default `["cyrillic-ext", "cyrillic", "greek-ext", "greek", "vietnamese", "latin-ext", "latin"]`
*
* An array of [font subsets](https://knaap.dev/posts/font-subsetting/):
*/
subsets: z.array(z.string()).nonempty().optional(),
/**
* A [unicode range](https://developer.mozilla.org/en-US/docs/Web/CSS/@font-face/unicode-range).
*/
unicodeRange: unicodeRangeSchema.optional()
})
).strict();
export {
fontProviderSchema,
localFontFamilySchema,
remoteFontFamilySchema,
styleSchema
};

14
node_modules/astro/dist/assets/fonts/constants.d.ts generated vendored Normal file
View File

@@ -0,0 +1,14 @@
import type { Defaults, FontType } from './types.js';
export declare const LOCAL_PROVIDER_NAME = "local";
export declare const DEFAULTS: Defaults;
export declare const VIRTUAL_MODULE_ID = "virtual:astro:assets/fonts/internal";
export declare const RESOLVED_VIRTUAL_MODULE_ID: string;
export declare const ASSETS_DIR = "fonts";
export declare const CACHE_DIR = "./fonts/";
export declare const FONT_TYPES: readonly ["woff2", "woff", "otf", "ttf", "eot"];
export declare const FONT_FORMATS: Array<{
type: FontType;
format: string;
}>;
export declare const GENERIC_FALLBACK_NAMES: readonly ["serif", "sans-serif", "monospace", "cursive", "fantasy", "system-ui", "ui-serif", "ui-sans-serif", "ui-monospace", "ui-rounded", "emoji", "math", "fangsong"];
export declare const FONTS_TYPES_FILE = "fonts.d.ts";

49
node_modules/astro/dist/assets/fonts/constants.js generated vendored Normal file
View File

@@ -0,0 +1,49 @@
const LOCAL_PROVIDER_NAME = "local";
const DEFAULTS = {
weights: ["400"],
styles: ["normal", "italic"],
subsets: ["cyrillic-ext", "cyrillic", "greek-ext", "greek", "vietnamese", "latin-ext", "latin"],
// Technically serif is the browser default but most websites these days use sans-serif
fallbacks: ["sans-serif"],
optimizedFallbacks: true
};
const VIRTUAL_MODULE_ID = "virtual:astro:assets/fonts/internal";
const RESOLVED_VIRTUAL_MODULE_ID = "\0" + VIRTUAL_MODULE_ID;
const ASSETS_DIR = "fonts";
const CACHE_DIR = "./fonts/";
const FONT_TYPES = ["woff2", "woff", "otf", "ttf", "eot"];
const FONT_FORMATS = [
{ type: "woff2", format: "woff2" },
{ type: "woff", format: "woff" },
{ type: "otf", format: "opentype" },
{ type: "ttf", format: "truetype" },
{ type: "eot", format: "embedded-opentype" }
];
const GENERIC_FALLBACK_NAMES = [
"serif",
"sans-serif",
"monospace",
"cursive",
"fantasy",
"system-ui",
"ui-serif",
"ui-sans-serif",
"ui-monospace",
"ui-rounded",
"emoji",
"math",
"fangsong"
];
const FONTS_TYPES_FILE = "fonts.d.ts";
export {
ASSETS_DIR,
CACHE_DIR,
DEFAULTS,
FONTS_TYPES_FILE,
FONT_FORMATS,
FONT_TYPES,
GENERIC_FALLBACK_NAMES,
LOCAL_PROVIDER_NAME,
RESOLVED_VIRTUAL_MODULE_ID,
VIRTUAL_MODULE_ID
};

88
node_modules/astro/dist/assets/fonts/definitions.d.ts generated vendored Normal file
View File

@@ -0,0 +1,88 @@
import type * as unifont from 'unifont';
import type { CollectedFontForMetrics } from './logic/optimize-fallbacks.js';
import type { AstroFontProvider, FontFaceMetrics, FontFileData, FontType, GenericFallbackName, PreloadData, ResolvedFontProvider, Style } from './types.js';
export interface Hasher {
hashString: (input: string) => string;
hashObject: (input: Record<string, any>) => string;
}
export interface RemoteFontProviderModResolver {
resolve: (id: string) => Promise<any>;
}
export interface RemoteFontProviderResolver {
resolve: (provider: AstroFontProvider) => Promise<ResolvedFontProvider>;
}
export interface LocalProviderUrlResolver {
resolve: (input: string) => string;
}
type SingleErrorInput<TType extends string, TData extends Record<string, any>> = {
type: TType;
data: TData;
cause: unknown;
};
export type ErrorHandlerInput = SingleErrorInput<'cannot-load-font-provider', {
entrypoint: string;
}> | SingleErrorInput<'unknown-fs-error', {}> | SingleErrorInput<'cannot-fetch-font-file', {
url: string;
}> | SingleErrorInput<'cannot-extract-font-type', {
url: string;
}> | SingleErrorInput<'cannot-extract-data', {
family: string;
url: string;
}>;
export interface ErrorHandler {
handle: (input: ErrorHandlerInput) => Error;
}
export interface UrlProxy {
proxy: (input: Pick<FontFileData, 'url' | 'init'> & {
type: FontType;
collectPreload: boolean;
data: Partial<unifont.FontFaceData>;
}) => string;
}
export interface UrlResolver {
resolve: (hash: string) => string;
}
export interface UrlProxyContentResolver {
resolve: (url: string) => string;
}
export interface DataCollector {
collect: (input: FontFileData & {
data: Partial<unifont.FontFaceData>;
preload: PreloadData | null;
}) => void;
}
export type CssProperties = Record<string, string | undefined>;
export interface CssRenderer {
generateFontFace: (family: string, properties: CssProperties) => string;
generateCssVariable: (key: string, values: Array<string>) => string;
}
export interface FontMetricsResolver {
getMetrics: (name: string, font: CollectedFontForMetrics) => Promise<FontFaceMetrics>;
generateFontFace: (input: {
metrics: FontFaceMetrics;
fallbackMetrics: FontFaceMetrics;
name: string;
font: string;
properties: CssProperties;
}) => string;
}
export interface SystemFallbacksProvider {
getLocalFonts: (fallback: GenericFallbackName) => Array<string> | null;
getMetricsForLocalFont: (family: string) => FontFaceMetrics;
}
export interface FontFetcher {
fetch: (input: FontFileData) => Promise<Buffer>;
}
export interface FontTypeExtractor {
extract: (url: string) => FontType;
}
export interface FontFileReader {
extract: (input: {
family: string;
url: string;
}) => {
weight: string;
style: Style;
};
}
export {};

View File

@@ -0,0 +1,9 @@
import type { CssProperties, CssRenderer } from '../definitions.js';
export declare function renderFontFace(properties: CssProperties, minify: boolean): string;
export declare function renderCssVariable(key: string, values: Array<string>, minify: boolean): string;
export declare function withFamily(family: string, properties: CssProperties): CssProperties;
/** If the value contains spaces (which would be incorrectly interpreted), we wrap it in quotes. */
export declare function handleValueWithSpaces(value: string): string;
export declare function createMinifiableCssRenderer({ minify }: {
minify: boolean;
}): CssRenderer;

View File

@@ -0,0 +1,42 @@
function renderFontFace(properties, minify) {
const lf = minify ? "" : `
`;
const sp = minify ? "" : " ";
return `@font-face${sp}{${lf}${Object.entries(properties).filter(([, value]) => Boolean(value)).map(([key, value]) => `${sp}${sp}${key}:${sp}${value};`).join(lf)}${lf}}${lf}`;
}
function renderCssVariable(key, values, minify) {
const lf = minify ? "" : `
`;
const sp = minify ? "" : " ";
return `:root${sp}{${lf}${sp}${sp}${key}:${sp}${values.map((v) => handleValueWithSpaces(v)).join(`,${sp}`)};${lf}}${lf}`;
}
function withFamily(family, properties) {
return {
"font-family": handleValueWithSpaces(family),
...properties
};
}
const SPACE_RE = /\s/;
function handleValueWithSpaces(value) {
if (SPACE_RE.test(value)) {
return JSON.stringify(value);
}
return value;
}
function createMinifiableCssRenderer({ minify }) {
return {
generateFontFace(family, properties) {
return renderFontFace(withFamily(family, properties), minify);
},
generateCssVariable(key, values) {
return renderCssVariable(key, values, minify);
}
};
}
export {
createMinifiableCssRenderer,
handleValueWithSpaces,
renderCssVariable,
renderFontFace,
withFamily
};

View File

@@ -0,0 +1,3 @@
import type { DataCollector } from '../definitions.js';
import type { CreateUrlProxyParams } from '../types.js';
export declare function createDataCollector({ hasUrl, saveUrl, savePreload, saveFontData, }: Omit<CreateUrlProxyParams, 'local'>): DataCollector;

View File

@@ -0,0 +1,21 @@
function createDataCollector({
hasUrl,
saveUrl,
savePreload,
saveFontData
}) {
return {
collect({ hash, url, init, preload, data }) {
if (!hasUrl(hash)) {
saveUrl({ hash, url, init });
if (preload) {
savePreload(preload);
}
}
saveFontData({ hash, url, data, init });
}
};
}
export {
createDataCollector
};

View File

@@ -0,0 +1,2 @@
import type { ErrorHandler } from '../definitions.js';
export declare function createAstroErrorHandler(): ErrorHandler;

View File

@@ -0,0 +1,41 @@
import { AstroError, AstroErrorData } from "../../../core/errors/index.js";
function getProps(input) {
if (input.type === "cannot-load-font-provider") {
return {
...AstroErrorData.CannotLoadFontProvider,
message: AstroErrorData.CannotLoadFontProvider.message(input.data.entrypoint)
};
} else if (input.type === "unknown-fs-error") {
return AstroErrorData.UnknownFilesystemError;
} else if (input.type === "cannot-fetch-font-file") {
return {
...AstroErrorData.CannotFetchFontFile,
message: AstroErrorData.CannotFetchFontFile.message(input.data.url)
};
} else if (input.type === "cannot-extract-font-type") {
return {
...AstroErrorData.CannotExtractFontType,
message: AstroErrorData.CannotExtractFontType.message(input.data.url)
};
} else if (input.type === "cannot-extract-data") {
return {
...AstroErrorData.CannotDetermineWeightAndStyleFromFontFile,
message: AstroErrorData.CannotDetermineWeightAndStyleFromFontFile.message(
input.data.family,
input.data.url
)
};
}
input;
return AstroErrorData.UnknownError;
}
function createAstroErrorHandler() {
return {
handle(input) {
return new AstroError(getProps(input), { cause: input.cause });
}
};
}
export {
createAstroErrorHandler
};

View File

@@ -0,0 +1,8 @@
import type { Storage } from 'unstorage';
import type { ErrorHandler, FontFetcher } from '../definitions.js';
export declare function createCachedFontFetcher({ storage, errorHandler, fetch, readFile, }: {
storage: Storage;
errorHandler: ErrorHandler;
fetch: (url: string, init?: RequestInit) => Promise<Response>;
readFile: (url: string) => Promise<Buffer>;
}): FontFetcher;

View File

@@ -0,0 +1,34 @@
import { isAbsolute } from "node:path";
import { cache } from "../utils.js";
function createCachedFontFetcher({
storage,
errorHandler,
fetch,
readFile
}) {
return {
async fetch({ hash, url, init }) {
return await cache(storage, hash, async () => {
try {
if (isAbsolute(url)) {
return await readFile(url);
}
const response = await fetch(url, init ?? void 0);
if (!response.ok) {
throw new Error(`Response was not successful, received status code ${response.status}`);
}
return Buffer.from(await response.arrayBuffer());
} catch (cause) {
throw errorHandler.handle({
type: "cannot-fetch-font-file",
data: { url },
cause
});
}
});
}
};
}
export {
createCachedFontFetcher
};

View File

@@ -0,0 +1,4 @@
import type { ErrorHandler, FontFileReader } from '../definitions.js';
export declare function createFontaceFontFileReader({ errorHandler, }: {
errorHandler: ErrorHandler;
}): FontFileReader;

View File

@@ -0,0 +1,26 @@
import { readFileSync } from "node:fs";
import { fontace } from "fontace";
function createFontaceFontFileReader({
errorHandler
}) {
return {
extract({ family, url }) {
try {
const data = fontace(readFileSync(url));
return {
weight: data.weight,
style: data.style
};
} catch (cause) {
throw errorHandler.handle({
type: "cannot-extract-data",
data: { family, url },
cause
});
}
}
};
}
export {
createFontaceFontFileReader
};

View File

@@ -0,0 +1,5 @@
import type { CssRenderer, FontFetcher, FontMetricsResolver } from '../definitions.js';
export declare function createCapsizeFontMetricsResolver({ fontFetcher, cssRenderer, }: {
fontFetcher: FontFetcher;
cssRenderer: CssRenderer;
}): FontMetricsResolver;

View File

@@ -0,0 +1,60 @@
import { fromBuffer } from "@capsizecss/unpack";
import { renderFontSrc } from "../utils.js";
function filterRequiredMetrics({
ascent,
descent,
lineGap,
unitsPerEm,
xWidthAvg
}) {
return {
ascent,
descent,
lineGap,
unitsPerEm,
xWidthAvg
};
}
function toPercentage(value, fractionDigits = 4) {
const percentage = value * 100;
return `${+percentage.toFixed(fractionDigits)}%`;
}
function createCapsizeFontMetricsResolver({
fontFetcher,
cssRenderer
}) {
const cache = {};
return {
async getMetrics(name, input) {
cache[name] ??= filterRequiredMetrics(await fromBuffer(await fontFetcher.fetch(input)));
return cache[name];
},
// Source: https://github.com/unjs/fontaine/blob/f00f84032c5d5da72c8798eae4cd68d3ddfbf340/src/css.ts#L170
generateFontFace({
metrics,
fallbackMetrics,
name: fallbackName,
font: fallbackFontName,
properties
}) {
const preferredFontXAvgRatio = metrics.xWidthAvg / metrics.unitsPerEm;
const fallbackFontXAvgRatio = fallbackMetrics.xWidthAvg / fallbackMetrics.unitsPerEm;
const sizeAdjust = preferredFontXAvgRatio / fallbackFontXAvgRatio;
const adjustedEmSquare = metrics.unitsPerEm * sizeAdjust;
const ascentOverride = metrics.ascent / adjustedEmSquare;
const descentOverride = Math.abs(metrics.descent) / adjustedEmSquare;
const lineGapOverride = metrics.lineGap / adjustedEmSquare;
return cssRenderer.generateFontFace(fallbackName, {
...properties,
src: renderFontSrc([{ name: fallbackFontName }]),
"size-adjust": toPercentage(sizeAdjust),
"ascent-override": toPercentage(ascentOverride),
"descent-override": toPercentage(descentOverride),
"line-gap-override": toPercentage(lineGapOverride)
});
}
};
}
export {
createCapsizeFontMetricsResolver
};

View File

@@ -0,0 +1,4 @@
import type { ErrorHandler, FontTypeExtractor } from '../definitions.js';
export declare function createFontTypeExtractor({ errorHandler, }: {
errorHandler: ErrorHandler;
}): FontTypeExtractor;

View File

@@ -0,0 +1,22 @@
import { extname } from "node:path";
import { isFontType } from "../utils.js";
function createFontTypeExtractor({
errorHandler
}) {
return {
extract(url) {
const extension = extname(url).slice(1);
if (!isFontType(extension)) {
throw errorHandler.handle({
type: "cannot-extract-font-type",
data: { url },
cause: `Unexpected extension, got "${extension}"`
});
}
return extension;
}
};
}
export {
createFontTypeExtractor
};

View File

@@ -0,0 +1,2 @@
import type { Hasher } from '../definitions.js';
export declare function createXxHasher(): Promise<Hasher>;

View File

@@ -0,0 +1,14 @@
import xxhash from "xxhash-wasm";
import { sortObjectByKey } from "../utils.js";
async function createXxHasher() {
const { h64ToString: hashString } = await xxhash();
return {
hashString,
hashObject(input) {
return hashString(JSON.stringify(sortObjectByKey(input)));
}
};
}
export {
createXxHasher
};

View File

@@ -0,0 +1,5 @@
import type { LocalProviderUrlResolver } from '../definitions.js';
export declare function createRequireLocalProviderUrlResolver({ root, intercept, }: {
root: URL;
intercept?: (path: string) => void;
}): LocalProviderUrlResolver;

View File

@@ -0,0 +1,17 @@
import { fileURLToPath } from "node:url";
import { resolveEntrypoint } from "../utils.js";
function createRequireLocalProviderUrlResolver({
root,
intercept
}) {
return {
resolve(input) {
const path = fileURLToPath(resolveEntrypoint(root, input));
intercept?.(path);
return path;
}
};
}
export {
createRequireLocalProviderUrlResolver
};

View File

@@ -0,0 +1,6 @@
import type { ViteDevServer } from 'vite';
import type { RemoteFontProviderModResolver } from '../definitions.js';
export declare function createBuildRemoteFontProviderModResolver(): RemoteFontProviderModResolver;
export declare function createDevServerRemoteFontProviderModResolver({ server, }: {
server: ViteDevServer;
}): RemoteFontProviderModResolver;

View File

@@ -0,0 +1,20 @@
function createBuildRemoteFontProviderModResolver() {
return {
resolve(id) {
return import(id);
}
};
}
function createDevServerRemoteFontProviderModResolver({
server
}) {
return {
resolve(id) {
return server.ssrLoadModule(id);
}
};
}
export {
createBuildRemoteFontProviderModResolver,
createDevServerRemoteFontProviderModResolver
};

View File

@@ -0,0 +1,6 @@
import type { ErrorHandler, RemoteFontProviderModResolver, RemoteFontProviderResolver } from '../definitions.js';
export declare function createRemoteFontProviderResolver({ root, modResolver, errorHandler, }: {
root: URL;
modResolver: RemoteFontProviderModResolver;
errorHandler: ErrorHandler;
}): RemoteFontProviderResolver;

View File

@@ -0,0 +1,47 @@
import { resolveEntrypoint } from "../utils.js";
function validateMod({
mod,
entrypoint,
errorHandler
}) {
try {
if (typeof mod !== "object" || mod === null) {
throw new Error(`Expected an object for the module, but received ${typeof mod}.`);
}
if (typeof mod.provider !== "function") {
throw new Error(`Invalid provider export in module, expected a function.`);
}
return {
provider: mod.provider
};
} catch (cause) {
throw errorHandler.handle({
type: "cannot-load-font-provider",
data: {
entrypoint
},
cause
});
}
}
function createRemoteFontProviderResolver({
root,
modResolver,
errorHandler
}) {
return {
async resolve({ entrypoint, config }) {
const id = resolveEntrypoint(root, entrypoint.toString()).href;
const mod = await modResolver.resolve(id);
const { provider } = validateMod({
mod,
entrypoint: id,
errorHandler
});
return { config, provider };
}
};
}
export {
createRemoteFontProviderResolver
};

View File

@@ -0,0 +1,4 @@
import { type Storage } from 'unstorage';
export declare function createFsStorage({ base }: {
base: URL;
}): Storage;

View File

@@ -0,0 +1,14 @@
import { fileURLToPath } from "node:url";
import { createStorage } from "unstorage";
import fsLiteDriver from "unstorage/drivers/fs-lite";
function createFsStorage({ base }) {
return createStorage({
// Types are weirly exported
driver: fsLiteDriver({
base: fileURLToPath(base)
})
});
}
export {
createFsStorage
};

View File

@@ -0,0 +1,11 @@
import type { SystemFallbacksProvider } from '../definitions.js';
export declare const DEFAULT_FALLBACKS: {
serif: "Times New Roman"[];
'sans-serif': "Arial"[];
monospace: "Courier New"[];
'system-ui': ("Arial" | "BlinkMacSystemFont" | "Segoe UI" | "Roboto" | "Helvetica Neue")[];
'ui-serif': "Times New Roman"[];
'ui-sans-serif': "Arial"[];
'ui-monospace': "Courier New"[];
};
export declare function createSystemFallbacksProvider(): SystemFallbacksProvider;

View File

@@ -0,0 +1,74 @@
const SYSTEM_METRICS = {
"Times New Roman": {
ascent: 1825,
descent: -443,
lineGap: 87,
unitsPerEm: 2048,
xWidthAvg: 832
},
Arial: {
ascent: 1854,
descent: -434,
lineGap: 67,
unitsPerEm: 2048,
xWidthAvg: 913
},
"Courier New": {
ascent: 1705,
descent: -615,
lineGap: 0,
unitsPerEm: 2048,
xWidthAvg: 1229
},
BlinkMacSystemFont: {
ascent: 1980,
descent: -432,
lineGap: 0,
unitsPerEm: 2048,
xWidthAvg: 853
},
"Segoe UI": {
ascent: 2210,
descent: -514,
lineGap: 0,
unitsPerEm: 2048,
xWidthAvg: 908
},
Roboto: {
ascent: 1900,
descent: -500,
lineGap: 0,
unitsPerEm: 2048,
xWidthAvg: 911
},
"Helvetica Neue": {
ascent: 952,
descent: -213,
lineGap: 28,
unitsPerEm: 1e3,
xWidthAvg: 450
}
};
const DEFAULT_FALLBACKS = {
serif: ["Times New Roman"],
"sans-serif": ["Arial"],
monospace: ["Courier New"],
"system-ui": ["BlinkMacSystemFont", "Segoe UI", "Roboto", "Helvetica Neue", "Arial"],
"ui-serif": ["Times New Roman"],
"ui-sans-serif": ["Arial"],
"ui-monospace": ["Courier New"]
};
function createSystemFallbacksProvider() {
return {
getLocalFonts(fallback) {
return DEFAULT_FALLBACKS[fallback] ?? null;
},
getMetricsForLocalFont(family) {
return SYSTEM_METRICS[family];
}
};
}
export {
DEFAULT_FALLBACKS,
createSystemFallbacksProvider
};

View File

@@ -0,0 +1,5 @@
import type { ErrorHandler, UrlProxyContentResolver } from '../definitions.js';
export declare function createLocalUrlProxyContentResolver({ errorHandler, }: {
errorHandler: ErrorHandler;
}): UrlProxyContentResolver;
export declare function createRemoteUrlProxyContentResolver(): UrlProxyContentResolver;

View File

@@ -0,0 +1,28 @@
import { readFileSync } from "node:fs";
function createLocalUrlProxyContentResolver({
errorHandler
}) {
return {
resolve(url) {
try {
return url + readFileSync(url, "utf-8");
} catch (cause) {
throw errorHandler.handle({
type: "unknown-fs-error",
data: {},
cause
});
}
}
};
}
function createRemoteUrlProxyContentResolver() {
return {
// Passthrough, the remote provider URL is enough
resolve: (url) => url
};
}
export {
createLocalUrlProxyContentResolver,
createRemoteUrlProxyContentResolver
};

View File

@@ -0,0 +1,7 @@
import type { DataCollector, Hasher, UrlProxy, UrlProxyContentResolver, UrlResolver } from '../definitions.js';
export declare function createUrlProxy({ contentResolver, hasher, dataCollector, urlResolver, }: {
contentResolver: UrlProxyContentResolver;
hasher: Hasher;
dataCollector: DataCollector;
urlResolver: UrlResolver;
}): UrlProxy;

View File

@@ -0,0 +1,24 @@
function createUrlProxy({
contentResolver,
hasher,
dataCollector,
urlResolver
}) {
return {
proxy({ url: originalUrl, type, data, collectPreload, init }) {
const hash = `${hasher.hashString(contentResolver.resolve(originalUrl))}.${type}`;
const url = urlResolver.resolve(hash);
dataCollector.collect({
url: originalUrl,
hash,
preload: collectPreload ? { url, type } : null,
data,
init
});
return url;
}
};
}
export {
createUrlProxy
};

View File

@@ -0,0 +1,9 @@
import type { AssetsPrefix } from '../../../types/public/index.js';
import type { UrlResolver } from '../definitions.js';
export declare function createDevUrlResolver({ base }: {
base: string;
}): UrlResolver;
export declare function createBuildUrlResolver({ base, assetsPrefix, }: {
base: string;
assetsPrefix: AssetsPrefix;
}): UrlResolver;

View File

@@ -0,0 +1,27 @@
import { fileExtension, joinPaths, prependForwardSlash } from "../../../core/path.js";
import { getAssetsPrefix } from "../../utils/getAssetsPrefix.js";
function createDevUrlResolver({ base }) {
return {
resolve(hash) {
return prependForwardSlash(joinPaths(base, hash));
}
};
}
function createBuildUrlResolver({
base,
assetsPrefix
}) {
return {
resolve(hash) {
const prefix = assetsPrefix ? getAssetsPrefix(fileExtension(hash), assetsPrefix) : void 0;
if (prefix) {
return joinPaths(prefix, base, hash);
}
return prependForwardSlash(joinPaths(base, hash));
}
};
}
export {
createBuildUrlResolver,
createDevUrlResolver
};

View File

@@ -0,0 +1,10 @@
import type * as unifont from 'unifont';
import type { Hasher } from '../definitions.js';
import type { ResolvedFontFamily } from '../types.js';
export declare function extractUnifontProviders({ families, hasher, }: {
families: Array<ResolvedFontFamily>;
hasher: Hasher;
}): {
families: Array<ResolvedFontFamily>;
providers: Array<unifont.Provider>;
};

View File

@@ -0,0 +1,28 @@
import { LOCAL_PROVIDER_NAME } from "../constants.js";
function extractUnifontProviders({
families,
hasher
}) {
const hashes = /* @__PURE__ */ new Set();
const providers = [];
for (const { provider } of families) {
if (provider === LOCAL_PROVIDER_NAME) {
continue;
}
const unifontProvider = provider.provider(provider.config);
const hash = hasher.hashObject({
name: unifontProvider._name,
...provider.config
});
unifontProvider._name += `-${hash}`;
provider.name = unifontProvider._name;
if (!hashes.has(hash)) {
hashes.add(hash);
providers.push(unifontProvider);
}
}
return { families, providers };
}
export {
extractUnifontProviders
};

View File

@@ -0,0 +1,7 @@
import type * as unifont from 'unifont';
import type { FontTypeExtractor, UrlProxy } from '../definitions.js';
export declare function normalizeRemoteFontFaces({ fonts, urlProxy, fontTypeExtractor, }: {
fonts: Array<unifont.FontFaceData>;
urlProxy: UrlProxy;
fontTypeExtractor: FontTypeExtractor;
}): Array<unifont.FontFaceData>;

View File

@@ -0,0 +1,40 @@
import { FONT_FORMATS } from "../constants.js";
function normalizeRemoteFontFaces({
fonts,
urlProxy,
fontTypeExtractor
}) {
return fonts.filter((font) => typeof font.meta?.priority === "number" ? font.meta.priority === 0 : true).map((font) => {
let index = 0;
return {
...font,
src: font.src.map((source) => {
if ("name" in source) {
return source;
}
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
},
init: font.meta?.init ?? null
})
};
index++;
return proxied;
})
};
});
}
export {
normalizeRemoteFontFaces
};

View File

@@ -0,0 +1,17 @@
import type * as unifont from 'unifont';
import type { FontMetricsResolver, SystemFallbacksProvider } from '../definitions.js';
import type { FontFileData, ResolvedFontFamily } from '../types.js';
export interface CollectedFontForMetrics extends FontFileData {
data: Partial<unifont.FontFaceData>;
}
export declare function optimizeFallbacks({ family, fallbacks: _fallbacks, collectedFonts, enabled, systemFallbacksProvider, fontMetricsResolver, }: {
family: Pick<ResolvedFontFamily, 'name' | 'nameWithHash'>;
fallbacks: Array<string>;
collectedFonts: Array<CollectedFontForMetrics>;
enabled: boolean;
systemFallbacksProvider: SystemFallbacksProvider;
fontMetricsResolver: FontMetricsResolver;
}): Promise<null | {
css: string;
fallbacks: Array<string>;
}>;

View File

@@ -0,0 +1,47 @@
import { isGenericFontFamily, unifontFontFaceDataToProperties } from "../utils.js";
async function optimizeFallbacks({
family,
fallbacks: _fallbacks,
collectedFonts,
enabled,
systemFallbacksProvider,
fontMetricsResolver
}) {
let fallbacks = [..._fallbacks];
if (fallbacks.length === 0 || !enabled || collectedFonts.length === 0) {
return null;
}
const lastFallback = fallbacks[fallbacks.length - 1];
if (!isGenericFontFamily(lastFallback)) {
return null;
}
const localFonts = systemFallbacksProvider.getLocalFonts(lastFallback);
if (!localFonts || localFonts.length === 0) {
return null;
}
if (localFonts.includes(family.name)) {
return null;
}
const localFontsMappings = localFonts.map((font) => ({
font,
// We must't wrap in quote because that's handled by the CSS renderer
name: `${family.nameWithHash} fallback: ${font}`
}));
fallbacks = [...localFontsMappings.map((m) => m.name), ...fallbacks];
let css = "";
for (const { font, name } of localFontsMappings) {
for (const collected of collectedFonts) {
css += fontMetricsResolver.generateFontFace({
metrics: await fontMetricsResolver.getMetrics(family.name, collected),
fallbackMetrics: systemFallbacksProvider.getMetricsForLocalFont(font),
font,
name,
properties: unifontFontFaceDataToProperties(collected.data)
});
}
}
return { css, fallbacks };
}
export {
optimizeFallbacks
};

View File

@@ -0,0 +1,17 @@
import type { Hasher, LocalProviderUrlResolver, RemoteFontProviderResolver } from '../definitions.js';
import type { FontFamily, ResolvedFontFamily } from '../types.js';
/**
* Dedupes properties if applicable and resolves entrypoints.
*/
export declare function resolveFamily({ family, hasher, remoteFontProviderResolver, localProviderUrlResolver, }: {
family: FontFamily;
hasher: Hasher;
remoteFontProviderResolver: RemoteFontProviderResolver;
localProviderUrlResolver: LocalProviderUrlResolver;
}): Promise<ResolvedFontFamily>;
/**
* A function for convenience. The actual logic lives in resolveFamily
*/
export declare function resolveFamilies({ families, ...dependencies }: {
families: Array<FontFamily>;
} & Omit<Parameters<typeof resolveFamily>[0], 'family'>): Promise<Array<ResolvedFontFamily>>;

View File

@@ -0,0 +1,67 @@
import { LOCAL_PROVIDER_NAME } from "../constants.js";
import { dedupe, withoutQuotes } from "../utils.js";
function resolveVariants({
variants,
localProviderUrlResolver
}) {
return variants.map((variant) => ({
...variant,
weight: variant.weight?.toString(),
src: variant.src.map((value) => {
const isValue = typeof value === "string" || value instanceof URL;
const url = (isValue ? value : value.url).toString();
const tech = isValue ? void 0 : value.tech;
return {
url: localProviderUrlResolver.resolve(url),
tech
};
})
}));
}
async function resolveFamily({
family,
hasher,
remoteFontProviderResolver,
localProviderUrlResolver
}) {
const name = withoutQuotes(family.name);
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) : void 0
};
}
return {
...family,
name,
nameWithHash,
weights: family.weights ? dedupe(family.weights.map((weight) => weight.toString())) : void 0,
styles: family.styles ? dedupe(family.styles) : void 0,
subsets: family.subsets ? dedupe(family.subsets) : void 0,
fallbacks: family.fallbacks ? dedupe(family.fallbacks) : void 0,
unicodeRange: family.unicodeRange ? dedupe(family.unicodeRange) : void 0,
// This will be Astro specific eventually
provider: await remoteFontProviderResolver.resolve(family.provider)
};
}
async function resolveFamilies({
families,
...dependencies
}) {
return await Promise.all(
families.map(
(family) => resolveFamily({
family,
...dependencies
})
)
);
}
export {
resolveFamilies,
resolveFamily
};

40
node_modules/astro/dist/assets/fonts/orchestrate.d.ts generated vendored Normal file
View File

@@ -0,0 +1,40 @@
import type { Storage } from 'unstorage';
import type { Logger } from '../../core/logger/core.js';
import type { CssRenderer, FontFileReader, FontMetricsResolver, FontTypeExtractor, Hasher, LocalProviderUrlResolver, RemoteFontProviderResolver, SystemFallbacksProvider, UrlProxy } from './definitions.js';
import type { ConsumableMap, CreateUrlProxyParams, Defaults, FontFamily, FontFileDataMap } from './types.js';
/**
* Manages how fonts are resolved:
*
* - families are resolved
* - unifont providers are extracted from families
* - unifont 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 declare function orchestrate({ families, hasher, remoteFontProviderResolver, localProviderUrlResolver, storage, cssRenderer, systemFallbacksProvider, fontMetricsResolver, fontTypeExtractor, fontFileReader, logger, createUrlProxy, defaults, }: {
families: Array<FontFamily>;
hasher: Hasher;
remoteFontProviderResolver: RemoteFontProviderResolver;
localProviderUrlResolver: LocalProviderUrlResolver;
storage: Storage;
cssRenderer: CssRenderer;
systemFallbacksProvider: SystemFallbacksProvider;
fontMetricsResolver: FontMetricsResolver;
fontTypeExtractor: FontTypeExtractor;
fontFileReader: FontFileReader;
logger: Logger;
createUrlProxy: (params: CreateUrlProxyParams) => UrlProxy;
defaults: Defaults;
}): Promise<{
fontFileDataMap: FontFileDataMap;
consumableMap: ConsumableMap;
}>;

135
node_modules/astro/dist/assets/fonts/orchestrate.js generated vendored Normal file
View File

@@ -0,0 +1,135 @@
import { bold } from "kleur/colors";
import * as unifont from "unifont";
import { LOCAL_PROVIDER_NAME } from "./constants.js";
import { extractUnifontProviders } from "./logic/extract-unifont-providers.js";
import { normalizeRemoteFontFaces } from "./logic/normalize-remote-font-faces.js";
import { optimizeFallbacks } from "./logic/optimize-fallbacks.js";
import { resolveFamilies } from "./logic/resolve-families.js";
import { resolveLocalFont } from "./providers/local.js";
import { pickFontFaceProperty, unifontFontFaceDataToProperties } from "./utils.js";
async function orchestrate({
families,
hasher,
remoteFontProviderResolver,
localProviderUrlResolver,
storage,
cssRenderer,
systemFallbacksProvider,
fontMetricsResolver,
fontTypeExtractor,
fontFileReader,
logger,
createUrlProxy,
defaults
}) {
let resolvedFamilies = await resolveFamilies({
families,
hasher,
remoteFontProviderResolver,
localProviderUrlResolver
});
const extractedUnifontProvidersResult = extractUnifontProviders({
families: resolvedFamilies,
hasher
});
resolvedFamilies = extractedUnifontProvidersResult.families;
const unifontProviders = extractedUnifontProvidersResult.providers;
const { resolveFont } = await unifont.createUnifont(unifontProviders, {
storage
});
const fontFileDataMap = /* @__PURE__ */ new Map();
const consumableMap = /* @__PURE__ */ new Map();
for (const family of resolvedFamilies) {
const preloadData = [];
let css = "";
const collectedFonts = [];
const fallbacks = family.fallbacks ?? defaults.fallbacks ?? [];
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) => {
preloadData.push(preload);
},
saveFontData: (collected) => {
if (fallbacks && 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.
!collectedFonts.some((f) => JSON.stringify(f.data) === JSON.stringify(collected.data))) {
collectedFonts.push(collected);
}
}
});
let fonts;
if (family.provider === LOCAL_PROVIDER_NAME) {
const result = resolveLocalFont({
family,
urlProxy,
fontTypeExtractor,
fontFileReader
});
fonts = result.fonts;
} else {
const result = await resolveFont(
family.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,
fallbacks: family.fallbacks ?? defaults.fallbacks
},
// By default, unifont goes through all providers. We use a different approach where
// we specify a provider per font. Name has been set while extracting unifont providers
// from families (inside extractUnifontProviders).
[family.provider.name]
);
if (result.fonts.length === 0) {
logger.warn(
"assets",
`No data found for font family ${bold(family.name)}. Review your configuration`
);
}
fonts = normalizeRemoteFontFaces({ fonts: result.fonts, urlProxy, fontTypeExtractor });
}
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 })
})
);
}
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 {
cssVarValues.push(...fallbacks);
}
css += cssRenderer.generateCssVariable(family.cssVariable, cssVarValues);
consumableMap.set(family.cssVariable, { preloadData, css });
}
return { fontFileDataMap, consumableMap };
}
export {
orchestrate
};

View File

@@ -0,0 +1,2 @@
import { providers } from 'unifont';
export declare const provider: typeof providers.adobe;

View File

@@ -0,0 +1,5 @@
import { providers } from "unifont";
const provider = providers.adobe;
export {
provider
};

View File

@@ -0,0 +1 @@
export declare const provider: () => import("unifont").Provider;

View File

@@ -0,0 +1,5 @@
import { providers } from "unifont";
const provider = providers.bunny;
export {
provider
};

View File

@@ -0,0 +1 @@
export declare const provider: () => import("unifont").Provider;

View File

@@ -0,0 +1,5 @@
import { providers } from "unifont";
const provider = providers.fontshare;
export {
provider
};

View File

@@ -0,0 +1 @@
export declare const provider: () => import("unifont").Provider;

View File

@@ -0,0 +1,5 @@
import { providers } from "unifont";
const provider = providers.fontsource;
export {
provider
};

View File

@@ -0,0 +1,2 @@
import { providers } from 'unifont';
export declare const provider: typeof providers.google;

View File

@@ -0,0 +1,5 @@
import { providers } from "unifont";
const provider = providers.google;
export {
provider
};

View File

@@ -0,0 +1,48 @@
import type { providers } from 'unifont';
import type { AstroFontProvider } from '../types.js';
/** [Adobe](https://fonts.adobe.com/) */
declare function adobe(config: Parameters<typeof providers.adobe>[0]): {
entrypoint: string | URL;
config?: Record<string, any> | undefined;
};
/** [Bunny](https://fonts.bunny.net/) */
declare function bunny(): {
entrypoint: string | URL;
config?: Record<string, any> | undefined;
};
/** [Fontshare](https://www.fontshare.com/) */
declare function fontshare(): {
entrypoint: string | URL;
config?: Record<string, any> | undefined;
};
/** [Fontsource](https://fontsource.org/) */
declare function fontsource(): {
entrypoint: string | URL;
config?: Record<string, any> | undefined;
};
/** [Google](https://fonts.google.com/) */
declare function google(config?: Parameters<typeof providers.google>[0]): {
entrypoint: string | URL;
config?: Record<string, any> | undefined;
};
/**
* Astro re-exports most [unifont](https://github.com/unjs/unifont/) 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/)
*/
export declare const fontProviders: {
adobe: typeof adobe;
bunny: typeof bunny;
fontshare: typeof fontshare;
fontsource: typeof fontsource;
google: typeof google;
};
/** A type helper for defining Astro font providers config objects */
export declare function defineAstroFontProvider(provider: AstroFontProvider): {
entrypoint: string | URL;
config?: Record<string, any> | undefined;
};
export {};

View File

@@ -0,0 +1,41 @@
function adobe(config) {
return defineAstroFontProvider({
entrypoint: "astro/assets/fonts/providers/adobe",
config
});
}
function bunny() {
return defineAstroFontProvider({
entrypoint: "astro/assets/fonts/providers/bunny"
});
}
function fontshare() {
return defineAstroFontProvider({
entrypoint: "astro/assets/fonts/providers/fontshare"
});
}
function fontsource() {
return defineAstroFontProvider({
entrypoint: "astro/assets/fonts/providers/fontsource"
});
}
function google(config) {
return defineAstroFontProvider({
entrypoint: "astro/assets/fonts/providers/google",
config
});
}
const fontProviders = {
adobe,
bunny,
fontshare,
fontsource,
google
};
function defineAstroFontProvider(provider) {
return provider;
}
export {
defineAstroFontProvider,
fontProviders
};

View File

@@ -0,0 +1,13 @@
import type * as unifont from 'unifont';
import type { FontFileReader, FontTypeExtractor, UrlProxy } from '../definitions.js';
import type { ResolvedLocalFontFamily } from '../types.js';
interface Options {
family: ResolvedLocalFontFamily;
urlProxy: UrlProxy;
fontTypeExtractor: FontTypeExtractor;
fontFileReader: FontFileReader;
}
export declare function resolveLocalFont({ family, urlProxy, fontTypeExtractor, fontFileReader, }: Options): {
fonts: Array<unifont.FontFaceData>;
};
export {};

View File

@@ -0,0 +1,53 @@
import { FONT_FORMATS } from "../constants.js";
function resolveLocalFont({
family,
urlProxy,
fontTypeExtractor,
fontFileReader
}) {
return {
fonts: family.variants.map((variant) => {
const shouldInfer = variant.weight === void 0 || variant.style === void 0;
const data = {
// 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
};
data.src = variant.src.map((source, index) => {
if (shouldInfer && index === 0) {
const result = fontFileReader.extract({ family: family.name, url: source.url });
if (variant.weight === void 0) data.weight = result.weight;
if (variant.style === void 0) data.style = result.style;
}
const type = fontTypeExtractor.extract(source.url);
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
},
init: null
}),
format: FONT_FORMATS.find((e) => e.type === type)?.format,
tech: source.tech
};
});
return data;
})
};
}
export {
resolveLocalFont
};

2
node_modules/astro/dist/assets/fonts/sync.d.ts generated vendored Normal file
View File

@@ -0,0 +1,2 @@
import type { AstroSettings } from '../../types/astro.js';
export declare function syncFonts(settings: AstroSettings): void;

17
node_modules/astro/dist/assets/fonts/sync.js generated vendored Normal file
View File

@@ -0,0 +1,17 @@
import { FONTS_TYPES_FILE } from "./constants.js";
function syncFonts(settings) {
if (!settings.config.experimental.fonts) {
return;
}
settings.injectedTypes.push({
filename: FONTS_TYPES_FILE,
content: `declare module 'astro:assets' {
/** @internal */
export type FontFamily = (${JSON.stringify(settings.config.experimental.fonts.map((family) => family.cssVariable))})[number];
}
`
});
}
export {
syncFonts
};

76
node_modules/astro/dist/assets/fonts/types.d.ts generated vendored Normal file
View File

@@ -0,0 +1,76 @@
import type { Font } from '@capsizecss/unpack';
import type * as unifont from 'unifont';
import type { z } from 'zod';
import type { fontProviderSchema, localFontFamilySchema, remoteFontFamilySchema, styleSchema } from './config.js';
import type { FONT_TYPES, GENERIC_FALLBACK_NAMES } from './constants.js';
import type { CollectedFontForMetrics } from './logic/optimize-fallbacks.js';
export type AstroFontProvider = z.infer<typeof fontProviderSchema>;
export interface ResolvedFontProvider {
name?: string;
provider: (config?: Record<string, any>) => unifont.Provider;
config?: Record<string, any>;
}
export type LocalFontFamily = z.infer<typeof localFontFamilySchema>;
interface ResolvedFontFamilyAttributes {
nameWithHash: string;
}
export interface ResolvedLocalFontFamily extends ResolvedFontFamilyAttributes, Omit<LocalFontFamily, 'variants'> {
variants: Array<Omit<LocalFontFamily['variants'][number], 'weight' | 'src'> & {
weight?: string;
src: Array<{
url: string;
tech?: string;
}>;
}>;
}
type RemoteFontFamily = z.infer<typeof remoteFontFamilySchema>;
/** @lintignore somehow required by pickFontFaceProperty in utils */
export interface ResolvedRemoteFontFamily extends ResolvedFontFamilyAttributes, Omit<z.output<typeof remoteFontFamilySchema>, 'provider' | 'weights'> {
provider: ResolvedFontProvider;
weights?: Array<string>;
}
export type FontFamily = LocalFontFamily | RemoteFontFamily;
export type ResolvedFontFamily = ResolvedLocalFontFamily | ResolvedRemoteFontFamily;
export type FontType = (typeof FONT_TYPES)[number];
/**
* Preload data is used for links generation inside the <Font /> component
*/
export interface PreloadData {
/**
* Absolute link to a font file, eg. /_astro/fonts/abc.woff
*/
url: string;
/**
* A font type, eg. woff2, woff, ttf...
*/
type: FontType;
}
export type FontFaceMetrics = Pick<Font, 'ascent' | 'descent' | 'lineGap' | 'unitsPerEm' | 'xWidthAvg'>;
export type GenericFallbackName = (typeof GENERIC_FALLBACK_NAMES)[number];
export type Defaults = Partial<Pick<ResolvedRemoteFontFamily, 'weights' | 'styles' | 'subsets' | 'fallbacks' | 'optimizedFallbacks'>>;
export interface FontFileData {
hash: string;
url: string;
init: RequestInit | null;
}
export interface CreateUrlProxyParams {
local: boolean;
hasUrl: (hash: string) => boolean;
saveUrl: (input: FontFileData) => void;
savePreload: (preload: PreloadData) => void;
saveFontData: (collected: CollectedFontForMetrics) => void;
}
/**
* Holds associations of hash and original font file URLs, so they can be
* downloaded whenever the hash is requested.
*/
export type FontFileDataMap = Map<FontFileData['hash'], Pick<FontFileData, 'url' | 'init'>>;
/**
* Holds associations of CSS variables and preloadData/css to be passed to the virtual module.
*/
export type ConsumableMap = Map<string, {
preloadData: Array<PreloadData>;
css: string;
}>;
export type Style = z.output<typeof styleSchema>;
export {};

0
node_modules/astro/dist/assets/fonts/types.js generated vendored Normal file
View File

27
node_modules/astro/dist/assets/fonts/utils.d.ts generated vendored Normal file
View File

@@ -0,0 +1,27 @@
import type * as unifont from 'unifont';
import type { Storage } from 'unstorage';
import type { CssProperties } from './definitions.js';
import type { FontType, GenericFallbackName, ResolvedFontFamily } from './types.js';
/**
* Turns unifont font face data into generic CSS properties, to be consumed by the CSS renderer.
*/
export declare function unifontFontFaceDataToProperties(font: Partial<unifont.FontFaceData>): CssProperties;
/**
* Turns unifont font face data src into a valid CSS property.
* Adapted from https://github.com/nuxt/fonts/blob/main/src/css/render.ts#L68-L81
*/
export declare function renderFontSrc(sources: Exclude<unifont.FontFaceData['src'][number], string>[]): string;
/**
* Removes the quotes from a string. Used for family names
*/
export declare function withoutQuotes(str: string): string;
export declare function isFontType(str: string): str is FontType;
export declare function cache(storage: Storage, key: string, cb: () => Promise<Buffer>): Promise<Buffer>;
export declare function isGenericFontFamily(str: string): str is GenericFallbackName;
export declare function dedupe<const T extends Array<any>>(arr: T): T;
export declare function sortObjectByKey<T extends Record<string, any>>(unordered: T): T;
export declare function resolveEntrypoint(root: URL, entrypoint: string): URL;
export declare function pickFontFaceProperty<T extends keyof Pick<unifont.FontFaceData, 'display' | 'unicodeRange' | 'stretch' | 'featureSettings' | 'variationSettings'>>(property: T, { data, family }: {
data: unifont.FontFaceData;
family: ResolvedFontFamily;
}): import("./types.js").ResolvedRemoteFontFamily[T] | NonNullable<unifont.FontFaceData[T]> | undefined;

83
node_modules/astro/dist/assets/fonts/utils.js generated vendored Normal file
View File

@@ -0,0 +1,83 @@
import { createRequire } from "node:module";
import { pathToFileURL } from "node:url";
import { FONT_TYPES, GENERIC_FALLBACK_NAMES, LOCAL_PROVIDER_NAME } from "./constants.js";
function unifontFontFaceDataToProperties(font) {
return {
src: font.src ? renderFontSrc(font.src) : void 0,
"font-display": font.display ?? "swap",
"unicode-range": font.unicodeRange?.length ? font.unicodeRange.join(",") : void 0,
"font-weight": Array.isArray(font.weight) ? font.weight.join(" ") : font.weight?.toString(),
"font-style": font.style,
"font-stretch": font.stretch,
"font-feature-settings": font.featureSettings,
"font-variation-settings": font.variationSettings
};
}
function renderFontSrc(sources) {
return sources.map((src) => {
if ("name" in src) {
return `local("${src.name}")`;
}
let rendered = `url("${src.url}")`;
if (src.format) {
rendered += ` format("${src.format}")`;
}
if (src.tech) {
rendered += ` tech(${src.tech})`;
}
return rendered;
}).join(", ");
}
const QUOTES_RE = /^["']|["']$/g;
function withoutQuotes(str) {
return str.trim().replace(QUOTES_RE, "");
}
function isFontType(str) {
return FONT_TYPES.includes(str);
}
async function cache(storage, key, cb) {
const existing = await storage.getItemRaw(key);
if (existing) {
return existing;
}
const data = await cb();
await storage.setItemRaw(key, data);
return data;
}
function isGenericFontFamily(str) {
return GENERIC_FALLBACK_NAMES.includes(str);
}
function dedupe(arr) {
return [...new Set(arr)];
}
function sortObjectByKey(unordered) {
const ordered = Object.keys(unordered).sort().reduce((obj, key) => {
const value = unordered[key];
obj[key] = Array.isArray(value) ? value.map((v) => typeof v === "object" && v !== null ? sortObjectByKey(v) : v) : typeof value === "object" && value !== null ? sortObjectByKey(value) : value;
return obj;
}, {});
return ordered;
}
function resolveEntrypoint(root, entrypoint) {
const require2 = createRequire(root);
try {
return pathToFileURL(require2.resolve(entrypoint));
} catch {
return new URL(entrypoint, root);
}
}
function pickFontFaceProperty(property, { data, family }) {
return data[property] ?? (family.provider === LOCAL_PROVIDER_NAME ? void 0 : family[property]);
}
export {
cache,
dedupe,
isFontType,
isGenericFontFamily,
pickFontFaceProperty,
renderFontSrc,
resolveEntrypoint,
sortObjectByKey,
unifontFontFaceDataToProperties,
withoutQuotes
};

View File

@@ -0,0 +1,10 @@
import type { Plugin } from 'vite';
import type { Logger } from '../../core/logger/core.js';
import type { AstroSettings } from '../../types/astro.js';
interface Options {
settings: AstroSettings;
sync: boolean;
logger: Logger;
}
export declare function fontsPlugin({ settings, sync, logger }: Options): Plugin;
export {};

View File

@@ -0,0 +1,252 @@
import { mkdirSync, writeFileSync } from "node:fs";
import { readFile } from "node:fs/promises";
import { isAbsolute } from "node:path";
import { fileURLToPath } from "node:url";
import { collectErrorMetadata } from "../../core/errors/dev/utils.js";
import { AstroError, AstroErrorData, isAstroError } from "../../core/errors/index.js";
import { formatErrorMessage } from "../../core/messages.js";
import { appendForwardSlash, joinPaths, prependForwardSlash } from "../../core/path.js";
import { getClientOutputDirectory } from "../../prerender/utils.js";
import {
ASSETS_DIR,
CACHE_DIR,
DEFAULTS,
RESOLVED_VIRTUAL_MODULE_ID,
VIRTUAL_MODULE_ID
} from "./constants.js";
import { createMinifiableCssRenderer } from "./implementations/css-renderer.js";
import { createDataCollector } from "./implementations/data-collector.js";
import { createAstroErrorHandler } from "./implementations/error-handler.js";
import { createCachedFontFetcher } from "./implementations/font-fetcher.js";
import { createFontaceFontFileReader } from "./implementations/font-file-reader.js";
import { createCapsizeFontMetricsResolver } from "./implementations/font-metrics-resolver.js";
import { createFontTypeExtractor } from "./implementations/font-type-extractor.js";
import { createXxHasher } from "./implementations/hasher.js";
import { createRequireLocalProviderUrlResolver } from "./implementations/local-provider-url-resolver.js";
import {
createBuildRemoteFontProviderModResolver,
createDevServerRemoteFontProviderModResolver
} from "./implementations/remote-font-provider-mod-resolver.js";
import { createRemoteFontProviderResolver } from "./implementations/remote-font-provider-resolver.js";
import { createFsStorage } from "./implementations/storage.js";
import { createSystemFallbacksProvider } from "./implementations/system-fallbacks-provider.js";
import { createUrlProxy } from "./implementations/url-proxy.js";
import {
createLocalUrlProxyContentResolver,
createRemoteUrlProxyContentResolver
} from "./implementations/url-proxy-content-resolver.js";
import { createBuildUrlResolver, createDevUrlResolver } from "./implementations/url-resolver.js";
import { orchestrate } from "./orchestrate.js";
function fontsPlugin({ settings, sync, logger }) {
if (!settings.config.experimental.fonts) {
return {
name: "astro:fonts:fallback",
resolveId(id) {
if (id === VIRTUAL_MODULE_ID) {
return RESOLVED_VIRTUAL_MODULE_ID;
}
},
load(id) {
if (id === RESOLVED_VIRTUAL_MODULE_ID) {
return {
code: ""
};
}
}
};
}
const assetsDir = prependForwardSlash(
appendForwardSlash(joinPaths(settings.config.build.assets, ASSETS_DIR))
);
const baseUrl = joinPaths(settings.config.base, assetsDir);
let fontFileDataMap = null;
let consumableMap = null;
let isBuild;
let fontFetcher = null;
let fontTypeExtractor = null;
const cleanup = () => {
consumableMap = null;
fontFileDataMap = null;
fontFetcher = null;
};
async function initialize({
cacheDir,
modResolver,
cssRenderer,
urlResolver
}) {
const { root } = settings.config;
const hasher = await createXxHasher();
const errorHandler = createAstroErrorHandler();
const remoteFontProviderResolver = createRemoteFontProviderResolver({
root,
modResolver,
errorHandler
});
const pathsToWarn = /* @__PURE__ */ new Set();
const localProviderUrlResolver = createRequireLocalProviderUrlResolver({
root,
intercept: (path) => {
if (path.startsWith(fileURLToPath(settings.config.publicDir))) {
if (pathsToWarn.has(path)) {
return;
}
pathsToWarn.add(path);
logger.warn(
"assets",
`Found a local font file ${JSON.stringify(path)} in the \`public/\` folder. To avoid duplicated files in the build output, move this file into \`src/\``
);
}
}
});
const storage = createFsStorage({ base: cacheDir });
const systemFallbacksProvider = createSystemFallbacksProvider();
fontFetcher = createCachedFontFetcher({ storage, errorHandler, fetch, readFile });
const fontMetricsResolver = createCapsizeFontMetricsResolver({ fontFetcher, cssRenderer });
fontTypeExtractor = createFontTypeExtractor({ errorHandler });
const fontFileReader = createFontaceFontFileReader({ errorHandler });
const res = await orchestrate({
families: settings.config.experimental.fonts,
hasher,
remoteFontProviderResolver,
localProviderUrlResolver,
storage,
cssRenderer,
systemFallbacksProvider,
fontMetricsResolver,
fontTypeExtractor,
fontFileReader,
logger,
createUrlProxy: ({ local, ...params }) => {
const dataCollector = createDataCollector(params);
const contentResolver = local ? createLocalUrlProxyContentResolver({ errorHandler }) : createRemoteUrlProxyContentResolver();
return createUrlProxy({
urlResolver,
contentResolver,
hasher,
dataCollector
});
},
defaults: DEFAULTS
});
fontFileDataMap = res.fontFileDataMap;
consumableMap = res.consumableMap;
}
return {
name: "astro:fonts",
config(_, { command }) {
isBuild = command === "build";
},
async buildStart() {
if (isBuild) {
await initialize({
cacheDir: new URL(CACHE_DIR, settings.config.cacheDir),
modResolver: createBuildRemoteFontProviderModResolver(),
cssRenderer: createMinifiableCssRenderer({ minify: true }),
urlResolver: createBuildUrlResolver({
base: baseUrl,
assetsPrefix: settings.config.build.assetsPrefix
})
});
}
},
async configureServer(server) {
await initialize({
// In dev, we cache fonts data in .astro so it can be easily inspected and cleared
cacheDir: new URL(CACHE_DIR, settings.dotAstroDir),
modResolver: createDevServerRemoteFontProviderModResolver({ server }),
cssRenderer: createMinifiableCssRenderer({ minify: false }),
urlResolver: createDevUrlResolver({ base: baseUrl })
});
const localPaths = [...fontFileDataMap.values()].filter(({ url }) => isAbsolute(url)).map((v) => v.url);
server.watcher.on("change", (path) => {
if (localPaths.includes(path)) {
logger.info("assets", "Font file updated");
server.restart();
}
});
server.watcher.on("unlink", (path) => {
if (localPaths.includes(path)) {
logger.warn(
"assets",
`The font file ${JSON.stringify(path)} referenced in your config has been deleted. Restore the file or remove this font from your configuration if it is no longer needed.`
);
}
});
server.middlewares.use(assetsDir, async (req, res, next) => {
if (!req.url) {
return next();
}
const hash = req.url.slice(1);
const associatedData = fontFileDataMap?.get(hash);
if (!associatedData) {
return next();
}
res.setHeader("Cache-Control", "no-store, no-cache, must-revalidate, max-age=0");
res.setHeader("Pragma", "no-cache");
res.setHeader("Expires", 0);
try {
const data = await fontFetcher.fetch({ hash, ...associatedData });
res.setHeader("Content-Length", data.length);
res.setHeader("Content-Type", `font/${fontTypeExtractor.extract(hash)}`);
res.end(data);
} catch (err) {
logger.error("assets", "Cannot download font file");
if (isAstroError(err)) {
logger.error(
"SKIP_FORMAT",
formatErrorMessage(collectErrorMetadata(err), logger.level() === "debug")
);
}
res.statusCode = 500;
res.end();
}
});
},
resolveId(id) {
if (id === VIRTUAL_MODULE_ID) {
return RESOLVED_VIRTUAL_MODULE_ID;
}
},
load(id) {
if (id === RESOLVED_VIRTUAL_MODULE_ID) {
return {
code: `export const fontsData = new Map(${JSON.stringify(Array.from(consumableMap?.entries() ?? []))})`
};
}
},
async buildEnd() {
if (sync || settings.config.experimental.fonts.length === 0) {
cleanup();
return;
}
try {
const dir = getClientOutputDirectory(settings);
const fontsDir = new URL(`.${assetsDir}`, dir);
try {
mkdirSync(fontsDir, { recursive: true });
} catch (cause) {
throw new AstroError(AstroErrorData.UnknownFilesystemError, { cause });
}
if (fontFileDataMap) {
logger.info("assets", "Copying fonts...");
await Promise.all(
Array.from(fontFileDataMap.entries()).map(async ([hash, associatedData]) => {
const data = await fontFetcher.fetch({ hash, ...associatedData });
try {
writeFileSync(new URL(hash, fontsDir), data);
} catch (cause) {
throw new AstroError(AstroErrorData.UnknownFilesystemError, { cause });
}
})
);
}
} finally {
cleanup();
}
}
};
}
export {
fontsPlugin
};

View File

@@ -1,4 +1,4 @@
import type { AstroConfig } from '../@types/astro.js';
import type { AstroConfig } from '../types/public/config.js';
import { type ImageService } from './services/service.js';
import { type GetImageResult, type UnresolvedImageTransform } from './types.js';
export declare function getConfiguredImageService(): Promise<ImageService>;

View File

@@ -1,10 +1,17 @@
import { isRemotePath } from "@astrojs/internal-helpers/path";
import { AstroError, AstroErrorData } from "../core/errors/index.js";
import { DEFAULT_HASH_PROPS } from "./consts.js";
import {
DEFAULT_RESOLUTIONS,
getSizesAttribute,
getWidths,
LIMITED_RESOLUTIONS
} from "./layout.js";
import { isLocalService } from "./services/service.js";
import {
isImageMetadata
} from "./types.js";
import { addCSSVarsToStyle, cssFitValues } from "./utils/imageAttributes.js";
import { isESMImportedImage, isRemoteImage, resolveSrc } from "./utils/imageKind.js";
import { inferRemoteSize } from "./utils/remoteProbe.js";
async function getConfiguredImageService() {
@@ -48,10 +55,14 @@ async function getImage(options, imageConfig) {
...options,
src: await resolveSrc(options.src)
};
let originalWidth;
let originalHeight;
if (options.inferSize && isRemoteImage(resolvedOptions.src) && isRemotePath(resolvedOptions.src)) {
const result = await inferRemoteSize(resolvedOptions.src);
resolvedOptions.width ??= result.width;
resolvedOptions.height ??= result.height;
originalWidth = result.width;
originalHeight = result.height;
delete resolvedOptions.inferSize;
}
const originalFilePath = isESMImportedImage(resolvedOptions.src) ? resolvedOptions.src.fsPath : void 0;
@@ -59,17 +70,64 @@ async function getImage(options, imageConfig) {
// @ts-expect-error - clone is a private, hidden prop
resolvedOptions.src.clone ?? resolvedOptions.src
) : resolvedOptions.src;
if (isESMImportedImage(clonedSrc)) {
originalWidth = clonedSrc.width;
originalHeight = clonedSrc.height;
}
if (originalWidth && originalHeight) {
const aspectRatio = originalWidth / originalHeight;
if (resolvedOptions.height && !resolvedOptions.width) {
resolvedOptions.width = Math.round(resolvedOptions.height * aspectRatio);
} else if (resolvedOptions.width && !resolvedOptions.height) {
resolvedOptions.height = Math.round(resolvedOptions.width / aspectRatio);
} else if (!resolvedOptions.width && !resolvedOptions.height) {
resolvedOptions.width = originalWidth;
resolvedOptions.height = originalHeight;
}
}
resolvedOptions.src = clonedSrc;
const layout = options.layout ?? imageConfig.layout ?? "none";
if (resolvedOptions.priority) {
resolvedOptions.loading ??= "eager";
resolvedOptions.decoding ??= "sync";
resolvedOptions.fetchpriority ??= "high";
delete resolvedOptions.priority;
} else {
resolvedOptions.loading ??= "lazy";
resolvedOptions.decoding ??= "async";
resolvedOptions.fetchpriority ??= "auto";
}
if (layout !== "none") {
resolvedOptions.widths ||= getWidths({
width: resolvedOptions.width,
layout,
originalWidth,
breakpoints: imageConfig.breakpoints?.length ? imageConfig.breakpoints : isLocalService(service) ? LIMITED_RESOLUTIONS : DEFAULT_RESOLUTIONS
});
resolvedOptions.sizes ||= getSizesAttribute({ width: resolvedOptions.width, layout });
delete resolvedOptions.densities;
resolvedOptions.style = addCSSVarsToStyle(
{
fit: cssFitValues.includes(resolvedOptions.fit ?? "") && resolvedOptions.fit,
pos: resolvedOptions.position
},
resolvedOptions.style
);
resolvedOptions["data-astro-image"] = layout;
}
const validatedOptions = service.validateOptions ? await service.validateOptions(resolvedOptions, imageConfig) : resolvedOptions;
const srcSetTransforms = service.getSrcSet ? await service.getSrcSet(validatedOptions, imageConfig) : [];
let imageURL = await service.getURL(validatedOptions, imageConfig);
const matchesValidatedTransform = (transform) => transform.width === validatedOptions.width && transform.height === validatedOptions.height && transform.format === validatedOptions.format;
let srcSets = await Promise.all(
srcSetTransforms.map(async (srcSet) => ({
transform: srcSet.transform,
url: await service.getURL(srcSet.transform, imageConfig),
descriptor: srcSet.descriptor,
attributes: srcSet.attributes
}))
srcSetTransforms.map(async (srcSet) => {
return {
transform: srcSet.transform,
url: matchesValidatedTransform(srcSet.transform) ? imageURL : await service.getURL(srcSet.transform, imageConfig),
descriptor: srcSet.descriptor,
attributes: srcSet.attributes
};
})
);
if (isLocalService(service) && globalThis.astroAsset.addStaticImage && !(isRemoteImage(validatedOptions.src) && imageURL === validatedOptions.src)) {
const propsToHash = service.propertiesToHash ?? DEFAULT_HASH_PROPS;
@@ -78,12 +136,14 @@ async function getImage(options, imageConfig) {
propsToHash,
originalFilePath
);
srcSets = srcSetTransforms.map((srcSet) => ({
transform: srcSet.transform,
url: globalThis.astroAsset.addStaticImage(srcSet.transform, propsToHash, originalFilePath),
descriptor: srcSet.descriptor,
attributes: srcSet.attributes
}));
srcSets = srcSetTransforms.map((srcSet) => {
return {
transform: srcSet.transform,
url: matchesValidatedTransform(srcSet.transform) ? imageURL : globalThis.astroAsset.addStaticImage(srcSet.transform, propsToHash, originalFilePath),
descriptor: srcSet.descriptor,
attributes: srcSet.attributes
};
});
}
return {
rawOptions: resolvedOptions,

25
node_modules/astro/dist/assets/layout.d.ts generated vendored Normal file
View File

@@ -0,0 +1,25 @@
import type { ImageLayout } from './types.js';
export declare const DEFAULT_RESOLUTIONS: number[];
export declare const LIMITED_RESOLUTIONS: number[];
/**
* Gets the breakpoints for an image, based on the layout and width
*
* The rules are as follows:
*
* - For full-width layout we return all breakpoints smaller than the original image width
* - For fixed layout we return 1x and 2x the requested width, unless the original image is smaller than that.
* - For responsive layout we return all breakpoints smaller than 2x the requested width, unless the original image is smaller than that.
*/
export declare const getWidths: ({ width, layout, breakpoints, originalWidth, }: {
width?: number;
layout: ImageLayout;
breakpoints?: Array<number>;
originalWidth?: number;
}) => Array<number>;
/**
* Gets the `sizes` attribute for an image, based on the layout and width
*/
export declare const getSizesAttribute: ({ width, layout, }: {
width?: number;
layout?: ImageLayout;
}) => string | undefined;

107
node_modules/astro/dist/assets/layout.js generated vendored Normal file
View File

@@ -0,0 +1,107 @@
const DEFAULT_RESOLUTIONS = [
640,
// older and lower-end phones
750,
// iPhone 6-8
828,
// iPhone XR/11
960,
// older horizontal phones
1080,
// iPhone 6-8 Plus
1280,
// 720p
1668,
// Various iPads
1920,
// 1080p
2048,
// QXGA
2560,
// WQXGA
3200,
// QHD+
3840,
// 4K
4480,
// 4.5K
5120,
// 5K
6016
// 6K
];
const LIMITED_RESOLUTIONS = [
640,
// older and lower-end phones
750,
// iPhone 6-8
828,
// iPhone XR/11
1080,
// iPhone 6-8 Plus
1280,
// 720p
1668,
// Various iPads
2048,
// QXGA
2560
// WQXGA
];
const getWidths = ({
width,
layout,
breakpoints = DEFAULT_RESOLUTIONS,
originalWidth
}) => {
const smallerThanOriginal = (w) => !originalWidth || w <= originalWidth;
if (layout === "full-width") {
return breakpoints.filter(smallerThanOriginal);
}
if (!width) {
return [];
}
const doubleWidth = width * 2;
const maxSize = originalWidth ? Math.min(doubleWidth, originalWidth) : doubleWidth;
if (layout === "fixed") {
return originalWidth && width > originalWidth ? [originalWidth] : [width, maxSize];
}
if (layout === "constrained") {
return [
// Always include the image at 1x and 2x the specified width
width,
doubleWidth,
...breakpoints
].filter((w) => w <= maxSize).sort((a, b) => a - b);
}
return [];
};
const getSizesAttribute = ({
width,
layout
}) => {
if (!width || !layout) {
return void 0;
}
switch (layout) {
// If screen is wider than the max size then image width is the max size,
// otherwise it's the width of the screen
case "constrained":
return `(min-width: ${width}px) ${width}px, 100vw`;
// Image is always the same width, whatever the size of the screen
case "fixed":
return `${width}px`;
// Image is always the width of the screen
case "full-width":
return `100vw`;
case "none":
default:
return void 0;
}
};
export {
DEFAULT_RESOLUTIONS,
LIMITED_RESOLUTIONS,
getSizesAttribute,
getWidths
};

10
node_modules/astro/dist/assets/runtime.d.ts generated vendored Normal file
View File

@@ -0,0 +1,10 @@
import type { ImageMetadata } from './types.js';
export interface SvgComponentProps {
meta: ImageMetadata;
attributes: Record<string, string>;
children: string;
}
export declare function createSvgComponent({ meta, attributes, children }: SvgComponentProps): import("../runtime/server/index.js").AstroComponentFactory & ImageMetadata;
type SvgAttributes = Record<string, any>;
export declare function dropAttributes(attributes: SvgAttributes): SvgAttributes;
export {};

43
node_modules/astro/dist/assets/runtime.js generated vendored Normal file
View File

@@ -0,0 +1,43 @@
import {
createComponent,
render,
spreadAttributes,
unescapeHTML
} from "../runtime/server/index.js";
function createSvgComponent({ meta, attributes, children }) {
const Component = createComponent((_, props) => {
const normalizedProps = normalizeProps(attributes, props);
return render`<svg${spreadAttributes(normalizedProps)}>${unescapeHTML(children)}</svg>`;
});
if (import.meta.env.DEV) {
makeNonEnumerable(Component);
Object.defineProperty(Component, Symbol.for("nodejs.util.inspect.custom"), {
value: (_, opts, inspect) => inspect(meta, opts)
});
}
Object.defineProperty(Component, "toJSON", {
value: () => meta,
enumerable: false
});
return Object.assign(Component, meta);
}
const ATTRS_TO_DROP = ["xmlns", "xmlns:xlink", "version"];
const DEFAULT_ATTRS = {};
function dropAttributes(attributes) {
for (const attr of ATTRS_TO_DROP) {
delete attributes[attr];
}
return attributes;
}
function normalizeProps(attributes, props) {
return dropAttributes({ ...DEFAULT_ATTRS, ...attributes, ...props });
}
function makeNonEnumerable(object) {
for (const property in object) {
Object.defineProperty(object, property, { enumerable: false });
}
}
export {
createSvgComponent,
dropAttributes
};

View File

@@ -1,5 +1,5 @@
import type { AstroConfig } from '../../@types/astro.js';
import type { ImageOutputFormat, ImageTransform, UnresolvedSrcSetValue } from '../types.js';
import type { AstroConfig } from '../../types/public/config.js';
import type { ImageFit, ImageOutputFormat, ImageTransform, UnresolvedSrcSetValue } from '../types.js';
export type ImageService = LocalImageService | ExternalImageService;
export declare function isLocalService(service: ImageService | undefined): service is LocalImageService;
export declare function parseQuality(quality: string): string | number;
@@ -13,7 +13,7 @@ interface SharedServiceProps<T extends Record<string, any> = Record<string, any>
/**
* Return the URL to the endpoint or URL your images are generated from.
*
* For a local service, your service should expose an endpoint handling the image requests, or use Astro's at `/_image`.
* For a local service, your service should expose an endpoint handling the image requests, or use Astro's which by default, is located at `/_image`.
*
* For external services, this should point to the URL your images are coming from, for instance, `/_vercel/image`
*
@@ -44,7 +44,7 @@ interface SharedServiceProps<T extends Record<string, any> = Record<string, any>
validateOptions?: (options: ImageTransform, imageConfig: ImageConfig<T>) => ImageTransform | Promise<ImageTransform>;
}
export type ExternalImageService<T extends Record<string, any> = Record<string, any>> = SharedServiceProps<T>;
export type LocalImageTransform = {
type LocalImageTransform = {
src: string;
[key: string]: any;
};
@@ -76,6 +76,8 @@ export type BaseServiceTransform = {
height?: number;
format: string;
quality?: string | null;
fit?: ImageFit;
position?: string;
};
/**
* Basic local service using the included `_image` endpoint.

View File

@@ -1,8 +1,8 @@
import { isRemoteAllowed } from "@astrojs/internal-helpers/remote";
import { AstroError, AstroErrorData } from "../../core/errors/index.js";
import { isRemotePath, joinPaths } from "../../core/path.js";
import { DEFAULT_HASH_PROPS, DEFAULT_OUTPUT_FORMAT, VALID_SUPPORTED_FORMATS } from "../consts.js";
import { isESMImportedImage } from "../utils/imageKind.js";
import { isRemoteAllowed } from "../utils/remotePattern.js";
import { isESMImportedImage, isRemoteImage } from "../utils/imageKind.js";
function isLocalService(service) {
if (!service) {
return false;
@@ -16,10 +16,11 @@ function parseQuality(quality) {
}
return result;
}
const sortNumeric = (a, b) => a - b;
const baseService = {
propertiesToHash: DEFAULT_HASH_PROPS,
validateOptions(options) {
if (!options.src || typeof options.src !== "string" && typeof options.src !== "object") {
if (!options.src || !isRemoteImage(options.src) && !isESMImportedImage(options.src)) {
throw new AstroError({
...AstroErrorData.ExpectedImage,
message: AstroErrorData.ExpectedImage.message(
@@ -76,11 +77,32 @@ const baseService = {
}
if (options.width) options.width = Math.round(options.width);
if (options.height) options.height = Math.round(options.height);
if (options.layout && options.width && options.height) {
options.fit ??= "cover";
delete options.layout;
}
if (options.fit === "none") {
delete options.fit;
}
return options;
},
getHTMLAttributes(options) {
const { targetWidth, targetHeight } = getTargetDimensions(options);
const { src, width, height, format, quality, densities, widths, formats, ...attributes } = options;
const {
src,
width,
height,
format,
quality,
densities,
widths,
formats,
layout,
priority,
fit,
position,
...attributes
} = options;
return {
...attributes,
width: targetWidth,
@@ -90,22 +112,28 @@ const baseService = {
};
},
getSrcSet(options) {
const srcSet = [];
const { targetWidth } = getTargetDimensions(options);
const { targetWidth, targetHeight } = getTargetDimensions(options);
const aspectRatio = targetWidth / targetHeight;
const { widths, densities } = options;
const targetFormat = options.format ?? DEFAULT_OUTPUT_FORMAT;
let transformedWidths = (widths ?? []).sort(sortNumeric);
let imageWidth = options.width;
let maxWidth = Infinity;
if (isESMImportedImage(options.src)) {
imageWidth = options.src.width;
maxWidth = imageWidth;
if (transformedWidths.length > 0 && transformedWidths.at(-1) > maxWidth) {
transformedWidths = transformedWidths.filter((width) => width <= maxWidth);
transformedWidths.push(maxWidth);
}
}
transformedWidths = Array.from(new Set(transformedWidths));
const {
width: transformWidth,
height: transformHeight,
...transformWithoutDimensions
} = options;
const allWidths = [];
let allWidths = [];
if (densities) {
const densityValues = densities.map((density) => {
if (typeof density === "number") {
@@ -114,40 +142,28 @@ const baseService = {
return parseFloat(density);
}
});
const densityWidths = densityValues.sort().map((density) => Math.round(targetWidth * density));
allWidths.push(
...densityWidths.map((width, index) => ({
maxTargetWidth: Math.min(width, maxWidth),
descriptor: `${densityValues[index]}x`
}))
);
} else if (widths) {
allWidths.push(
...widths.map((width) => ({
maxTargetWidth: Math.min(width, maxWidth),
descriptor: `${width}w`
}))
);
const densityWidths = densityValues.sort(sortNumeric).map((density) => Math.round(targetWidth * density));
allWidths = densityWidths.map((width, index) => ({
width,
descriptor: `${densityValues[index]}x`
}));
} else if (transformedWidths.length > 0) {
allWidths = transformedWidths.map((width) => ({
width,
descriptor: `${width}w`
}));
}
for (const { maxTargetWidth, descriptor } of allWidths) {
const srcSetTransform = { ...transformWithoutDimensions };
if (maxTargetWidth !== imageWidth) {
srcSetTransform.width = maxTargetWidth;
} else {
if (options.width && options.height) {
srcSetTransform.width = options.width;
srcSetTransform.height = options.height;
}
}
srcSet.push({
transform: srcSetTransform,
return allWidths.map(({ width, descriptor }) => {
const height = Math.round(width / aspectRatio);
const transform = { ...transformWithoutDimensions, width, height };
return {
transform,
descriptor,
attributes: {
type: `image/${targetFormat}`
}
});
}
return srcSet;
};
});
},
getURL(options, imageConfig) {
const searchParams = new URLSearchParams();
@@ -162,12 +178,14 @@ const baseService = {
w: "width",
h: "height",
q: "quality",
f: "format"
f: "format",
fit: "fit",
position: "position"
};
Object.entries(params).forEach(([param, key]) => {
options[key] && searchParams.append(param, options[key].toString());
});
const imageEndpoint = joinPaths(import.meta.env.BASE_URL, "/_image");
const imageEndpoint = joinPaths(import.meta.env.BASE_URL, imageConfig.endpoint.route);
return `${imageEndpoint}?${searchParams}`;
},
parseURL(url) {
@@ -180,7 +198,9 @@ const baseService = {
width: params.has("w") ? parseInt(params.get("w")) : void 0,
height: params.has("h") ? parseInt(params.get("h")) : void 0,
format: params.get("f"),
quality: params.get("q")
quality: params.get("q"),
fit: params.get("fit"),
position: params.get("position") ?? void 0
};
return transform;
}

View File

@@ -20,6 +20,15 @@ async function loadSharp() {
sharpImport.cache(false);
return sharpImport;
}
const fitMap = {
fill: "fill",
contain: "inside",
cover: "cover",
none: "outside",
"scale-down": "inside",
outside: "outside",
inside: "inside"
};
const sharpService = {
validateOptions: baseService.validateOptions,
getURL: baseService.getURL,
@@ -36,10 +45,26 @@ const sharpService = {
limitInputPixels: config.service.config.limitInputPixels
});
result.rotate();
if (transform.height && !transform.width) {
result.resize({ height: Math.round(transform.height) });
const withoutEnlargement = Boolean(transform.fit);
if (transform.width && transform.height && transform.fit) {
const fit = fitMap[transform.fit] ?? "inside";
result.resize({
width: Math.round(transform.width),
height: Math.round(transform.height),
fit,
position: transform.position,
withoutEnlargement
});
} else if (transform.height && !transform.width) {
result.resize({
height: Math.round(transform.height),
withoutEnlargement
});
} else if (transform.width) {
result.resize({ width: Math.round(transform.width) });
result.resize({
width: Math.round(transform.width),
withoutEnlargement
});
}
if (transform.format) {
let quality = void 0;
@@ -51,7 +76,17 @@ const sharpService = {
quality = transform.quality in qualityTable ? qualityTable[transform.quality] : void 0;
}
}
result.toFormat(transform.format, { quality });
const isGifInput = inputBuffer[0] === 71 && // 'G'
inputBuffer[1] === 73 && // 'I'
inputBuffer[2] === 70 && // 'F'
inputBuffer[3] === 56 && // '8'
(inputBuffer[4] === 57 || inputBuffer[4] === 55) && // '9' or '7'
inputBuffer[5] === 97;
if (transform.format === "webp" && isGifInput) {
result.webp({ quality: typeof quality === "number" ? quality : void 0, loop: 0 });
} else {
result.toFormat(transform.format, { quality });
}
}
const { data, info } = await result.toBuffer({ resolveWithObject: true });
return {

View File

@@ -1,3 +0,0 @@
import { type LocalImageService } from './service.js';
declare const service: LocalImageService;
export default service;

View File

@@ -1,90 +0,0 @@
import { yellow } from "kleur/colors";
import { imageMetadata } from "../utils/metadata.js";
import {
baseService,
parseQuality
} from "./service.js";
import { processBuffer } from "./vendor/squoosh/image-pool.js";
console.warn(
yellow(
"The Squoosh image service is deprecated and will be removed in Astro 5.x. We suggest migrating to the default Sharp image service instead, as it is faster, more powerful and better maintained."
)
);
const baseQuality = { low: 25, mid: 50, high: 80, max: 100 };
const qualityTable = {
avif: {
// Squoosh's AVIF encoder has a bit of a weird behavior where `62` is technically the maximum, and anything over is overkill
max: 62,
high: 45,
mid: 35,
low: 20
},
jpeg: baseQuality,
jpg: baseQuality,
webp: baseQuality
// Squoosh's PNG encoder does not support a quality setting, so we can skip that here
};
async function getRotationForEXIF(inputBuffer, src) {
const meta = await imageMetadata(inputBuffer, src);
if (!meta) return void 0;
switch (meta.orientation) {
case 3:
case 4:
return { type: "rotate", numRotations: 2 };
case 5:
case 6:
return { type: "rotate", numRotations: 1 };
case 7:
case 8:
return { type: "rotate", numRotations: 3 };
case void 0:
default:
return void 0;
}
}
const service = {
validateOptions: baseService.validateOptions,
getURL: baseService.getURL,
parseURL: baseService.parseURL,
getHTMLAttributes: baseService.getHTMLAttributes,
getSrcSet: baseService.getSrcSet,
async transform(inputBuffer, transformOptions) {
const transform = transformOptions;
let format = transform.format;
if (format === "svg") return { data: inputBuffer, format: "svg" };
const operations = [];
const rotation = await getRotationForEXIF(inputBuffer, transform.src);
if (rotation) {
operations.push(rotation);
}
if (transform.height && !transform.width) {
operations.push({
type: "resize",
height: Math.round(transform.height)
});
} else if (transform.width) {
operations.push({
type: "resize",
width: Math.round(transform.width)
});
}
let quality = void 0;
if (transform.quality) {
const parsedQuality = parseQuality(transform.quality);
if (typeof parsedQuality === "number") {
quality = parsedQuality;
} else {
quality = transform.quality in qualityTable[format] ? qualityTable[format][transform.quality] : void 0;
}
}
const data = await processBuffer(inputBuffer, operations, format, quality);
return {
data: Buffer.from(data),
format
};
}
};
var squoosh_default = service;
export {
squoosh_default as default
};

View File

@@ -1,11 +0,0 @@
var AVIFTune = /* @__PURE__ */ ((AVIFTune2) => {
AVIFTune2[AVIFTune2["auto"] = 0] = "auto";
AVIFTune2[AVIFTune2["psnr"] = 1] = "psnr";
AVIFTune2[AVIFTune2["ssim"] = 2] = "ssim";
return AVIFTune2;
})(AVIFTune || {});
var avif_enc_d_default = moduleFactory;
export {
AVIFTune,
avif_enc_d_default as default
};

View File

@@ -1,2 +0,0 @@
declare var Module: (Module: any) => any;
export default Module;

File diff suppressed because it is too large Load Diff

View File

@@ -1,2 +0,0 @@
declare const _default: Buffer;
export default _default;

File diff suppressed because one or more lines are too long

View File

@@ -1,2 +0,0 @@
declare var Module: (Module: any) => any;
export default Module;

File diff suppressed because it is too large Load Diff

Some files were not shown because too many files have changed in this diff Show More