From 0ba01e41d6f959113b02768321bc2a0d0a60f825 Mon Sep 17 00:00:00 2001 From: Tony Sullivan Date: Thu, 7 Jul 2022 14:02:05 -0500 Subject: [PATCH] refactor: moving getImage to it's own file --- packages/integrations/image/src/config.ts | 3 + packages/integrations/image/src/get-image.ts | 135 +++++++++++++++++ packages/integrations/image/src/index.ts | 149 +------------------ 3 files changed, 141 insertions(+), 146 deletions(-) create mode 100644 packages/integrations/image/src/config.ts create mode 100644 packages/integrations/image/src/get-image.ts diff --git a/packages/integrations/image/src/config.ts b/packages/integrations/image/src/config.ts new file mode 100644 index 000000000..db52614c5 --- /dev/null +++ b/packages/integrations/image/src/config.ts @@ -0,0 +1,3 @@ +export const PKG_NAME = '@astrojs/image'; +export const ROUTE_PATTERN = '/_image'; +export const OUTPUT_DIR = '/_image'; diff --git a/packages/integrations/image/src/get-image.ts b/packages/integrations/image/src/get-image.ts new file mode 100644 index 000000000..dc314e01f --- /dev/null +++ b/packages/integrations/image/src/get-image.ts @@ -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 { + 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 { + // 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 `` 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 `` element. + */ + export async function getImage( + loader: ImageService, + transform: GetImageTransform +): Promise { + (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); +} diff --git a/packages/integrations/image/src/index.ts b/packages/integrations/image/src/index.ts index f6432208c..71ba17a35 100644 --- a/packages/integrations/image/src/index.ts +++ b/packages/integrations/image/src/index.ts @@ -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 { - 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 { - // 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 `` 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 `` element. - */ -export async function getImage( - loader: ImageService, - transform: GetImageTransform -): Promise { - (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',