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:
parent
eee14b5e5f
commit
2a7dd040e8
6 changed files with 59 additions and 15 deletions
5
.changeset/purple-vans-bake.md
Normal file
5
.changeset/purple-vans-bake.md
Normal file
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
'@astrojs/image': patch
|
||||
---
|
||||
|
||||
Improves the `astro dev` experience when using a third-party hosted image service
|
|
@ -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>
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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'
|
||||
|
|
Loading…
Reference in a new issue