refactor: moving getImage to it's own file
This commit is contained in:
parent
48d47d5db2
commit
0ba01e41d6
3 changed files with 141 additions and 146 deletions
3
packages/integrations/image/src/config.ts
Normal file
3
packages/integrations/image/src/config.ts
Normal file
|
@ -0,0 +1,3 @@
|
|||
export const PKG_NAME = '@astrojs/image';
|
||||
export const ROUTE_PATTERN = '/_image';
|
||||
export const OUTPUT_DIR = '/_image';
|
135
packages/integrations/image/src/get-image.ts
Normal file
135
packages/integrations/image/src/get-image.ts
Normal file
|
@ -0,0 +1,135 @@
|
|||
import slash from 'slash';
|
||||
import { ROUTE_PATTERN } from './config.js';
|
||||
import { ImageAttributes, ImageMetadata, ImageService, isSSRService, OutputFormat, TransformOptions } from './types.js';
|
||||
import { parseAspectRatio } from './utils.js';
|
||||
|
||||
export interface GetImageTransform extends Omit<TransformOptions, 'src'> {
|
||||
src: string | ImageMetadata | Promise<{ default: ImageMetadata }>;
|
||||
}
|
||||
|
||||
function resolveSize(transform: TransformOptions): TransformOptions {
|
||||
// keep width & height as provided
|
||||
if (transform.width && transform.height) {
|
||||
return transform;
|
||||
}
|
||||
|
||||
if (!transform.width && !transform.height) {
|
||||
throw new Error(`"width" and "height" cannot both be undefined`);
|
||||
}
|
||||
|
||||
if (!transform.aspectRatio) {
|
||||
throw new Error(`"aspectRatio" must be included if only "${transform.width ? "width": "height"}" is provided`)
|
||||
}
|
||||
|
||||
let aspectRatio: number;
|
||||
|
||||
// parse aspect ratio strings, if required (ex: "16:9")
|
||||
if (typeof transform.aspectRatio === 'number') {
|
||||
aspectRatio = transform.aspectRatio;
|
||||
} else {
|
||||
const [width, height] = transform.aspectRatio.split(':');
|
||||
aspectRatio = parseInt(width) / parseInt(height);
|
||||
}
|
||||
|
||||
if (transform.width) {
|
||||
// only width was provided, calculate height
|
||||
return {
|
||||
...transform,
|
||||
width: transform.width,
|
||||
height: Math.round(transform.width / aspectRatio)
|
||||
} as TransformOptions;
|
||||
} else if (transform.height) {
|
||||
// only height was provided, calculate width
|
||||
return {
|
||||
...transform,
|
||||
width: Math.round(transform.height * aspectRatio),
|
||||
height: transform.height
|
||||
};
|
||||
}
|
||||
|
||||
return transform;
|
||||
}
|
||||
|
||||
async function resolveTransform(input: GetImageTransform): Promise<TransformOptions> {
|
||||
// for remote images, only validate the width and height props
|
||||
if (typeof input.src === 'string') {
|
||||
return resolveSize(input as TransformOptions);
|
||||
}
|
||||
|
||||
// resolve the metadata promise, usually when the ESM import is inlined
|
||||
const metadata = 'then' in input.src
|
||||
? (await input.src).default
|
||||
: input.src;
|
||||
|
||||
let { width, height, aspectRatio, format = metadata.format, ...rest } = input;
|
||||
|
||||
if (!width && !height) {
|
||||
// neither dimension was provided, use the file metadata
|
||||
width = metadata.width;
|
||||
height = metadata.height;
|
||||
} else if (width) {
|
||||
// one dimension was provided, calculate the other
|
||||
let ratio = parseAspectRatio(aspectRatio) || metadata.width / metadata.height;
|
||||
height = height || Math.round(width / ratio);
|
||||
} else if (height) {
|
||||
// one dimension was provided, calculate the other
|
||||
let ratio = parseAspectRatio(aspectRatio) || metadata.width / metadata.height;
|
||||
width = width || Math.round(height * ratio);
|
||||
}
|
||||
|
||||
return {
|
||||
...rest,
|
||||
src: metadata.src,
|
||||
width,
|
||||
height,
|
||||
aspectRatio,
|
||||
format: format as OutputFormat,
|
||||
}
|
||||
}
|
||||
|
||||
function getImageAttributes(src: string, transform: TransformOptions): ImageAttributes {
|
||||
return {
|
||||
loading: 'lazy',
|
||||
decoding: 'async',
|
||||
src,
|
||||
width: transform.width,
|
||||
height: transform.height
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the HTML attributes required to build an `<img />` for the transformed image.
|
||||
*
|
||||
* @param loader @type {ImageService} The image service used for transforming images.
|
||||
* @param transform @type {TransformOptions} The transformations requested for the optimized image.
|
||||
* @returns @type {ImageAttributes} The HTML attributes to be included on the built `<img />` element.
|
||||
*/
|
||||
export async function getImage(
|
||||
loader: ImageService,
|
||||
transform: GetImageTransform
|
||||
): Promise<ImageAttributes> {
|
||||
(globalThis as any).loader = loader;
|
||||
|
||||
const resolved = await resolveTransform(transform);
|
||||
|
||||
// For SSR services, build URLs for the injected route
|
||||
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);
|
||||
}
|
||||
|
||||
const src =
|
||||
globalThis && (globalThis as any).filenameFormat
|
||||
? (globalThis as any).filenameFormat(resolved, searchParams)
|
||||
: `${ROUTE_PATTERN}?${searchParams.toString()}`;
|
||||
|
||||
// Windows compat
|
||||
return getImageAttributes(slash(src), resolved);
|
||||
}
|
||||
|
||||
// For hosted services, return the `src` attribute as-is
|
||||
return getImageAttributes(await loader.getImageSrc(resolved), resolved);
|
||||
}
|
|
@ -1,162 +1,19 @@
|
|||
import type { AstroConfig, AstroIntegration } from 'astro';
|
||||
import fs from 'fs/promises';
|
||||
import path from 'path';
|
||||
import slash from 'slash';
|
||||
import { fileURLToPath } from 'url';
|
||||
import {
|
||||
ImageAttributes,
|
||||
ImageMetadata,
|
||||
ImageService,
|
||||
IntegrationOptions,
|
||||
isSSRService,
|
||||
OutputFormat,
|
||||
TransformOptions,
|
||||
} from './types.js';
|
||||
import { OUTPUT_DIR, PKG_NAME, ROUTE_PATTERN } from './config.js';
|
||||
export * from './get-image.js';
|
||||
import { IntegrationOptions, TransformOptions } from './types.js';
|
||||
import {
|
||||
ensureDir,
|
||||
isRemoteImage,
|
||||
loadLocalImage,
|
||||
loadRemoteImage,
|
||||
parseAspectRatio,
|
||||
propsToFilename,
|
||||
} from './utils.js';
|
||||
import { createPlugin } from './vite-plugin-astro-image.js';
|
||||
|
||||
const PKG_NAME = '@astrojs/image';
|
||||
const ROUTE_PATTERN = '/_image';
|
||||
const OUTPUT_DIR = '/_image';
|
||||
|
||||
export interface GetImageTransform extends Omit<TransformOptions, 'src'> {
|
||||
src: string | ImageMetadata | Promise<{ default: ImageMetadata }>;
|
||||
}
|
||||
|
||||
function resolveSize(transform: TransformOptions): TransformOptions {
|
||||
// keep width & height as provided
|
||||
if (transform.width && transform.height) {
|
||||
return transform;
|
||||
}
|
||||
|
||||
if (!transform.width && !transform.height) {
|
||||
throw new Error(`"width" and "height" cannot both be undefined`);
|
||||
}
|
||||
|
||||
if (!transform.aspectRatio) {
|
||||
throw new Error(`"aspectRatio" must be included if only "${transform.width ? "width": "height"}" is provided`)
|
||||
}
|
||||
|
||||
let aspectRatio: number;
|
||||
|
||||
// parse aspect ratio strings, if required (ex: "16:9")
|
||||
if (typeof transform.aspectRatio === 'number') {
|
||||
aspectRatio = transform.aspectRatio;
|
||||
} else {
|
||||
const [width, height] = transform.aspectRatio.split(':');
|
||||
aspectRatio = parseInt(width) / parseInt(height);
|
||||
}
|
||||
|
||||
if (transform.width) {
|
||||
// only width was provided, calculate height
|
||||
return {
|
||||
...transform,
|
||||
width: transform.width,
|
||||
height: Math.round(transform.width / aspectRatio)
|
||||
} as TransformOptions;
|
||||
} else if (transform.height) {
|
||||
// only height was provided, calculate width
|
||||
return {
|
||||
...transform,
|
||||
width: Math.round(transform.height * aspectRatio),
|
||||
height: transform.height
|
||||
};
|
||||
}
|
||||
|
||||
return transform;
|
||||
}
|
||||
|
||||
async function resolveTransform(input: GetImageTransform): Promise<TransformOptions> {
|
||||
// for remote images, only validate the width and height props
|
||||
if (typeof input.src === 'string') {
|
||||
return resolveSize(input as TransformOptions);
|
||||
}
|
||||
|
||||
// resolve the metadata promise, usually when the ESM import is inlined
|
||||
const metadata = 'then' in input.src
|
||||
? (await input.src).default
|
||||
: input.src;
|
||||
|
||||
let { width, height, aspectRatio, format = metadata.format, ...rest } = input;
|
||||
|
||||
if (!width && !height) {
|
||||
// neither dimension was provided, use the file metadata
|
||||
width = metadata.width;
|
||||
height = metadata.height;
|
||||
} else if (width) {
|
||||
// one dimension was provided, calculate the other
|
||||
let ratio = parseAspectRatio(aspectRatio) || metadata.width / metadata.height;
|
||||
height = height || Math.round(width / ratio);
|
||||
} else if (height) {
|
||||
// one dimension was provided, calculate the other
|
||||
let ratio = parseAspectRatio(aspectRatio) || metadata.width / metadata.height;
|
||||
width = width || Math.round(height * ratio);
|
||||
}
|
||||
|
||||
return {
|
||||
...rest,
|
||||
src: metadata.src,
|
||||
width,
|
||||
height,
|
||||
aspectRatio,
|
||||
format: format as OutputFormat,
|
||||
}
|
||||
}
|
||||
|
||||
function getImageAttributes(src: string, transform: TransformOptions): ImageAttributes {
|
||||
return {
|
||||
loading: 'lazy',
|
||||
decoding: 'async',
|
||||
src,
|
||||
width: transform.width,
|
||||
height: transform.height
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the HTML attributes required to build an `<img />` for the transformed image.
|
||||
*
|
||||
* @param loader @type {ImageService} The image service used for transforming images.
|
||||
* @param transform @type {TransformOptions} The transformations requested for the optimized image.
|
||||
* @returns @type {ImageAttributes} The HTML attributes to be included on the built `<img />` element.
|
||||
*/
|
||||
export async function getImage(
|
||||
loader: ImageService,
|
||||
transform: GetImageTransform
|
||||
): Promise<ImageAttributes> {
|
||||
(globalThis as any).loader = loader;
|
||||
|
||||
const resolved = await resolveTransform(transform);
|
||||
|
||||
// For SSR services, build URLs for the injected route
|
||||
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);
|
||||
}
|
||||
|
||||
const src =
|
||||
globalThis && (globalThis as any).filenameFormat
|
||||
? (globalThis as any).filenameFormat(resolved, searchParams)
|
||||
: `${ROUTE_PATTERN}?${searchParams.toString()}`;
|
||||
|
||||
// Windows compat
|
||||
return getImageAttributes(slash(src), resolved);
|
||||
}
|
||||
|
||||
// For hosted services, return the `src` attribute as-is
|
||||
return getImageAttributes(await loader.getImageSrc(resolved), resolved);
|
||||
}
|
||||
|
||||
const createIntegration = (options: IntegrationOptions = {}): AstroIntegration => {
|
||||
const resolvedOptions = {
|
||||
serviceEntryPoint: '@astrojs/image/sharp',
|
||||
|
|
Loading…
Reference in a new issue