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:
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.
|
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>
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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'
|
||||||
|
|
Loading…
Add table
Reference in a new issue