Skip to content

Commit

Permalink
feat: add experimental responsive images config option (#12378)
Browse files Browse the repository at this point in the history
* feat: add experimental responsive images config option

* Apply suggestions from code review

Co-authored-by: Sarah Rainsberger <[email protected]>

* Update config types

* Move config into `images`

* Move jsdocs

---------

Co-authored-by: Sarah Rainsberger <[email protected]>
  • Loading branch information
ascorbic and sarah11918 authored Nov 6, 2024
1 parent 5f328e0 commit 84492b0
Show file tree
Hide file tree
Showing 5 changed files with 99 additions and 4 deletions.
4 changes: 3 additions & 1 deletion packages/astro/client.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,9 @@ declare module 'astro:assets' {
getImage: (
options: import('./dist/assets/types.js').UnresolvedImageTransform,
) => Promise<import('./dist/assets/types.js').GetImageResult>;
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;
Expand Down
7 changes: 7 additions & 0 deletions packages/astro/src/assets/types.ts
Original file line number Diff line number Diff line change
@@ -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';

Expand Down Expand Up @@ -151,6 +152,12 @@ type ImageSharedProps<T> = T & {
* ```
*/
quality?: ImageQuality;

layout?: ImageLayout;

fit?: 'fill' | 'contain' | 'cover' | 'none' | 'scale-down' | (string & {});

position?: string;
} & (
| {
/**
Expand Down
4 changes: 2 additions & 2 deletions packages/astro/src/assets/vite-plugin-assets.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
22 changes: 21 additions & 1 deletion packages/astro/src/core/config/schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,7 @@ export const ASTRO_CONFIG_DEFAULTS = {
experimental: {
clientPrerender: false,
contentIntellisense: false,
responsiveImages: false,
},
} satisfies AstroUserConfig & { server: { open: boolean } };

Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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.`,
Expand Down Expand Up @@ -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) {
Expand All @@ -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;
Expand Down
66 changes: 66 additions & 0 deletions packages/astro/src/types/public/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1070,6 +1070,37 @@ export interface ViteUserConfig extends OriginalViteUserConfig {
*/
remotePatterns?: Partial<RemotePattern>[];

/**
* @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;
};

/**
Expand Down Expand Up @@ -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 `<Image />` 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';
* ---
* <Image src={myImage} alt="A description of my image." layout='fixed' />
* ```
*
*/

responsiveImages?: boolean;
};
}

export type ImageLayout = 'responsive' | 'fixed' | 'full-width' | 'none';

/**
* Resolved Astro Config
*
Expand Down

0 comments on commit 84492b0

Please sign in to comment.