Improving support for third-party hosted image services (#3957)

* WIP: always use the built-in sharp service for local images in `dev`

* adding type definitions for the integration's use of globalThis

* simplifying the globalThis type checking

* chore: adding changeset

* removing temp hosted service used for testing
This commit is contained in:
Tony Sullivan 2022-07-18 19:43:40 +00:00 committed by GitHub
parent eee14b5e5f
commit 2a7dd040e8
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
6 changed files with 59 additions and 15 deletions

View file

@ -0,0 +1,5 @@
---
'@astrojs/image': patch
---
Improves the `astro dev` experience when using a third-party hosted image service

View file

@ -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.
<details>

View file

@ -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);

View file

@ -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<TransformOptions, 'src'> {
src: string | ImageMetadata | Promise<{ default: ImageMetadata }>;
@ -105,23 +105,32 @@ export async function getImage(
loader: ImageService,
transform: GetImageTransform
): Promise<ImageAttributes> {
(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 {

View file

@ -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;

View file

@ -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'