From ffa55bbe22d73bc752230cacf35ecf87bbf37d9a Mon Sep 17 00:00:00 2001 From: Princesseuh Date: Fri, 14 Apr 2023 17:33:26 +0200 Subject: [PATCH 1/7] feat(image): Add a setting to pass settings to the image service --- packages/astro/src/assets/image-endpoint.ts | 10 +++++++-- packages/astro/src/assets/internal.ts | 21 ++++++++++++++----- packages/astro/src/assets/services/service.ts | 17 ++++++++++----- .../astro/src/assets/vite-plugin-assets.ts | 17 ++++++++++++--- packages/astro/src/core/config/schema.ts | 1 + 5 files changed, 51 insertions(+), 15 deletions(-) diff --git a/packages/astro/src/assets/image-endpoint.ts b/packages/astro/src/assets/image-endpoint.ts index e3f90941ef81..e3553edbc6ed 100644 --- a/packages/astro/src/assets/image-endpoint.ts +++ b/packages/astro/src/assets/image-endpoint.ts @@ -4,6 +4,8 @@ import { isRemotePath } from '../core/path.js'; import { getConfiguredImageService } from './internal.js'; import { isLocalService } from './services/service.js'; import { etag } from './utils/etag.js'; +// @ts-expect-error +import { imageServiceConfig } from 'astro:assets'; async function loadRemoteImage(src: URL) { try { @@ -31,7 +33,7 @@ export const get: APIRoute = async ({ request }) => { } const url = new URL(request.url); - const transform = await imageService.parseURL(url); + const transform = await imageService.parseURL(url, imageServiceConfig); if (!transform || !transform.src) { throw new Error('Incorrect transform returned by `parseURL`'); @@ -49,7 +51,11 @@ export const get: APIRoute = async ({ request }) => { return new Response('Not Found', { status: 404 }); } - const { data, format } = await imageService.transform(inputBuffer, transform); + const { data, format } = await imageService.transform( + inputBuffer, + transform, + imageServiceConfig + ); return new Response(data, { status: 200, diff --git a/packages/astro/src/assets/internal.ts b/packages/astro/src/assets/internal.ts index f38b88124680..084231e3523d 100644 --- a/packages/astro/src/assets/internal.ts +++ b/packages/astro/src/assets/internal.ts @@ -45,7 +45,10 @@ export async function getConfiguredImageService(): Promise { * * This is functionally equivalent to using the `` component, as the component calls this function internally. */ -export async function getImage(options: ImageTransform): Promise { +export async function getImage( + options: ImageTransform, + serviceOptions: Record +): Promise { if (!options || typeof options !== 'object') { throw new AstroError({ ...AstroErrorData.ExpectedImageOptions, @@ -54,9 +57,11 @@ export async function getImage(options: ImageTransform): Promise } const service = await getConfiguredImageService(); - const validatedOptions = service.validateOptions ? service.validateOptions(options) : options; + const validatedOptions = service.validateOptions + ? service.validateOptions(options, serviceOptions) + : options; - let imageURL = service.getURL(validatedOptions); + let imageURL = service.getURL(validatedOptions, serviceOptions); // In build and for local services, we need to collect the requested parameters so we can generate the final images if (isLocalService(service) && globalThis.astroAsset.addStaticImage) { @@ -68,7 +73,9 @@ export async function getImage(options: ImageTransform): Promise options: validatedOptions, src: imageURL, attributes: - service.getHTMLAttributes !== undefined ? service.getHTMLAttributes(validatedOptions) : {}, + service.getHTMLAttributes !== undefined + ? service.getHTMLAttributes(validatedOptions, serviceOptions) + : {}, }; } @@ -121,7 +128,11 @@ export async function generateImage( serverRoot ) ); - const resultData = await imageService.transform(fileData, { ...options, src: originalImagePath }); + const resultData = await imageService.transform( + fileData, + { ...options, src: originalImagePath }, + buildOpts.settings.config.image.serviceConfig + ); const finalFileURL = new URL('.' + filepath, clientRoot); const finalFolderURL = new URL('./', finalFileURL); diff --git a/packages/astro/src/assets/services/service.ts b/packages/astro/src/assets/services/service.ts index aa33d89019de..a7cc7a190173 100644 --- a/packages/astro/src/assets/services/service.ts +++ b/packages/astro/src/assets/services/service.ts @@ -31,14 +31,17 @@ interface SharedServiceProps { * For external services, this should point to the URL your images are coming from, for instance, `/_vercel/image` * */ - getURL: (options: ImageTransform) => string; + getURL: (options: ImageTransform, serviceOptions: Record) => string; /** * Return any additional HTML attributes separate from `src` that your service requires to show the image properly. * * For example, you might want to return the `width` and `height` to avoid CLS, or a particular `class` or `style`. * In most cases, you'll want to return directly what your user supplied you, minus the attributes that were used to generate the image. */ - getHTMLAttributes?: (options: ImageTransform) => Record; + getHTMLAttributes?: ( + options: ImageTransform, + serviceOptions: Record + ) => Record; /** * Validate and return the options passed by the user. * @@ -47,7 +50,10 @@ interface SharedServiceProps { * * This method should returns options, and can be used to set defaults (ex: a default output format to be used if the user didn't specify one.) */ - validateOptions?: (options: ImageTransform) => ImageTransform; + validateOptions?: ( + options: ImageTransform, + serviceOptions: Record + ) => ImageTransform; } export type ExternalImageService = SharedServiceProps; @@ -63,14 +69,15 @@ export interface LocalImageService extends SharedServiceProps { * * In most cases, this will get query parameters using, for example, `params.get('width')` and return those. */ - parseURL: (url: URL) => LocalImageTransform | undefined; + parseURL: (url: URL, serviceOptions: Record) => LocalImageTransform | undefined; /** * Performs the image transformations on the input image and returns both the binary data and * final image format of the optimized image. */ transform: ( inputBuffer: Buffer, - transform: LocalImageTransform + transform: LocalImageTransform, + serviceOptions: Record ) => Promise<{ data: Buffer; format: ImageOutputFormat }>; } diff --git a/packages/astro/src/assets/vite-plugin-assets.ts b/packages/astro/src/assets/vite-plugin-assets.ts index a78def7e81e5..4d5a418d16ad 100644 --- a/packages/astro/src/assets/vite-plugin-assets.ts +++ b/packages/astro/src/assets/vite-plugin-assets.ts @@ -79,8 +79,12 @@ export default function assets({ load(id) { if (id === resolvedVirtualModuleId) { return ` - export { getImage, getConfiguredImageService, isLocalService } from "astro/assets"; + export { getConfiguredImageService, isLocalService } from "astro/assets"; + import { getImage as getImageInternal } from "astro/assets"; export { default as Image } from "astro/components/Image.astro"; + + export const imageServiceConfig = ${JSON.stringify(settings.config.image.serviceConfig)}; + export const getImage = async (options) => await getImageInternal(options, imageServiceConfig); `; } }, @@ -116,7 +120,10 @@ export default function assets({ } } - const transform = await globalThis.astroAsset.imageService.parseURL(url); + const transform = await globalThis.astroAsset.imageService.parseURL( + url, + settings.config.image.serviceConfig + ); if (transform === undefined) { error(logging, 'image', `Failed to parse transform for ${url}`); @@ -127,7 +134,11 @@ export default function assets({ let format: string = meta.format; if (transform) { - const result = await globalThis.astroAsset.imageService.transform(file, transform); + const result = await globalThis.astroAsset.imageService.transform( + file, + transform, + settings.config.image.serviceConfig + ); data = result.data; format = result.format; } diff --git a/packages/astro/src/core/config/schema.ts b/packages/astro/src/core/config/schema.ts index 1ba6fd829cde..84e51fb37d70 100644 --- a/packages/astro/src/core/config/schema.ts +++ b/packages/astro/src/core/config/schema.ts @@ -128,6 +128,7 @@ export const AstroConfigSchema = z.object({ z.literal('astro/assets/services/squoosh'), z.string(), ]), + serviceConfig: z.record(z.any()).optional().default({}), }) .default({ service: 'astro/assets/services/squoosh', From 4314ee782903bf4f603a6111dd8cdaec5f008b2d Mon Sep 17 00:00:00 2001 From: Princesseuh Date: Tue, 25 Apr 2023 12:53:21 +0200 Subject: [PATCH 2/7] fix: use same name everywhere --- packages/astro/src/assets/internal.ts | 8 ++++---- packages/astro/src/assets/services/service.ts | 13 +++++-------- 2 files changed, 9 insertions(+), 12 deletions(-) diff --git a/packages/astro/src/assets/internal.ts b/packages/astro/src/assets/internal.ts index 084231e3523d..d5c567dd651a 100644 --- a/packages/astro/src/assets/internal.ts +++ b/packages/astro/src/assets/internal.ts @@ -47,7 +47,7 @@ export async function getConfiguredImageService(): Promise { */ export async function getImage( options: ImageTransform, - serviceOptions: Record + serviceConfig: Record ): Promise { if (!options || typeof options !== 'object') { throw new AstroError({ @@ -58,10 +58,10 @@ export async function getImage( const service = await getConfiguredImageService(); const validatedOptions = service.validateOptions - ? service.validateOptions(options, serviceOptions) + ? service.validateOptions(options, serviceConfig) : options; - let imageURL = service.getURL(validatedOptions, serviceOptions); + let imageURL = service.getURL(validatedOptions, serviceConfig); // In build and for local services, we need to collect the requested parameters so we can generate the final images if (isLocalService(service) && globalThis.astroAsset.addStaticImage) { @@ -74,7 +74,7 @@ export async function getImage( src: imageURL, attributes: service.getHTMLAttributes !== undefined - ? service.getHTMLAttributes(validatedOptions, serviceOptions) + ? service.getHTMLAttributes(validatedOptions, serviceConfig) : {}, }; } diff --git a/packages/astro/src/assets/services/service.ts b/packages/astro/src/assets/services/service.ts index a7cc7a190173..5c19ce3d9f40 100644 --- a/packages/astro/src/assets/services/service.ts +++ b/packages/astro/src/assets/services/service.ts @@ -31,7 +31,7 @@ interface SharedServiceProps { * For external services, this should point to the URL your images are coming from, for instance, `/_vercel/image` * */ - getURL: (options: ImageTransform, serviceOptions: Record) => string; + getURL: (options: ImageTransform, serviceConfig: Record) => string; /** * Return any additional HTML attributes separate from `src` that your service requires to show the image properly. * @@ -40,7 +40,7 @@ interface SharedServiceProps { */ getHTMLAttributes?: ( options: ImageTransform, - serviceOptions: Record + serviceConfig: Record ) => Record; /** * Validate and return the options passed by the user. @@ -50,10 +50,7 @@ interface SharedServiceProps { * * This method should returns options, and can be used to set defaults (ex: a default output format to be used if the user didn't specify one.) */ - validateOptions?: ( - options: ImageTransform, - serviceOptions: Record - ) => ImageTransform; + validateOptions?: (options: ImageTransform, serviceConfig: Record) => ImageTransform; } export type ExternalImageService = SharedServiceProps; @@ -69,7 +66,7 @@ export interface LocalImageService extends SharedServiceProps { * * In most cases, this will get query parameters using, for example, `params.get('width')` and return those. */ - parseURL: (url: URL, serviceOptions: Record) => LocalImageTransform | undefined; + parseURL: (url: URL, serviceConfig: Record) => LocalImageTransform | undefined; /** * Performs the image transformations on the input image and returns both the binary data and * final image format of the optimized image. @@ -77,7 +74,7 @@ export interface LocalImageService extends SharedServiceProps { transform: ( inputBuffer: Buffer, transform: LocalImageTransform, - serviceOptions: Record + serviceConfig: Record ) => Promise<{ data: Buffer; format: ImageOutputFormat }>; } From aa9e243373f0b9572dc65972e57917aee91916a7 Mon Sep 17 00:00:00 2001 From: Princesseuh Date: Tue, 25 Apr 2023 13:29:55 +0200 Subject: [PATCH 3/7] test: add test --- packages/astro/test/core-image.test.js | 11 +++++++++++ packages/astro/test/fixtures/core-image/service.mjs | 3 ++- 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/packages/astro/test/core-image.test.js b/packages/astro/test/core-image.test.js index 49f6ce6ae11b..73eded661738 100644 --- a/packages/astro/test/core-image.test.js +++ b/packages/astro/test/core-image.test.js @@ -621,6 +621,9 @@ describe('astro:image', () => { }, image: { service: fileURLToPath(new URL('./fixtures/core-image/service.mjs', import.meta.url)), + serviceConfig: { + foo: 'bar', + }, }, }); devServer = await fixture.startDevServer(); @@ -641,5 +644,13 @@ describe('astro:image', () => { const $ = cheerio.load(html); expect($('img').attr('data-service')).to.equal('my-custom-service'); }); + + it('gets service config', async () => { + const response = await fixture.fetch('/'); + const html = await response.text(); + + const $ = cheerio.load(html); + expect($('#local img').attr('data-service-config')).to.equal('bar'); + }); }); }); diff --git a/packages/astro/test/fixtures/core-image/service.mjs b/packages/astro/test/fixtures/core-image/service.mjs index dfede13b3b30..646622f5166f 100644 --- a/packages/astro/test/fixtures/core-image/service.mjs +++ b/packages/astro/test/fixtures/core-image/service.mjs @@ -7,8 +7,9 @@ const service = { getURL(options) { return squoosh.getURL(options); }, - getHTMLAttributes(options) { + getHTMLAttributes(options, serviceConfig) { options['data-service'] = 'my-custom-service'; + options['data-service-config'] = serviceConfig.foo; return squoosh.getHTMLAttributes(options); }, parseURL(url) { From 372c6893400c14e8f747d63716add75a2480e20b Mon Sep 17 00:00:00 2001 From: Princesseuh Date: Tue, 25 Apr 2023 17:01:20 +0200 Subject: [PATCH 4/7] feat: update shape --- packages/astro/src/@types/astro.ts | 18 +++++++++++------- packages/astro/src/assets/internal.ts | 2 +- .../astro/src/assets/vite-plugin-assets.ts | 12 ++++++------ packages/astro/src/core/config/schema.ts | 16 +++++++++------- packages/astro/test/core-image.test.js | 5 +---- 5 files changed, 28 insertions(+), 25 deletions(-) diff --git a/packages/astro/src/@types/astro.ts b/packages/astro/src/@types/astro.ts index 43fc7ceb75d6..037d9c24295c 100644 --- a/packages/astro/src/@types/astro.ts +++ b/packages/astro/src/@types/astro.ts @@ -746,26 +746,30 @@ export interface AstroUserConfig { /** * @docs * @name image.service (Experimental) - * @type {'astro/assets/services/sharp' | 'astro/assets/services/squoosh' | string} - * @default `'astro/assets/services/squoosh'` + * @type {{entrypoint: 'astro/assets/services/sharp' | 'astro/assets/services/squoosh' | string, config: Record}} + * @default `{entrypoint: 'astro/assets/services/squoosh', config?: {}}` * @version 2.1.0 * @description * Set which image service is used for Astro’s experimental assets support. * - * The value should be a module specifier for the image service to use: - * either one of Astro’s two built-in services, or a third-party implementation. + * The value should be an object with an entrypoint for the image service to use and optionally, a config object to pass to the service. + * + * The service entrypoint can be either one of the included services, or a third-party package. * * ```js * { * image: { * // Example: Enable the Sharp-based image service - * service: 'astro/assets/services/sharp', + * service: { entrypoint: 'astro/assets/services/sharp' }, * }, * } * ``` */ - // eslint-disable-next-line @typescript-eslint/ban-types - service: 'astro/assets/services/sharp' | 'astro/assets/services/squoosh' | (string & {}); + service: { + // eslint-disable-next-line @typescript-eslint/ban-types + entrypoint: 'astro/assets/services/sharp' | 'astro/assets/services/squoosh' | (string & {}); + config?: Record; + }; }; /** diff --git a/packages/astro/src/assets/internal.ts b/packages/astro/src/assets/internal.ts index d5c567dd651a..945b5a3e862d 100644 --- a/packages/astro/src/assets/internal.ts +++ b/packages/astro/src/assets/internal.ts @@ -131,7 +131,7 @@ export async function generateImage( const resultData = await imageService.transform( fileData, { ...options, src: originalImagePath }, - buildOpts.settings.config.image.serviceConfig + buildOpts.settings.config.image.service.config ); const finalFileURL = new URL('.' + filepath, clientRoot); diff --git a/packages/astro/src/assets/vite-plugin-assets.ts b/packages/astro/src/assets/vite-plugin-assets.ts index 4d5a418d16ad..3c0a94d2ab3c 100644 --- a/packages/astro/src/assets/vite-plugin-assets.ts +++ b/packages/astro/src/assets/vite-plugin-assets.ts @@ -38,7 +38,7 @@ export default function assets({ const adapterName = settings.config.adapter?.name; if ( ['astro/assets/services/sharp', 'astro/assets/services/squoosh'].includes( - settings.config.image.service + settings.config.image.service.entrypoint ) && adapterName && UNSUPPORTED_ADAPTERS.has(adapterName) @@ -70,7 +70,7 @@ export default function assets({ }, async resolveId(id) { if (id === VIRTUAL_SERVICE_ID) { - return await this.resolve(settings.config.image.service); + return await this.resolve(settings.config.image.service.entrypoint); } if (id === VIRTUAL_MODULE_ID) { return resolvedVirtualModuleId; @@ -83,7 +83,7 @@ export default function assets({ import { getImage as getImageInternal } from "astro/assets"; export { default as Image } from "astro/components/Image.astro"; - export const imageServiceConfig = ${JSON.stringify(settings.config.image.serviceConfig)}; + export const imageServiceConfig = ${JSON.stringify(settings.config.image.service.config)}; export const getImage = async (options) => await getImageInternal(options, imageServiceConfig); `; } @@ -122,7 +122,7 @@ export default function assets({ const transform = await globalThis.astroAsset.imageService.parseURL( url, - settings.config.image.serviceConfig + settings.config.image.service.config ); if (transform === undefined) { @@ -137,7 +137,7 @@ export default function assets({ const result = await globalThis.astroAsset.imageService.transform( file, transform, - settings.config.image.serviceConfig + settings.config.image.service.config ); data = result.data; format = result.format; @@ -166,7 +166,7 @@ export default function assets({ >(); } - const hash = hashTransform(options, settings.config.image.service); + const hash = hashTransform(options, settings.config.image.service.entrypoint); let filePath: string; if (globalThis.astroAsset.staticImages.has(hash)) { diff --git a/packages/astro/src/core/config/schema.ts b/packages/astro/src/core/config/schema.ts index 84e51fb37d70..4c55dc5b63a2 100644 --- a/packages/astro/src/core/config/schema.ts +++ b/packages/astro/src/core/config/schema.ts @@ -123,15 +123,17 @@ export const AstroConfigSchema = z.object({ ), image: z .object({ - service: z.union([ - z.literal('astro/assets/services/sharp'), - z.literal('astro/assets/services/squoosh'), - z.string(), - ]), - serviceConfig: z.record(z.any()).optional().default({}), + service: z.object({ + entrypoint: z.union([ + z.literal('astro/assets/services/sharp'), + z.literal('astro/assets/services/squoosh'), + z.string(), + ]), + config: z.record(z.any()).default({}), + }), }) .default({ - service: 'astro/assets/services/squoosh', + service: { entrypoint: 'astro/assets/services/squoosh', config: {} }, }), markdown: z .object({ diff --git a/packages/astro/test/core-image.test.js b/packages/astro/test/core-image.test.js index 73eded661738..7865094e56b8 100644 --- a/packages/astro/test/core-image.test.js +++ b/packages/astro/test/core-image.test.js @@ -620,10 +620,7 @@ describe('astro:image', () => { assets: true, }, image: { - service: fileURLToPath(new URL('./fixtures/core-image/service.mjs', import.meta.url)), - serviceConfig: { - foo: 'bar', - }, + service: { entrypoint: fileURLToPath(new URL('./fixtures/core-image/service.mjs', import.meta.url)), config: {foo: 'bar'} } }, }); devServer = await fixture.startDevServer(); From e0e7a3d1f3c4c70b17f9a68946dd255fe4e3603e Mon Sep 17 00:00:00 2001 From: Princesseuh Date: Tue, 25 Apr 2023 18:26:49 +0200 Subject: [PATCH 5/7] feat: add utilities to configure services --- packages/astro/src/@types/astro.ts | 12 +++++++----- packages/astro/src/assets/index.ts | 16 ++++++++++++++++ 2 files changed, 23 insertions(+), 5 deletions(-) diff --git a/packages/astro/src/@types/astro.ts b/packages/astro/src/@types/astro.ts index 037d9c24295c..674445cd71cd 100644 --- a/packages/astro/src/@types/astro.ts +++ b/packages/astro/src/@types/astro.ts @@ -335,6 +335,12 @@ export interface ViteUserConfig extends vite.UserConfig { ssr?: vite.SSROptions; } +export interface ImageServiceConfig { + // eslint-disable-next-line @typescript-eslint/ban-types + entrypoint: 'astro/assets/services/sharp' | 'astro/assets/services/squoosh' | (string & {}); + config?: Record; +} + /** * Astro User Config * Docs: https://docs.astro.build/reference/configuration-reference/ @@ -765,11 +771,7 @@ export interface AstroUserConfig { * } * ``` */ - service: { - // eslint-disable-next-line @typescript-eslint/ban-types - entrypoint: 'astro/assets/services/sharp' | 'astro/assets/services/squoosh' | (string & {}); - config?: Record; - }; + service: ImageServiceConfig; }; /** diff --git a/packages/astro/src/assets/index.ts b/packages/astro/src/assets/index.ts index 6b792fa97db3..cbc7a605de80 100644 --- a/packages/astro/src/assets/index.ts +++ b/packages/astro/src/assets/index.ts @@ -1,5 +1,21 @@ +import type { ImageServiceConfig } from '../@types/astro.js'; + export { getConfiguredImageService, getImage } from './internal.js'; export { baseService, isLocalService } from './services/service.js'; export { type LocalImageProps, type RemoteImageProps } from './types.js'; export { emitESMImage } from './utils/emitAsset.js'; export { imageMetadata } from './utils/metadata.js'; + +export function getSharpImageService(): ImageServiceConfig { + return { + entrypoint: 'astro/assets/services/sharp', + config: {}, + }; +} + +export function getSquooshImageService(): ImageServiceConfig { + return { + entrypoint: 'astro/assets/services/squoosh', + config: {}, + }; +} From 7c562c70044c315686906ac9108d9ca55b6ca899 Mon Sep 17 00:00:00 2001 From: Princesseuh Date: Tue, 25 Apr 2023 18:32:58 +0200 Subject: [PATCH 6/7] chore: changeset --- .changeset/tall-news-hang.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/tall-news-hang.md diff --git a/.changeset/tall-news-hang.md b/.changeset/tall-news-hang.md new file mode 100644 index 000000000000..0887bbec1b22 --- /dev/null +++ b/.changeset/tall-news-hang.md @@ -0,0 +1,5 @@ +--- +'astro': patch +--- + +Update `experimental.assets`'s `image.service` configuration to allow for a config option in addition to an entrypoint From 4b6915f40ff7ceec9a91733a41e4619a42a0a1db Mon Sep 17 00:00:00 2001 From: Princesseuh Date: Thu, 27 Apr 2023 11:14:53 +0200 Subject: [PATCH 7/7] fix: remove get --- packages/astro/src/assets/index.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/astro/src/assets/index.ts b/packages/astro/src/assets/index.ts index cbc7a605de80..bab74a81561b 100644 --- a/packages/astro/src/assets/index.ts +++ b/packages/astro/src/assets/index.ts @@ -6,14 +6,14 @@ export { type LocalImageProps, type RemoteImageProps } from './types.js'; export { emitESMImage } from './utils/emitAsset.js'; export { imageMetadata } from './utils/metadata.js'; -export function getSharpImageService(): ImageServiceConfig { +export function sharpImageService(): ImageServiceConfig { return { entrypoint: 'astro/assets/services/sharp', config: {}, }; } -export function getSquooshImageService(): ImageServiceConfig { +export function squooshImageService(): ImageServiceConfig { return { entrypoint: 'astro/assets/services/squoosh', config: {},