diff --git a/packages/astro/client.d.ts b/packages/astro/client.d.ts index 19e10694f082..a2e4cf0eb9be 100644 --- a/packages/astro/client.d.ts +++ b/packages/astro/client.d.ts @@ -47,7 +47,9 @@ declare module 'astro:assets' { getImage: ( options: import('./dist/assets/types.js').UnresolvedImageTransform, ) => Promise; - imageConfig: import('./dist/types/public/config.js').AstroConfig['image']; + imageConfig: import('./dist/types/public/config.js').AstroConfig['image'] & { + experimentalResponsiveImages: boolean; + }; getConfiguredImageService: typeof import('./dist/assets/index.js').getConfiguredImageService; inferRemoteSize: typeof import('./dist/assets/utils/index.js').inferRemoteSize; Image: typeof import('./components/Image.astro').default; diff --git a/packages/astro/src/assets/types.ts b/packages/astro/src/assets/types.ts index 6de28d43c6b0..125002b89f3a 100644 --- a/packages/astro/src/assets/types.ts +++ b/packages/astro/src/assets/types.ts @@ -1,4 +1,5 @@ import type { WithRequired } from '../type-utils.js'; +import type { ImageLayout } from '../types/public/index.js'; import type { VALID_INPUT_FORMATS, VALID_OUTPUT_FORMATS } from './consts.js'; import type { ImageService } from './services/service.js'; @@ -151,6 +152,12 @@ type ImageSharedProps = T & { * ``` */ quality?: ImageQuality; + + layout?: ImageLayout; + + fit?: 'fill' | 'contain' | 'cover' | 'none' | 'scale-down' | (string & {}); + + position?: string; } & ( | { /** diff --git a/packages/astro/src/assets/vite-plugin-assets.ts b/packages/astro/src/assets/vite-plugin-assets.ts index 037fae725a97..8214ce6657f6 100644 --- a/packages/astro/src/assets/vite-plugin-assets.ts +++ b/packages/astro/src/assets/vite-plugin-assets.ts @@ -115,14 +115,14 @@ export default function assets({ settings }: { settings: AstroSettings }): vite. }, load(id) { if (id === resolvedVirtualModuleId) { - return ` + return /* ts */ ` export { getConfiguredImageService, isLocalService } from "astro/assets"; import { getImage as getImageInternal } from "astro/assets"; export { default as Image } from "astro/components/Image.astro"; export { default as Picture } from "astro/components/Picture.astro"; export { inferRemoteSize } from "astro/assets/utils/inferRemoteSize.js"; - export const imageConfig = ${JSON.stringify(settings.config.image)}; + export const imageConfig = ${JSON.stringify({ ...settings.config.image, experimentalResponsiveImages: settings.config.experimental.responsiveImages })}; // This is used by the @astrojs/node integration to locate images. // It's unused on other platforms, but on some platforms like Netlify (and presumably also Vercel) // new URL("dist/...") is interpreted by the bundler as a signal to include that directory diff --git a/packages/astro/src/core/config/schema.ts b/packages/astro/src/core/config/schema.ts index 48af43339c96..775c577520cd 100644 --- a/packages/astro/src/core/config/schema.ts +++ b/packages/astro/src/core/config/schema.ts @@ -95,6 +95,7 @@ export const ASTRO_CONFIG_DEFAULTS = { experimental: { clientPrerender: false, contentIntellisense: false, + responsiveImages: false, }, } satisfies AstroUserConfig & { server: { open: boolean } }; @@ -284,6 +285,9 @@ export const AstroConfigSchema = z.object({ }), ) .default([]), + experimentalLayout: z.enum(['responsive', 'fixed', 'full-width', 'none']).optional(), + experimentalObjectFit: z.string().optional(), + experimentalObjectPosition: z.string().optional(), }) .default(ASTRO_CONFIG_DEFAULTS.image), devToolbar: z @@ -525,6 +529,10 @@ export const AstroConfigSchema = z.object({ .boolean() .optional() .default(ASTRO_CONFIG_DEFAULTS.experimental.contentIntellisense), + responsiveImages: z + .boolean() + .optional() + .default(ASTRO_CONFIG_DEFAULTS.experimental.responsiveImages), }) .strict( `Invalid or outdated experimental feature.\nCheck for incorrect spelling or outdated Astro version.\nSee https://docs.astro.build/en/reference/configuration-reference/#experimental-flags for a list of all current experiments.`, @@ -688,7 +696,7 @@ export function createRelativeSchema(cmd: string, fileProtocolRoot: string) { 'The value of `outDir` must not point to a path within the folder set as `publicDir`, this will cause an infinite loop', }) .superRefine((configuration, ctx) => { - const { site, i18n, output } = configuration; + const { site, i18n, output, image, experimental } = configuration; const hasDomains = i18n?.domains ? Object.keys(i18n.domains).length > 0 : false; if (hasDomains) { if (!site) { @@ -705,6 +713,18 @@ export function createRelativeSchema(cmd: string, fileProtocolRoot: string) { }); } } + if ( + !experimental.responsiveImages && + (image.experimentalLayout || + image.experimentalObjectFit || + image.experimentalObjectPosition) + ) { + ctx.addIssue({ + code: z.ZodIssueCode.custom, + message: + 'The `experimentalLayout`, `experimentalObjectFit`, and `experimentalObjectPosition` options are only available when `experimental.responsiveImages` is enabled.', + }); + } }); return AstroConfigRelativeSchema; diff --git a/packages/astro/src/types/public/config.ts b/packages/astro/src/types/public/config.ts index 1f8318726b19..ce4052b4370f 100644 --- a/packages/astro/src/types/public/config.ts +++ b/packages/astro/src/types/public/config.ts @@ -1070,6 +1070,37 @@ export interface ViteUserConfig extends OriginalViteUserConfig { */ remotePatterns?: Partial[]; + + /** + * @docs + * @name image.experimentalLayout + * @default `undefined` + * @description + * The default layout type for responsive images. Can be overridden by the `layout` prop on the image component. + * Requires the `experimental.responsiveImages` flag to be enabled. + * - `responsive` - The image will scale to fit the container, maintaining its aspect ratio, but will not exceed the specified dimensions. + * - `fixed` - The image will maintain its original dimensions. + * - `full-width` - The image will scale to fit the container, maintaining its aspect ratio. + */ + experimentalLayout?: ImageLayout | undefined; + /** + * @docs + * @name image.experimentalObjectFit + * @default `"cover"` + * @description + * The default object-fit value for responsive images. Can be overridden by the `fit` prop on the image component. + * Requires the `experimental.responsiveImages` flag to be enabled. + */ + experimentalObjectFit?: 'contain' | 'cover' | 'fill' | 'none' | 'scale-down' | (string & {}); + /** + * @docs + * @name image.experimentalObjectPosition + * @default `"center"` + * @description + * The default object-position value for responsive images. Can be overridden by the `position` prop on the image component. + * Requires the `experimental.responsiveImages` flag to be enabled. + */ + experimentalObjectPosition?: string; }; /** @@ -1699,9 +1730,44 @@ export interface ViteUserConfig extends OriginalViteUserConfig { * To use this feature with the Astro VS Code extension, you must also enable the `astro.content-intellisense` option in your VS Code settings. For editors using the Astro language server directly, pass the `contentIntellisense: true` initialization parameter to enable this feature. */ contentIntellisense?: boolean; + + /** + * @docs + * @name experimental.responsiveImages + * @type {boolean} + * @default `undefined` + * @version 5.0.0 + * @description + * + * Enables and configures automatic responsive image options for images in your project. Set to `true` (for no default option passed to your images) or an object with default responsive image configuration options. + * + * ```js + * { + * experimental: { + * responsiveImages: { + * layout: 'responsive', + * }, + * } + * ``` + * + * Then, you can add a `layout` option to any `` component when needed to override your default configuration: `responsive`, `fixed`, `full-width`, or `none`. This attribute is required to transform your images if `responsiveImages.layout` is not configured. Images with a layout value of `undefined` or `none` will not be transformed. + * + * ```astro + * --- + * import { Image } from 'astro:assets'; + * import myImage from '../assets/my_image.png'; + * --- + * A description of my image. + * ``` + * + */ + + responsiveImages?: boolean; }; } +export type ImageLayout = 'responsive' | 'fixed' | 'full-width' | 'none'; + /** * Resolved Astro Config *