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

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

View file

@ -1,10 +1,10 @@
import type { APIRoute } from 'astro'; import type { APIRoute } from 'astro';
import { lookup } from 'mrmime'; import { lookup } from 'mrmime';
// @ts-ignore
import loader from 'virtual:image-loader';
import { loadImage } from '../utils.js'; import { loadImage } from '../utils.js';
export const get: APIRoute = async ({ request }) => { export const get: APIRoute = async ({ request }) => {
const loader = globalThis.astroImage.ssrLoader;
try { try {
const url = new URL(request.url); const url = new URL(request.url);
const transform = loader.parseTransform(url.searchParams); const transform = loader.parseTransform(url.searchParams);

View file

@ -8,7 +8,7 @@ import {
OutputFormat, OutputFormat,
TransformOptions, TransformOptions,
} from './types.js'; } from './types.js';
import { parseAspectRatio } from './utils.js'; import { isRemoteImage, parseAspectRatio } from './utils.js';
export interface GetImageTransform extends Omit<TransformOptions, 'src'> { export interface GetImageTransform extends Omit<TransformOptions, 'src'> {
src: string | ImageMetadata | Promise<{ default: ImageMetadata }>; src: string | ImageMetadata | Promise<{ default: ImageMetadata }>;
@ -105,23 +105,32 @@ export async function getImage(
loader: ImageService, loader: ImageService,
transform: GetImageTransform transform: GetImageTransform
): Promise<ImageAttributes> { ): Promise<ImageAttributes> {
(globalThis as any).loader = loader; globalThis.astroImage.loader = loader;
const resolved = await resolveTransform(transform); const resolved = await resolveTransform(transform);
const attributes = await loader.getImageAttributes(resolved); 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 // For SSR services, build URLs for the injected route
if (isSSRService(loader)) { if (isSSRService(_loader)) {
const { searchParams } = loader.serializeTransform(resolved); const { searchParams } = _loader.serializeTransform(resolved);
// cache all images rendered to HTML // cache all images rendered to HTML
if (globalThis && (globalThis as any).addStaticImage) { if (globalThis?.astroImage) {
(globalThis as any)?.addStaticImage(resolved); globalThis.astroImage.addStaticImage(resolved);
} }
const src = const src = globalThis?.astroImage
globalThis && (globalThis as any).filenameFormat ? globalThis.astroImage.filenameFormat(resolved, searchParams)
? (globalThis as any).filenameFormat(resolved, searchParams)
: `${ROUTE_PATTERN}?${searchParams.toString()}`; : `${ROUTE_PATTERN}?${searchParams.toString()}`;
return { return {

View file

@ -3,6 +3,7 @@ import fs from 'fs/promises';
import path from 'path'; import path from 'path';
import { fileURLToPath } from 'url'; import { fileURLToPath } from 'url';
import { OUTPUT_DIR, PKG_NAME, ROUTE_PATTERN } from './constants.js'; import { OUTPUT_DIR, PKG_NAME, ROUTE_PATTERN } from './constants.js';
import sharp from './loaders/sharp.js';
import { IntegrationOptions, TransformOptions } from './types.js'; import { IntegrationOptions, TransformOptions } from './types.js';
import { import {
ensureDir, ensureDir,
@ -51,15 +52,15 @@ const createIntegration = (options: IntegrationOptions = {}): AstroIntegration =
// Used to cache all images rendered to HTML // Used to cache all images rendered to HTML
// Added to globalThis to share the same map in Node and Vite // 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); staticImages.set(propsToFilename(transform), transform);
}; };
// TODO: Add support for custom, user-provided filename format functions // TODO: Add support for custom, user-provided filename format functions
(globalThis as any).filenameFormat = ( function filenameFormat(
transform: TransformOptions, transform: TransformOptions,
searchParams: URLSearchParams searchParams: URLSearchParams
) => { ) {
if (mode === 'ssg') { if (mode === 'ssg') {
return isRemoteImage(transform.src) return isRemoteImage(transform.src)
? path.join(OUTPUT_DIR, path.basename(propsToFilename(transform))) ? 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') { if (mode === 'ssr') {
injectRoute({ injectRoute({
pattern: ROUTE_PATTERN, pattern: ROUTE_PATTERN,
@ -83,7 +94,12 @@ const createIntegration = (options: IntegrationOptions = {}): AstroIntegration =
}, },
'astro:build:done': async ({ dir }) => { 'astro:build:done': async ({ dir }) => {
for await (const [filename, transform] of staticImages) { 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 inputBuffer: Buffer | undefined = undefined;
let outputFile: string; let outputFile: string;

View file

@ -2,6 +2,18 @@
export type { Image, Picture } from '../components/index.js'; export type { Image, Picture } from '../components/index.js';
export * from './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 = export type InputFormat =
| 'heic' | 'heic'
| 'heif' | 'heif'