0
Fork 0
mirror of https://github.com/withastro/astro.git synced 2025-03-10 23:01:26 -05:00

Expose inferRemoteSize function (#11098)

* feat: expose and rename `inferSize`

* feat: separate `ISize` type

* feat: reformat function to use `ImageMetadata`

* nit(assets): re-use image-metadata code for remote images

* chore: changeset

* chore: changeset

* feat(assets): Export from `astro:assets`

* fix: proper errors

* fix: dont export from astro/assets

* fix: ests

* Update .changeset/large-geese-play.md

Co-authored-by: Sarah Rainsberger <sarah@rainsberger.ca>

* fix: ests

* Update .changeset/large-geese-play.md

Co-authored-by: Sarah Rainsberger <sarah@rainsberger.ca>

---------

Co-authored-by: Erika <3019731+Princesseuh@users.noreply.github.com>
Co-authored-by: Sarah Rainsberger <sarah@rainsberger.ca>
This commit is contained in:
Matteo Manfredi 2024-07-17 17:27:04 +02:00 committed by Emanuele Stoppa
parent d54671fcf0
commit 9bbfad3fc2
11 changed files with 64 additions and 26 deletions

View file

@ -0,0 +1,20 @@
---
"astro": minor
---
Adds a new `inferRemoteSize()` function that can be used to infer the dimensions of a remote image.
Previously, the ability to infer these values was only available by adding the [`inferSize`] attribute to the `<Image>` and `<Picture>` components or `getImage()`. Now, you can also access this data outside of these components.
This is useful for when you need to know the dimensions of an image for styling purposes or to calculate different densities for responsive images.
```astro
---
import { inferRemoteSize, Image } from 'astro:assets';
const imageUrl = 'https://...';
const { width, height } = await inferRemoteSize(imageUrl);
---
<Image src={imageUrl} width={width / 2} height={height} densities={[1.5, 2]} />
```

View file

@ -54,6 +54,7 @@ declare module 'astro:assets' {
) => Promise<import('./dist/assets/types.js').GetImageResult>;
imageConfig: import('./dist/@types/astro.js').AstroConfig['image'];
getConfiguredImageService: typeof import('./dist/assets/index.js').getConfiguredImageService;
inferRemoteSize: typeof import('./dist/assets/utils/index.js').inferRemoteSize;
Image: typeof import('./components/Image.astro').default;
Picture: typeof import('./components/Picture.astro').default;
};

View file

@ -63,6 +63,7 @@
"./actions/runtime/*": "./dist/actions/runtime/*",
"./assets": "./dist/assets/index.js",
"./assets/utils": "./dist/assets/utils/index.js",
"./assets/utils/inferRemoteSize.js": "./dist/assets/utils/remoteProbe.js",
"./assets/endpoint/*": "./dist/assets/endpoint/*.js",
"./assets/services/sharp": "./dist/assets/services/sharp.js",
"./assets/services/squoosh": "./dist/assets/services/squoosh.js",

View file

@ -10,7 +10,7 @@ import {
isImageMetadata,
} from './types.js';
import { isESMImportedImage, isRemoteImage, resolveSrc } from './utils/imageKind.js';
import { probe } from './utils/remoteProbe.js';
import { inferRemoteSize } from './utils/remoteProbe.js';
export async function getConfiguredImageService(): Promise<ImageService> {
if (!globalThis?.astroAsset?.imageService) {
@ -66,17 +66,10 @@ export async function getImage(
// Infer size for remote images if inferSize is true
if (options.inferSize && isRemoteImage(resolvedOptions.src)) {
try {
const result = await probe(resolvedOptions.src); // Directly probe the image URL
resolvedOptions.width ??= result.width;
resolvedOptions.height ??= result.height;
delete resolvedOptions.inferSize; // Delete so it doesn't end up in the attributes
} catch {
throw new AstroError({
...AstroErrorData.FailedToFetchRemoteImageDimensions,
message: AstroErrorData.FailedToFetchRemoteImageDimensions.message(resolvedOptions.src),
});
}
const result = await inferRemoteSize(resolvedOptions.src); // Directly probe the image URL
resolvedOptions.width ??= result.width;
resolvedOptions.height ??= result.height;
delete resolvedOptions.inferSize; // Delete so it doesn't end up in the attributes
}
const originalFilePath = isESMImportedImage(resolvedOptions.src)

View file

@ -1,4 +1,4 @@
export { emitESMImage } from './emitAsset.js';
export { emitESMImage } from './node/emitAsset.js';
export { isESMImportedImage, isRemoteImage } from './imageKind.js';
export { imageMetadata } from './metadata.js';
export { getOrigQueryParams } from './queryParams.js';
@ -12,3 +12,4 @@ export {
type RemotePattern,
} from './remotePattern.js';
export { hashTransform, propsToFilename } from './transformToPath.js';
export { inferRemoteSize } from './remoteProbe.js';

View file

@ -2,9 +2,9 @@ import fs from 'node:fs/promises';
import path from 'node:path';
import { fileURLToPath, pathToFileURL } from 'node:url';
import type * as vite from 'vite';
import { prependForwardSlash, slash } from '../../core/path.js';
import type { ImageMetadata } from '../types.js';
import { imageMetadata } from './metadata.js';
import { prependForwardSlash, slash } from '../../../core/path.js';
import type { ImageMetadata } from '../../types.js';
import { imageMetadata } from '../metadata.js';
type FileEmitter = vite.Rollup.EmitFile;

View file

@ -1,11 +1,15 @@
import { lookup } from './vendor/image-size/lookup.js';
import type { ISize } from './vendor/image-size/types/interface.ts';
import { AstroError, AstroErrorData } from '../../core/errors/index.js';
import type { ImageMetadata } from '../types.js';
import { imageMetadata } from './metadata.js';
export async function probe(url: string): Promise<ISize> {
export async function inferRemoteSize(url: string): Promise<Omit<ImageMetadata, 'src' | 'fsPath'>> {
// Start fetching the image
const response = await fetch(url);
if (!response.body || !response.ok) {
throw new Error('Failed to fetch image');
throw new AstroError({
...AstroErrorData.FailedToFetchRemoteImageDimensions,
message: AstroErrorData.FailedToFetchRemoteImageDimensions.message(url),
});
}
const reader = response.body.getReader();
@ -31,17 +35,22 @@ export async function probe(url: string): Promise<ISize> {
try {
// Attempt to determine the size with each new chunk
const dimensions = lookup(accumulatedChunks);
const dimensions = await imageMetadata(accumulatedChunks, url);
if (dimensions) {
await reader.cancel(); // stop stream as we have size now
return dimensions;
}
} catch (error) {
// This catch block is specifically for `sizeOf` failures,
// This catch block is specifically for `imageMetadata` errors
// which might occur if the accumulated data isn't yet sufficient.
}
}
}
throw new Error('Failed to parse the size');
throw new AstroError({
...AstroErrorData.NoImageMetadata,
message: AstroErrorData.NoImageMetadata.message(url),
});
}

View file

@ -14,7 +14,7 @@ import {
} from '../core/path.js';
import { isServerLikeOutput } from '../core/util.js';
import { VALID_INPUT_FORMATS, VIRTUAL_MODULE_ID, VIRTUAL_SERVICE_ID } from './consts.js';
import { emitESMImage } from './utils/emitAsset.js';
import { emitESMImage } from './utils/node/emitAsset.js';
import { getAssetsPrefix } from './utils/getAssetsPrefix.js';
import { isESMImportedImage } from './utils/imageKind.js';
import { getProxyCode } from './utils/proxy.js';
@ -133,6 +133,7 @@ export default function assets({
import { getImage as getImageInternal } from "astro/assets";
export { default as Image } from "astro/components/Image.astro";
export { default as Picture } from "astro/components/Picture.astro";
export { inferRemoteSize } from "astro/assets/utils/inferRemoteSize.js";
export const imageConfig = ${JSON.stringify(settings.config.image)};
// This is used by the @astrojs/node integration to locate images.

View file

@ -1,7 +1,7 @@
import type { PluginContext } from 'rollup';
import { z } from 'zod';
import type { ImageMetadata, OmitBrand } from '../assets/types.js';
import { emitESMImage } from '../assets/utils/emitAsset.js';
import { emitESMImage } from '../assets/utils/node/emitAsset.js';
export function createImage(
pluginContext: PluginContext,

View file

@ -70,6 +70,11 @@ describe('astro:image:infersize', () => {
true
);
});
it('direct function call work', async () => {
let $dimensions = $('#direct');
assert.equal($dimensions.text().trim(), '64x64');
});
});
});
});

View file

@ -1,6 +1,9 @@
---
// https://avatars.githubusercontent.com/u/622227?s=64 is a .jpeg
import { Image, Picture, getImage } from 'astro:assets';
import { Image, Picture, getImage, inferRemoteSize } from 'astro:assets';
const { width, height } = await inferRemoteSize('https://avatars.githubusercontent.com/u/622227?s=64');
const remoteImg = await getImage({
src: 'https://avatars.githubusercontent.com/u/622227?s=64',
inferSize: true,
@ -10,3 +13,7 @@ const remoteImg = await getImage({
<Image src="https://avatars.githubusercontent.com/u/622227?s=64," inferSize={true} , alt="" />
<Picture src="https://avatars.githubusercontent.com/u/622227?s=64," inferSize={true} , alt="" />
<img src={remoteImg.src} {...remoteImg.attributes} id="getImage" />
<div id="direct">
{width}x{height}
</div>