diff options
Diffstat (limited to 'packages/integrations/vercel/src')
7 files changed, 119 insertions, 43 deletions
diff --git a/packages/integrations/vercel/src/image/build-service.ts b/packages/integrations/vercel/src/image/build-service.ts index 0e45167d4..bd58d3af6 100644 --- a/packages/integrations/vercel/src/image/build-service.ts +++ b/packages/integrations/vercel/src/image/build-service.ts @@ -40,8 +40,9 @@ const service: ExternalImageService = { }; }, getURL(options) { - const fileSrc = - typeof options.src === 'string' ? options.src : removeLeadingForwardSlash(options.src.src); + const fileSrc = isESMImportedImage(options.src) + ? removeLeadingForwardSlash(options.src.src) + : options.src; const searchParams = new URLSearchParams(); searchParams.append('url', fileSrc); diff --git a/packages/integrations/vercel/src/image/dev-service.ts b/packages/integrations/vercel/src/image/dev-service.ts index a335c8d23..c9702cff9 100644 --- a/packages/integrations/vercel/src/image/dev-service.ts +++ b/packages/integrations/vercel/src/image/dev-service.ts @@ -1,10 +1,9 @@ import type { LocalImageService } from 'astro'; -import squooshService from 'astro/assets/services/squoosh'; -import { sharedValidateOptions } from './shared.js'; +import sharpService from 'astro/assets/services/sharp'; +import { baseDevService } from './shared-dev-service.js'; const service: LocalImageService = { - validateOptions: (options, serviceOptions) => - sharedValidateOptions(options, serviceOptions.service.config, 'development'), + ...baseDevService, getHTMLAttributes(options, serviceOptions) { const { inputtedWidth, ...props } = options; @@ -13,45 +12,19 @@ const service: LocalImageService = { props.width = inputtedWidth; } - return squooshService.getHTMLAttributes - ? squooshService.getHTMLAttributes(props, serviceOptions) + return sharpService.getHTMLAttributes + ? sharpService.getHTMLAttributes(props, serviceOptions) : {}; }, - getURL(options) { - const fileSrc = typeof options.src === 'string' ? options.src : options.src.src; - - const searchParams = new URLSearchParams(); - searchParams.append('href', fileSrc); - - options.width && searchParams.append('w', options.width.toString()); - options.quality && searchParams.append('q', options.quality.toString()); - - return '/_image?' + searchParams; - }, - parseURL(url) { - const params = url.searchParams; - - if (!params.has('href')) { - return undefined; - } - - const transform = { - src: params.get('href')!, - width: params.has('w') ? parseInt(params.get('w')!) : undefined, - quality: params.get('q'), - }; - - return transform; - }, transform(inputBuffer, transform, serviceOptions) { // NOTE: Hardcoding webp here isn't accurate to how the Vercel Image Optimization API works, normally what we should // do is setup a custom endpoint that sniff the user's accept-content header and serve the proper format based on the // user's Vercel config. However, that's: a lot of work for: not much. The dev service is inaccurate to the prod service // in many more ways, this is one of the less offending cases and is, imo, okay, erika - 2023-04-27 - transform.format = 'webp'; + transform.format = transform.src.endsWith('svg') ? 'svg' : 'webp'; - // The base Squoosh service works the same way as the Vercel Image Optimization API, so it's a safe fallback in local - return squooshService.transform(inputBuffer, transform, serviceOptions); + // The base sharp service works the same way as the Vercel Image Optimization API, so it's a safe fallback in local + return sharpService.transform(inputBuffer, transform, serviceOptions); }, }; diff --git a/packages/integrations/vercel/src/image/shared-dev-service.ts b/packages/integrations/vercel/src/image/shared-dev-service.ts new file mode 100644 index 000000000..4251603a7 --- /dev/null +++ b/packages/integrations/vercel/src/image/shared-dev-service.ts @@ -0,0 +1,33 @@ +import type { LocalImageService } from 'astro'; +import { sharedValidateOptions } from './shared.js'; + +export const baseDevService: Omit<LocalImageService, 'transform'> = { + validateOptions: (options, serviceOptions) => + sharedValidateOptions(options, serviceOptions.service.config, 'development'), + getURL(options) { + const fileSrc = typeof options.src === 'string' ? options.src : options.src.src; + + const searchParams = new URLSearchParams(); + searchParams.append('href', fileSrc); + + options.width && searchParams.append('w', options.width.toString()); + options.quality && searchParams.append('q', options.quality.toString()); + + return '/_image?' + searchParams; + }, + parseURL(url) { + const params = url.searchParams; + + if (!params.has('href')) { + return undefined; + } + + const transform = { + src: params.get('href')!, + width: params.has('w') ? parseInt(params.get('w')!) : undefined, + quality: params.get('q'), + }; + + return transform; + }, +}; diff --git a/packages/integrations/vercel/src/image/shared.ts b/packages/integrations/vercel/src/image/shared.ts index f6cace2a2..079186e18 100644 --- a/packages/integrations/vercel/src/image/shared.ts +++ b/packages/integrations/vercel/src/image/shared.ts @@ -12,6 +12,10 @@ export function getDefaultImageConfig(astroImageConfig: AstroConfig['image']): V export function isESMImportedImage(src: ImageMetadata | string): src is ImageMetadata { return typeof src === 'object'; } + +// eslint-disable-next-line @typescript-eslint/ban-types +export type DevImageService = 'sharp' | 'squoosh' | (string & {}); + // https://vercel.com/docs/build-output-api/v3/configuration#images type ImageFormat = 'image/avif' | 'image/webp'; @@ -64,16 +68,32 @@ export function getAstroImageConfig( images: boolean | undefined, imagesConfig: VercelImageConfig | undefined, command: string, + devImageService: DevImageService, astroImageConfig: AstroConfig['image'] ) { + let devService = '@astrojs/vercel/dev-image-service'; + + switch (devImageService) { + case 'sharp': + devService = '@astrojs/vercel/dev-image-service'; + break; + case 'squoosh': + devService = '@astrojs/vercel/squoosh-dev-image-service'; + break; + default: + if (typeof devImageService === 'string') { + devService = devImageService; + } else { + devService = '@astrojs/vercel/dev-image-service'; + } + break; + } + if (images) { return { image: { service: { - entrypoint: - command === 'dev' - ? '@astrojs/vercel/dev-image-service' - : '@astrojs/vercel/build-image-service', + entrypoint: command === 'dev' ? devService : '@astrojs/vercel/build-image-service', config: imagesConfig ? imagesConfig : getDefaultImageConfig(astroImageConfig), }, }, diff --git a/packages/integrations/vercel/src/image/squoosh-dev-service.ts b/packages/integrations/vercel/src/image/squoosh-dev-service.ts new file mode 100644 index 000000000..d3b05bb11 --- /dev/null +++ b/packages/integrations/vercel/src/image/squoosh-dev-service.ts @@ -0,0 +1,31 @@ +import type { LocalImageService } from 'astro'; +import squooshService from 'astro/assets/services/squoosh'; +import { baseDevService } from './shared-dev-service.js'; + +const service: LocalImageService = { + ...baseDevService, + getHTMLAttributes(options, serviceOptions) { + const { inputtedWidth, ...props } = options; + + // If `validateOptions` returned a different width than the one of the image, use it for attributes + if (inputtedWidth) { + props.width = inputtedWidth; + } + + return squooshService.getHTMLAttributes + ? squooshService.getHTMLAttributes(props, serviceOptions) + : {}; + }, + transform(inputBuffer, transform, serviceOptions) { + // NOTE: Hardcoding webp here isn't accurate to how the Vercel Image Optimization API works, normally what we should + // do is setup a custom endpoint that sniff the user's accept-content header and serve the proper format based on the + // user's Vercel config. However, that's: a lot of work for: not much. The dev service is inaccurate to the prod service + // in many more ways, this is one of the less offending cases and is, imo, okay, erika - 2023-04-27 + transform.format = transform.src.endsWith('svg') ? 'svg' : 'webp'; + + // The base squoosh service works the same way as the Vercel Image Optimization API, so it's a safe fallback in local + return squooshService.transform(inputBuffer, transform, serviceOptions); + }, +}; + +export default service; diff --git a/packages/integrations/vercel/src/serverless/adapter.ts b/packages/integrations/vercel/src/serverless/adapter.ts index 1c0eb9530..22785abf5 100644 --- a/packages/integrations/vercel/src/serverless/adapter.ts +++ b/packages/integrations/vercel/src/serverless/adapter.ts @@ -12,6 +12,7 @@ import { fileURLToPath, pathToFileURL } from 'node:url'; import { getAstroImageConfig, getDefaultImageConfig, + type DevImageService, type VercelImageConfig, } from '../image/shared.js'; import { exposeEnv } from '../lib/env.js'; @@ -68,6 +69,7 @@ export interface VercelServerlessConfig { analytics?: boolean; imageService?: boolean; imagesConfig?: VercelImageConfig; + devImageService?: DevImageService; edgeMiddleware?: boolean; functionPerRoute?: boolean; } @@ -78,6 +80,7 @@ export default function vercelServerless({ analytics, imageService, imagesConfig, + devImageService = 'sharp', functionPerRoute = true, edgeMiddleware = false, }: VercelServerlessConfig = {}): AstroIntegration { @@ -147,7 +150,13 @@ export default function vercelServerless({ external: ['@vercel/nft'], }, }, - ...getAstroImageConfig(imageService, imagesConfig, command, config.image), + ...getAstroImageConfig( + imageService, + imagesConfig, + command, + devImageService, + config.image + ), }); }, 'astro:config:done': ({ setAdapter, config, logger }) => { diff --git a/packages/integrations/vercel/src/static/adapter.ts b/packages/integrations/vercel/src/static/adapter.ts index 2908dbf58..27bc2fe2d 100644 --- a/packages/integrations/vercel/src/static/adapter.ts +++ b/packages/integrations/vercel/src/static/adapter.ts @@ -3,6 +3,7 @@ import type { AstroAdapter, AstroConfig, AstroIntegration } from 'astro'; import { getAstroImageConfig, getDefaultImageConfig, + type DevImageService, type VercelImageConfig, } from '../image/shared.js'; import { exposeEnv } from '../lib/env.js'; @@ -36,12 +37,14 @@ export interface VercelStaticConfig { analytics?: boolean; imageService?: boolean; imagesConfig?: VercelImageConfig; + devImageService?: DevImageService; } export default function vercelStatic({ analytics, imageService, imagesConfig, + devImageService = 'sharp', }: VercelStaticConfig = {}): AstroIntegration { let _config: AstroConfig; @@ -63,7 +66,13 @@ export default function vercelStatic({ vite: { define: viteDefine, }, - ...getAstroImageConfig(imageService, imagesConfig, command, config.image), + ...getAstroImageConfig( + imageService, + imagesConfig, + command, + devImageService, + config.image + ), }); }, 'astro:config:done': ({ setAdapter, config }) => { |