跳转到内容

图像服务 API

astro:assets 的设计旨在让任何图像优化服务都能轻松地在 Astro 之上构建服务。

Astro 提供两种类型的图像服务:本地服务和外部服务。

  • 本地服务直接在构建时为静态站点处理图像转换,或在开发模式和按需渲染时于运行时处理。这些通常是像 Sharp、ImageMagick 或 Squoosh 这样的库的包装器。在开发模式和按需渲染的生产路由中,本地服务使用 API 端点进行转换。
  • 外部服务指向 URL,可以为 Cloudinary、Vercel 或任何符合 RIAPI 规范的服务器等服务添加支持。

服务定义采用导出的默认对象的形式,其中包含各种必需的方法(“钩子”)。

外部服务提供一个 getURL() 方法,该方法指向输出的 <img> 标签的 src

本地服务提供一个 transform() 方法来对图像执行转换,以及 getURL()parseURL() 方法以在开发模式和按需渲染时使用端点。

两种服务都可以提供 getHTMLAttributes() 来确定输出 <img> 标签的其他属性,以及 validateOptions() 来验证和增强传递的选项。

外部服务指向一个远程 URL,该 URL 将用作最终 <img> 标签的 src 属性。这个远程 URL 负责下载、转换和返回图像。

import type { ExternalImageService, ImageTransform, AstroConfig } from "astro";
const service: ExternalImageService = {
validateOptions(options: ImageTransform, imageConfig: AstroConfig['image']) {
const serviceConfig = imageConfig.service.config;
// Enforce the user set max width.
if (options.width > serviceConfig.maxWidth) {
console.warn(`Image width ${options.width} exceeds max width ${serviceConfig.maxWidth}. Falling back to max width.`);
options.width = serviceConfig.maxWidth;
}
return options;
},
getURL(options, imageConfig) {
return `https://mysupercdn.com/${options.src}?q=${options.quality}&w=${options.width}&h=${options.height}`;
},
getHTMLAttributes(options, imageConfig) {
const { src, format, quality, ...attributes } = options;
return {
...attributes,
loading: options.loading ?? 'lazy',
decoding: options.decoding ?? 'async',
};
}
};
export default service;

要创建你自己的本地服务,你可以指向内置端点/_image),或者你也可以创建自己的端点来调用服务的方法。

import type { LocalImageService, AstroConfig } from "astro";
const service: LocalImageService = {
getURL(options: ImageTransform, imageConfig: AstroConfig['image']) {
const searchParams = new URLSearchParams();
searchParams.append('href', typeof options.src === "string" ? options.src : options.src.src);
options.width && searchParams.append('w', options.width.toString());
options.height && searchParams.append('h', options.height.toString());
options.quality && searchParams.append('q', options.quality.toString());
options.format && searchParams.append('f', options.format);
return `/my_custom_endpoint_that_transforms_images?${searchParams}`;
// Or use the built-in endpoint, which will call your parseURL and transform functions:
// return `/_image?${searchParams}`;
},
parseURL(url: URL, imageConfig) {
return {
src: params.get('href')!,
width: params.has('w') ? parseInt(params.get('w')!) : undefined,
height: params.has('h') ? parseInt(params.get('h')!) : undefined,
format: params.get('f'),
quality: params.get('q'),
};
},
transform(buffer: Uint8Array, options: { src: string, [key: string]: any }, imageConfig): { data: Uint8Array, format: OutputFormat } {
const { buffer } = mySuperLibraryThatEncodesImages(options);
return {
data: buffer,
format: options.format,
};
},
getHTMLAttributes(options, imageConfig) {
let targetWidth = options.width;
let targetHeight = options.height;
if (typeof options.src === "object") {
const aspectRatio = options.src.width / options.src.height;
if (targetHeight && !targetWidth) {
targetWidth = Math.round(targetHeight * aspectRatio);
} else if (targetWidth && !targetHeight) {
targetHeight = Math.round(targetWidth / aspectRatio);
}
}
const { src, width, height, format, quality, ...attributes } = options;
return {
...attributes,
width: targetWidth,
height: targetHeight,
loading: attributes.loading ?? 'lazy',
decoding: attributes.decoding ?? 'async',
};
},
propertiesToHash: ['src', 'width', 'height', 'format', 'quality'],
};
export default service;

在为静态站点和预渲染路由构建时,<Image />getImage(options) 都会调用 transform() 函数。它们分别通过组件属性或 options 参数传递选项。转换后的图像将被构建到 dist/_astro 文件夹中。它们的文件名将包含传递给 propertiesToHash 的属性的哈希值。此属性是可选的,默认为 ['src', 'width', 'height', 'format', 'quality']。如果你的自定义图像服务有更多会改变生成图像的选项,请将它们添加到此数组中。

在开发模式和使用适配器进行按需渲染时,Astro 无法提前知道哪些图像需要优化。Astro 使用一个 GET 端点(默认为 /_image)在运行时处理图像。<Image />getImage() 将其选项传递给 getURL(),该方法将返回端点 URL。然后,端点调用 parseURL() 并将结果属性传递给 transform()

如果你将自己的端点实现为 Astro 端点,你可以使用 getConfiguredImageServiceimageConfig 来调用你的服务的 parseURLtransform 方法,并提供图像配置。

要访问图像服务配置(image.service.config),你可以使用 imageConfig.service.config

src/api/my_custom_endpoint_that_transforms_images.ts
import type { APIRoute } from "astro";
import { getConfiguredImageService, imageConfig } from 'astro:assets';
export const GET: APIRoute = async ({ request }) => {
const imageService = await getConfiguredImageService();
const imageTransform = imageService.parseURL(new URL(request.url), imageConfig);
// ... fetch the image from imageTransform.src and store it in inputBuffer
const { data, format } = await imageService.transform(inputBuffer, imageTransform, imageConfig);
return new Response(data, {
status: 200,
headers: {
'Content-Type': mime.getType(format) || ''
}
}
);
}

有关完整示例,请参阅内置端点

本地和外部服务都需要

getURL(options: ImageTransform, imageConfig: AstroConfig['image']): string

对于本地服务,此钩子返回生成图像的端点的 URL(用于按需渲染和开发模式)。它在构建期间不被使用。`getURL()` 指向的本地端点可以调用 parseURL()transform()

对于外部服务,此钩子返回图像的最终 URL。

对于这两种类型的服务,options 是用户作为 <Image /> 组件的属性或作为 getImage() 的选项传递的属性。它们的类型如下

export type ImageTransform = {
// ESM imported images | remote/public image paths
src: ImageMetadata | string;
width?: number;
height?: number;
widths?: number[] | undefined;
densities?: (number | `${number}x`)[] | undefined;
quality?: ImageQuality;
format?: OutputFormat;
alt?: string;
[key: string]: any;
};

本地服务需要;外部服务不可用

parseURL(url: URL, imageConfig: AstroConfig['image']): { src: string, [key: string]: any}

此钩子将 getURL() 生成的 URL 解析回一个对象,其中包含供 transform 使用的不同属性(用于按需渲染和开发模式)。它在构建期间不被使用。

仅本地服务需要;外部服务不可用

transform(buffer: Uint8Array, options: { src: string, [key: string]: any }, imageConfig: AstroConfig['image']): { data: Uint8Array, format: OutputFormat }

此钩子转换并返回图像,并在构建期间被调用以创建最终的资源文件。

你必须返回一个 format,以确保在按需渲染和开发模式下向用户提供正确的 MIME 类型。

本地和外部服务均可选

getHTMLAttributes(options: ImageTransform, imageConfig: AstroConfig['image']): Record<string, any>

此钩子根据用户传递的参数(options)返回用于将图像呈现为 HTML 的所有附加属性。

添加于: astro@3.3.0

本地和外部服务均可选。

getSrcSet?: (options: ImageTransform, imageConfig: AstroConfig['image']): SrcSetValue[] | Promise<SrcSetValue[]>;

此钩子生成指定图像的多个变体,例如,用于在 <img><picture>source 上生成 srcset 属性。

此钩子返回一个包含以下属性的对象数组

export type SrcSetValue = {
transform: ImageTransform;
descriptor?: string;
attributes?: Record<string, any>;
};

本地和外部服务均可选

validateOptions(options: ImageTransform, imageConfig: AstroConfig['image']): ImageTransform

此钩子允许你验证和增强用户传递的选项。这对于设置默认选项或告知用户某个参数是必需的很有用。

查看 validateOptions() 在 Astro 内置服务中的使用方式.

astro.config.mjs 中配置要使用的图像服务。配置采用以下形式

astro.config.mjs
import { defineConfig } from "astro/config";
export default defineConfig({
image: {
service: {
entrypoint: "your-entrypoint", // 'astro/assets/services/sharp' | string,
config: {
// ... service-specific config. Optional.
}
}
},
});

Astro 暴露了许多可用于开发自定义图像服务的辅助函数。这些工具可以从 astro/assets/utils 导入

import {
isRemoteAllowed,
matchHostname,
matchPathname,
matchPattern,
matchPort,
matchProtocol,
isESMImportedImage,
isRemoteImage,
resolveSrc,
imageMetadata,
emitESMImage,
getOrigQueryParams,
inferRemoteSize,
propsToFilename,
hashTransform
} from "astro/assets/utils";

类型: (src: string, { domains, remotePatterns }: {domains: string[], remotePatterns: RemotePattern[] }): boolean

添加于: `astro@4.0.0`

根据指定的域和远程模式,确定是否允许给定的远程资源(由其源 URL 标识)。

import { isRemoteAllowed } from 'astro/assets/utils';
const testImageURL = 'https://example.com/images/test.jpg';
const domains = ['example.com', 'anotherdomain.com'];
const remotePatterns = [
{ protocol: 'https', hostname: 'images.example.com', pathname: '/**' }, // Allow any path under this hostname
];
const url = new URL(testImageURL);
const isAllowed = isRemoteAllowed(url.href, { domains, remotePatterns });
console.log(`Is the remote image allowed? ${isAllowed}`);

类型: (url: URL, hostname?: string, allowWildcard = false): boolean

添加于: `astro@4.0.0`

将给定 URL 的主机名与指定的主机名进行匹配,并可选地支持通配符模式。

import { matchHostname } from 'astro/assets/utils';
const testURL = new URL('https://sub.example.com/path/to/resource');
// Example usage of matchHostname
const hostnameToMatch = 'example.com';
// Match without wildcard
const isMatchWithoutWildcard = matchHostname(testURL, hostnameToMatch);
console.log(`Does the hostname match without wildcard? ${isMatchWithoutWildcard}`); // Output: false
// Match with wildcard
const isMatchWithWildcard = matchHostname(testURL, hostnameToMatch, true);
console.log(`Does the hostname match with wildcard? ${isMatchWithWildcard}`); // Output: true

类型: (url: URL, pathname?: string, allowWildcard = false): boolean

添加于: `astro@4.0.0`

将给定 URL 的路径名与指定的模式进行匹配,并可选地支持通配符。

import { matchPathname } from 'astro/assets/utils';
const testURL = new URL('https://example.com/images/photo.jpg');
// Example pathname to match
const pathnameToMatch = '/images/photo.jpg';
// Match without wildcard
const isMatchWithoutWildcard = matchPathname(testURL, pathnameToMatch);
console.log(`Does the pathname match without wildcard? ${isMatchWithoutWildcard}`); // Output: true
// Match with wildcard
const wildcardPathname = '/images/*';
const isMatchWithWildcard = matchPathname(testURL, wildcardPathname, true);
console.log(`Does the pathname match with wildcard? ${isMatchWithWildcard}`); // Output: true

类型: (url: URL, remotePattern: RemotePattern): boolean

添加于: `astro@4.0.0`

根据协议、主机名、端口和路径名,评估给定的 URL 是否与指定的远程模式匹配。

import { matchPattern } from 'astro/assets/utils';
const testURL = new URL('https://images.example.com/photos/test.jpg');
// Define a remote pattern to match the URL
const remotePattern = {
protocol: 'https',
hostname: 'images.example.com',
pathname: '/photos/**', // Wildcard to allow all files under /photos/
port: '', // Optional: Match any port or leave empty for default
};
// Check if the URL matches the remote pattern
const isPatternMatched = matchPattern(testURL, remotePattern);
console.log(`Does the URL match the remote pattern? ${isPatternMatched}`); // Output: true

类型: (url: URL, port?: string): boolean

添加于: `astro@4.0.0`

检查给定 URL 的端口是否与指定的端口匹配。如果未提供端口,则返回 true

import { matchPort } from 'astro/assets/utils';
const testURL1 = new URL('https://example.com:8080/resource');
const testURL2 = new URL('https://example.com/resource');
// Example usage of matchPort
const portToMatch = '8080';
// Match a URL with a port specified
const isPortMatch1 = matchPort(testURL1, portToMatch);
console.log(`Does the port match? ${isPortMatch1}`); // Output: true
// Match a URL without a port specified (default port will be assumed)
const isPortMatch2 = matchPort(testURL2, portToMatch);
console.log(`Does the port match? ${isPortMatch2}`); // Output: false
// Check a URL without explicitly providing a port (defaults to true if port is undefined)
const isPortMatch3 = matchPort(testURL1);
console.log(`Does the port match (no port specified)? ${isPortMatch3}`); // Output: true

类型: (url: URL, protocol?: string): boolean

添加于: `astro@4.0.0`

将提供的 URL 的协议与指定的协议进行比较。

import { matchProtocol } from 'astro/assets/utils';
const testURL1 = new URL('https://example.com/resource');
const testURL2 = new URL('http://example.com/resource');
// Example usage of matchProtocol
const protocolToMatch = 'https';
// Match a URL with correct protocol
const isProtocolMatch1 = matchProtocol(testURL1, protocolToMatch);
console.log(`Does the protocol match for testURL1? ${isProtocolMatch1}`); // Output: true
// Match a URL with incorrect protocol
const isProtocolMatch2 = matchProtocol(testURL2, protocolToMatch);
console.log(`Does the protocol match for testURL2? ${isProtocolMatch2}`); // Output: false
// Match a URL without explicitly providing a protocol (defaults to true if protocol is undefined)
const isProtocolMatch3 = matchProtocol(testURL1);
console.log(`Does the protocol match (no protocol specified)? ${isProtocolMatch3}`); // Output: true

类型: (src: ImageMetadata | string): boolean

添加于: `astro@4.0.0`

确定给定的源是否是 ECMAScript 模块(ESM)导入的图像。

import { isESMImportedImage } from 'astro/assets/utils';
// Example usage of isESMImportedImage
const imageMetadataExample = {
src: '/images/photo.jpg',
width: 800,
height: 600,
format: 'jpg',
};
const filePathExample = '/images/photo.jpg';
// Check if the input is an ESM imported image
const isMetadataImage = isESMImportedImage(imageMetadataExample);
console.log(`Is imageMetadataExample an ESM imported image? ${isMetadataImage}`); // Output: true
const isFilePathImage = isESMImportedImage(filePathExample);
console.log(`Is filePathExample an ESM imported image? ${isFilePathImage}`); // Output: false

类型: (src: ImageMetadata | string): boolean

添加于: `astro@4.0.0`

确定提供的源是否是字符串形式的远程图像 URL。

import { isRemoteImage } from 'astro/assets/utils';
// Example usage of isRemoteImage
const remoteImageUrl = 'https://example.com/images/photo.jpg';
const localImageMetadata = {
src: '/images/photo.jpg',
width: 800,
height: 600,
format: 'jpg',
};
// Check if the input is a remote image URL
const isRemote1 = isRemoteImage(remoteImageUrl);
console.log(`Is remoteImageUrl a remote image? ${isRemote1}`); // Output: true
const isRemote2 = isRemoteImage(localImageMetadata);
console.log(`Is localImageMetadata a remote image? ${isRemote2}`); // Output: false

类型: (src: UnresolvedImageTransform['src']): Promise<string | ImageMetadata>

添加于: `astro@4.0.0`

返回图像源。此函数确保如果 src 是一个 Promise(例如,动态 import()),它会被等待并提取正确的 src。如果 src 已经是一个解析过的值,则按原样返回。

import { resolveSrc } from 'astro/assets/utils';
import localImage from "./images/photo.jpg";
const resolvedLocal = await resolveSrc(localImage);
// will be `{ src: '/images/photo.jpg', width: 800, height: 600, format: 'jpg' }`
const resolvedRemote = await resolveSrc("https://example.com/remote-img.jpg");
// will be `"https://example.com/remote-img.jpg"`
const resolvedDynamic = await resolveSrc(import("./images/dynamic-image.jpg"))
// will be `{ src: '/images/dynamic-image.jpg', width: 800, height: 600, format: 'jpg' }`

类型: (data: Uint8Array, src?: string): Promise<Omit<ImageMetadata, 'src' | 'fsPath'>>

添加于: `astro@4.0.0`

从提供的图像数据中提取图像元数据,如尺寸、格式和方向。

import { imageMetadata } from 'astro/assets/utils';
async function extractImageMetadata() {
// Example image data (Uint8Array)
const exampleImageData = new Uint8Array([/* ...binary image data... */]);
// Optional source path (useful for debugging or additional metadata context)
const sourcePath = '/images/photo.jpg';
try {
// Extract metadata from the image data
const metadata = await imageMetadata(exampleImageData, sourcePath);
console.log('Extracted Image Metadata:', metadata);
// Example output:
// {
// width: 800,
// height: 600,
// format: 'jpg',
// orientation: undefined
// }
} catch (error) {
console.error('Failed to extract metadata from image:', error);
}
}
await extractImageMetadata();

类型: (id: string | undefined, _watchMode: boolean, experimentalSvgEnabled: boolean, fileEmitter?: FileEmitter): Promise<ImageMetadataWithContents | undefined>

添加于: `astro@4.0.0`

处理一个图像文件并发出其元数据,并可选择性地发出其内容。在构建模式下,该函数使用 fileEmitter 生成资源引用。在开发模式下,它解析为一个带有元数据查询参数的本地文件 URL。

import { emitESMImage } from 'astro/assets/utils';
const imageId = '/images/photo.jpg';
const unusedWatchMode = false; // Deprecated, unused
const unusedExperimentalSvgEnabled = false; // Set to `true` only if you are using SVG and want the file data to be embedded
try {
const result = await emitESMImage(imageId, unusedWatchMode, unusedExperimentalSvgEnabled);
if (result) {
console.log('Image metadata with contents:', result);
// Example output:
// {
// width: 800,
// height: 600,
// format: 'jpg',
// contents: Uint8Array([...])
// }
} else {
console.log('No metadata was emitted for this image.');
}
} catch (error) {
console.error('Failed to emit ESM image:', error);
}

类型: (id: string | undefined, fileEmitter?: FileEmitter): Promise<ImageMetadataWithContents | undefined>

添加于: astro@5.7.0

处理一个图像文件并发出其元数据,并可选择性地发出其内容。在构建模式下,该函数使用 fileEmitter 生成资源引用。在开发模式下,它解析为一个带有元数据查询参数的本地文件 URL。

import { emitImageMetadata } from 'astro/assets/utils';
const imageId = '/images/photo.jpg';
try {
const result = await emitImageMetadata(imageId);
if (result) {
console.log('Image metadata with contents:', result);
// Example output:
// {
// width: 800,
// height: 600,
// format: 'jpg',
// contents: Uint8Array([...])
// }
} else {
console.log('No metadata was emitted for this image.');
}
} catch (error) {
console.error('Failed to emit ESM image:', error);
}

类型: (params: URLSearchParams): Pick<ImageMetadata, 'width' | 'height' | 'format'> | undefined

添加于: `astro@4.0.0`

URLSearchParams 对象中检索图像的 widthheightformat。如果这些参数中的任何一个缺失或无效,该函数将返回 undefined

import { getOrigQueryParams } from 'astro/assets/utils';
const url = new URL('https://example.com/image.jpg?width=800&height=600&format=jpg');
const queryParams = url.searchParams;
// Extract the original query parameters
const origParams = getOrigQueryParams(queryParams);
if (origParams) {
console.log('Original query parameters:', origParams);
// Example output:
// {
// width: 800,
// height: 600,
// format: 'jpg'
// }
} else {
console.log('Failed to extract original query parameters.');
}

类型: (url: string): Promise<Omit<ImageMetadata, 'src' | 'fsPath'>>

添加于: `astro@4.0.0`

通过流式传输远程图像的数据并逐步分析,直到获得足够的元数据,来推断其尺寸。

import { inferRemoteSize } from 'astro/assets/utils';
async function getRemoteImageSize() {
const remoteImageUrl = 'https://example.com/image.jpg';
try {
// Infer remote image size from the URL
const imageSize = await inferRemoteSize(remoteImageUrl);
console.log('Inferred remote image size:', imageSize);
// Example output:
// {
// width: 1920,
// height: 1080,
// format: 'jpg'
// }
} catch (error) {
console.error('Failed to infer the size of the remote image:', error);
}
}
await getRemoteImageSize();

类型: (filePath: string, transform: ImageTransform, hash: string): string

添加于: `astro@4.0.0`

根据图像的源路径、转换属性和唯一的哈希值,生成一个格式化的文件名。

格式化的文件名遵循以下结构

<prefixDirname>/<baseFilename>_<hash><outputExtension>

  • prefixDirname:如果图像是 ESM 导入的图像,则这是原始文件路径的目录名;否则,它将是一个空字符串。
  • baseFilename:文件的基本名称,或者如果文件是 data: URI,则为哈希后的短名称。
  • hash:为区分转换后的文件而生成的唯一哈希字符串。
  • outputExtension:从 transform.format 或原始文件扩展名派生的所需输出文件扩展名。
import { propsToFilename } from 'astro/assets/utils';
function generateTransformedFilename() {
const filePath = '/images/photo.jpg';
const transform = {
format: 'png',
src: '/images/photo.jpg'
};
const hash = 'abcd1234';
// Generate the transformed filename based on the file path, transformation, and hash
const filename = propsToFilename(filePath, transform, hash);
console.log('Generated transformed filename:', filename);
// Example output: '/images/photo_abcd1234.png'
}
generateTransformedFilename();

类型: (transform: ImageTransform, imageService: string, propertiesToHash: string[]): string

添加于: `astro@4.0.0`

根据选定的属性和指定的 imageService,将提供的 transform 对象转换为哈希字符串。

import { hashTransform } from 'astro/assets/utils';
function generateTransformHash() {
const transform = {
width: 800,
height: 600,
format: 'jpg',
};
const imageService = 'astroImageService';
const propertiesToHash = ['width', 'height', 'format'];
// Generate the hash based on the transform, image service, and properties
const hash = hashTransform(transform, imageService, propertiesToHash);
console.log('Generated transform hash:', hash);
// Example output: 'd41d8cd98f00b204e9800998ecf8427e'
}
generateTransformHash();
贡献 社区 赞助