diff --git a/.changeset/purple-vans-bake.md b/.changeset/purple-vans-bake.md new file mode 100644 index 000000000000..3af99ba74feb --- /dev/null +++ b/.changeset/purple-vans-bake.md @@ -0,0 +1,5 @@ +--- +'@astrojs/image': patch +--- + +Improves the `astro dev` experience when using a third-party hosted image service diff --git a/packages/integrations/image/README.md b/packages/integrations/image/README.md index b14587d71227..ee9fdf3c92a5 100644 --- a/packages/integrations/image/README.md +++ b/packages/integrations/image/README.md @@ -73,6 +73,8 @@ The included `sharp` transformer supports resizing images and encoding them to d The intergration can be configured to run with a different image service, either a hosted image service or a full image transformer that runs locally in your build or SSR deployment. +> During development, local images may not have been published yet and would not be available to hosted image services. Local images will always use the built-in `sharp` service when using `astro dev`. + There are currently no other configuration options for the `@astrojs/image` integration. Please [open an issue](https://github.com/withastro/astro/issues/new/choose) if you have a compelling use case to share.
diff --git a/packages/integrations/image/src/endpoints/dev.ts b/packages/integrations/image/src/endpoints/dev.ts index 3d8b28993bd4..67b37b177470 100644 --- a/packages/integrations/image/src/endpoints/dev.ts +++ b/packages/integrations/image/src/endpoints/dev.ts @@ -1,10 +1,10 @@ import type { APIRoute } from 'astro'; import { lookup } from 'mrmime'; -// @ts-ignore -import loader from 'virtual:image-loader'; import { loadImage } from '../utils.js'; export const get: APIRoute = async ({ request }) => { + const loader = globalThis.astroImage.ssrLoader; + try { const url = new URL(request.url); const transform = loader.parseTransform(url.searchParams); diff --git a/packages/integrations/image/src/get-image.ts b/packages/integrations/image/src/get-image.ts index 5eb41cf736eb..83ac2e5ba06c 100644 --- a/packages/integrations/image/src/get-image.ts +++ b/packages/integrations/image/src/get-image.ts @@ -8,7 +8,7 @@ import { OutputFormat, TransformOptions, } from './types.js'; -import { parseAspectRatio } from './utils.js'; +import { isRemoteImage, parseAspectRatio } from './utils.js'; export interface GetImageTransform extends Omit { src: string | ImageMetadata | Promise<{ default: ImageMetadata }>; @@ -105,23 +105,32 @@ export async function getImage( loader: ImageService, transform: GetImageTransform ): Promise { - (globalThis as any).loader = loader; + globalThis.astroImage.loader = loader; const resolved = await resolveTransform(transform); + const attributes = await loader.getImageAttributes(resolved); + const isDev = globalThis.astroImage.command === 'dev'; + const isLocalImage = !isRemoteImage(resolved.src); + + const _loader = isDev && isLocalImage ? globalThis.astroImage.ssrLoader : loader; + + if (!_loader) { + throw new Error('@astrojs/image: loader not found!'); + } + // For SSR services, build URLs for the injected route - if (isSSRService(loader)) { - const { searchParams } = loader.serializeTransform(resolved); + if (isSSRService(_loader)) { + const { searchParams } = _loader.serializeTransform(resolved); // cache all images rendered to HTML - if (globalThis && (globalThis as any).addStaticImage) { - (globalThis as any)?.addStaticImage(resolved); + if (globalThis?.astroImage) { + globalThis.astroImage.addStaticImage(resolved); } - const src = - globalThis && (globalThis as any).filenameFormat - ? (globalThis as any).filenameFormat(resolved, searchParams) + const src = globalThis?.astroImage + ? globalThis.astroImage.filenameFormat(resolved, searchParams) : `${ROUTE_PATTERN}?${searchParams.toString()}`; return { diff --git a/packages/integrations/image/src/index.ts b/packages/integrations/image/src/index.ts index 3721f9667e28..da1ec1d4b8d4 100644 --- a/packages/integrations/image/src/index.ts +++ b/packages/integrations/image/src/index.ts @@ -3,6 +3,7 @@ import fs from 'fs/promises'; import path from 'path'; import { fileURLToPath } from 'url'; import { OUTPUT_DIR, PKG_NAME, ROUTE_PATTERN } from './constants.js'; +import sharp from './loaders/sharp.js'; import { IntegrationOptions, TransformOptions } from './types.js'; import { ensureDir, @@ -51,15 +52,15 @@ const createIntegration = (options: IntegrationOptions = {}): AstroIntegration = // Used to cache all images rendered to HTML // Added to globalThis to share the same map in Node and Vite - (globalThis as any).addStaticImage = (transform: TransformOptions) => { + function addStaticImage(transform: TransformOptions) { staticImages.set(propsToFilename(transform), transform); }; // TODO: Add support for custom, user-provided filename format functions - (globalThis as any).filenameFormat = ( + function filenameFormat( transform: TransformOptions, searchParams: URLSearchParams - ) => { + ) { if (mode === 'ssg') { return isRemoteImage(transform.src) ? path.join(OUTPUT_DIR, path.basename(propsToFilename(transform))) @@ -73,6 +74,16 @@ const createIntegration = (options: IntegrationOptions = {}): AstroIntegration = } }; + // Initialize the integration's globalThis namespace + // This is needed to share scope between Node and Vite + globalThis.astroImage = { + loader: undefined, // initialized in first getImage() call + ssrLoader: sharp, + command, + addStaticImage, + filenameFormat, + } + if (mode === 'ssr') { injectRoute({ pattern: ROUTE_PATTERN, @@ -83,7 +94,12 @@ const createIntegration = (options: IntegrationOptions = {}): AstroIntegration = }, 'astro:build:done': async ({ dir }) => { for await (const [filename, transform] of staticImages) { - const loader = (globalThis as any).loader; + const loader = globalThis.astroImage.loader; + + if (!loader || !('transform' in loader)) { + // this should never be hit, how was a staticImage added without an SSR service? + return; + } let inputBuffer: Buffer | undefined = undefined; let outputFile: string; diff --git a/packages/integrations/image/src/types.ts b/packages/integrations/image/src/types.ts index 96067e8285d5..e6c315c23376 100644 --- a/packages/integrations/image/src/types.ts +++ b/packages/integrations/image/src/types.ts @@ -2,6 +2,18 @@ export type { Image, Picture } from '../components/index.js'; export * from './index.js'; +interface ImageIntegration { + loader?: ImageService; + ssrLoader: SSRImageService; + command: 'dev' | 'build'; + addStaticImage: (transform: TransformOptions) => void; + filenameFormat: (transform: TransformOptions, searchParams: URLSearchParams) => string; +} + +declare global { + var astroImage: ImageIntegration; +} + export type InputFormat = | 'heic' | 'heif'