feat: update with RFC feedback

This commit is contained in:
Princesseuh 2023-09-28 15:39:41 +02:00
parent 3809a452ae
commit 7398d63331
No known key found for this signature in database
GPG key ID: 105BBD6D57F2B0C0
7 changed files with 70 additions and 34 deletions

View file

@ -23,10 +23,11 @@ if (typeof props.height === 'string') {
} }
const image = await getImage(props); const image = await getImage(props);
const additionalAttributes: Record<string, any> = {}; const additionalAttributes: Record<string, any> = {};
if (image.srcSet.length > 0) { if (image.srcSet.values.length > 0) {
additionalAttributes.srcset = image.srcSetValue; additionalAttributes.srcset = image.srcSet.attribute;
} }
--- ---

View file

@ -6,17 +6,22 @@ import { AstroError, AstroErrorData } from '../dist/core/errors/index.js';
import { HTMLAttributes } from '../types'; import { HTMLAttributes } from '../types';
type Props = (LocalImageProps | RemoteImageProps) & { type Props = (LocalImageProps | RemoteImageProps) & {
formats: ImageOutputFormat[]; formats?: ImageOutputFormat[];
fallbackFormat: ImageOutputFormat; fallbackFormat?: ImageOutputFormat;
pictureAttributes: HTMLAttributes<'picture'>; pictureAttributes?: HTMLAttributes<'picture'>;
}; };
const props = Astro.props; const {formats = ["webp"], pictureAttributes = {}, ...props} = Astro.props;
const optimizedImages: Record<
ImageOutputFormat, if (props.alt === undefined || props.alt === null) {
GetImageResult throw new AstroError(AstroErrorData.ImageMissingAlt);
> = await Promise.all( }
props.formats.map(async (format) => await getImage({ ...props, format: format, widths: props.widths, densities: props.densities }))
const optimizedImages: GetImageResult[] = await Promise.all(
formats.map(
async (format) =>
await getImage({ ...props, format: format, widths: props.widths, densities: props.densities })
)
); );
const fallbackFormat = const fallbackFormat =
@ -26,19 +31,24 @@ const fallbackFormat =
: 'png' : 'png'
: 'png'; : 'png';
const originalImage = await getImage({ ...props, format: fallbackFormat, widths: props.widths, densities: props.densities }); const fallbackImage = await getImage({
...props,
format: fallbackFormat,
widths: props.widths,
densities: props.densities,
});
if (props.alt === undefined || props.alt === null) { const additionalAttributes: Record<string, any> = {};
throw new AstroError(AstroErrorData.ImageMissingAlt); if (fallbackImage.srcSet.values.length > 0) {
additionalAttributes.srcset = fallbackImage.srcSet.attribute;
} }
--- ---
<picture {...props.pictureAttributes}> <picture {...pictureAttributes}>
{Object.entries(optimizedImages).map(([format, image]) => ( {
<source Object.entries(optimizedImages).map(([_, image]) => (
srcset={image.srcSetValue} <source srcset={`${image.src}, ` + image.srcSet.attribute} type={image.srcSet.values[0].attributes?.type} />
type={image.srcSet[0].attributes?.type} ))
/> }
))} <img src={fallbackImage.src} {...additionalAttributes} {...fallbackImage.attributes} />
<img src={originalImage.src} {...originalImage.attributes} />
</picture> </picture>

View file

@ -124,8 +124,10 @@ export async function getImage(
rawOptions: resolvedOptions, rawOptions: resolvedOptions,
options: validatedOptions, options: validatedOptions,
src: imageURL, src: imageURL,
srcSet: srcSets, srcSet: {
srcSetValue: srcSets.map((srcSet) => `${srcSet.url} ${srcSet.descriptor}`).join(', '), values: srcSets,
attribute: srcSets.map((srcSet) => `${srcSet.url} ${srcSet.descriptor}`).join(', '),
},
attributes: attributes:
service.getHTMLAttributes !== undefined service.getHTMLAttributes !== undefined
? service.getHTMLAttributes(validatedOptions, imageConfig) ? service.getHTMLAttributes(validatedOptions, imageConfig)

View file

@ -188,8 +188,7 @@ export const baseService: Omit<LocalImageService, 'transform'> = {
} }
if (options.widths && options.densities) { if (options.widths && options.densities) {
console.warn('Cannot use `widths` and `densities` at the same time. Using `densities`.'); throw new AstroError(AstroErrorData.IncompatibleDescriptorOptions);
options.widths = undefined;
} }
// We currently do not support processing SVGs, so whenever the input format is a SVG, force the output to also be one // We currently do not support processing SVGs, so whenever the input format is a SVG, force the output to also be one
@ -226,6 +225,9 @@ export const baseService: Omit<LocalImageService, 'transform'> = {
const targetFormat = options.format ?? DEFAULT_OUTPUT_FORMAT; const targetFormat = options.format ?? DEFAULT_OUTPUT_FORMAT;
const aspectRatio = targetWidth / targetHeight; const aspectRatio = targetWidth / targetHeight;
const imageWidth = isESMImportedImage(options.src) ? options.src.width : options.width;
const maxWidth = options.width ?? imageWidth ?? Infinity;
if (densities) { if (densities) {
const densityValues = densities.map((density) => { const densityValues = densities.map((density) => {
if (typeof density === 'number') { if (typeof density === 'number') {
@ -235,14 +237,17 @@ export const baseService: Omit<LocalImageService, 'transform'> = {
} }
}); });
const densityWidths = densityValues.map((density) => Math.round(targetWidth * density)); const densityWidths = densityValues
.sort()
.map((density) => Math.round(targetWidth * density));
densityWidths.forEach((width, index) => { densityWidths.forEach((width, index) => {
const maxTargetWidth = Math.min(width, maxWidth);
srcSet.push({ srcSet.push({
transform: { transform: {
...options, ...options,
width, width: maxTargetWidth,
height: Math.round(width / aspectRatio), height: Math.round(maxTargetWidth / aspectRatio),
format: targetFormat, format: targetFormat,
}, },
descriptor: `${densityValues[index]}x`, descriptor: `${densityValues[index]}x`,
@ -253,11 +258,12 @@ export const baseService: Omit<LocalImageService, 'transform'> = {
}); });
} else if (widths) { } else if (widths) {
widths.forEach((width) => { widths.forEach((width) => {
const maxTargetWidth = Math.min(width, maxWidth);
srcSet.push({ srcSet.push({
transform: { transform: {
...options, ...options,
width, width,
height: Math.round(width / aspectRatio), height: Math.round(maxTargetWidth / aspectRatio),
format: targetFormat, format: targetFormat,
}, },
descriptor: `${width}w`, descriptor: `${width}w`,

View file

@ -59,8 +59,10 @@ export interface GetImageResult {
rawOptions: ImageTransform; rawOptions: ImageTransform;
options: ImageTransform; options: ImageTransform;
src: string; src: string;
srcSet: SrcSetValue[]; srcSet: {
srcSetValue: string; values: SrcSetValue[];
attribute: string;
};
attributes: Record<string, any>; attributes: Record<string, any>;
} }

View file

@ -16,8 +16,8 @@ export function propsToFilename(transform: ImageTransform, hash: string) {
} }
export function hashTransform(transform: ImageTransform, imageService: string) { export function hashTransform(transform: ImageTransform, imageService: string) {
// take everything from transform except alt, which is not used in the hash // Extract the fields we want to hash
const { alt, ...rest } = transform; const { alt, class: className, style, ...rest } = transform;
const hashFields = { ...rest, imageService }; const hashFields = { ...rest, imageService };
return shorthash(JSON.stringify(hashFields)); return shorthash(JSON.stringify(hashFields));
} }

View file

@ -621,6 +621,21 @@ export const ExpectedImageOptions = {
`Expected getImage() parameter to be an object. Received \`${options}\`.`, `Expected getImage() parameter to be an object. Received \`${options}\`.`,
} satisfies ErrorData; } satisfies ErrorData;
/**
* @docs
* @see
* - [Images](https://docs.astro.build/en/guides/images/)
* @description
* Only one of `densities` or `widths` can be specified. Those attributes are used to construct a `srcset` attribute, which cannot have both `x` and `w` descriptors.
*/
export const IncompatibleDescriptorOptions = {
name: 'IncompatibleDescriptorOptions',
title: 'Cannot set both `densities` and `widths`',
message:
"Only one of `densities` or `widths` can be specified. In most cases, you'll probably want to use only `widths` if you require specific widths.",
hint: 'Those attributes are used to construct a `srcset` attribute, which cannot have both `x` and `w` descriptors.',
} satisfies ErrorData;
/** /**
* @docs * @docs
* @see * @see