astro 5.0.0-beta.8 → 5.0.0-beta.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/client.d.ts +24 -5
- package/components/Image.astro +26 -3
- package/components/Picture.astro +33 -6
- package/components/image.css +17 -0
- package/dist/actions/integration.js +3 -2
- package/dist/actions/runtime/virtual/server.js +5 -2
- package/dist/actions/utils.d.ts +1 -1
- package/dist/actions/utils.js +2 -0
- package/dist/assets/build/generate.js +2 -2
- package/dist/assets/consts.js +9 -1
- package/dist/assets/endpoint/config.js +2 -1
- package/dist/assets/internal.d.ts +5 -1
- package/dist/assets/internal.js +66 -12
- package/dist/assets/layout.d.ts +25 -0
- package/dist/assets/layout.js +103 -0
- package/dist/assets/runtime.d.ts +10 -0
- package/dist/assets/runtime.js +69 -0
- package/dist/assets/services/service.d.ts +3 -1
- package/dist/assets/services/service.js +57 -37
- package/dist/assets/services/sharp.js +28 -3
- package/dist/assets/types.d.ts +53 -0
- package/dist/assets/utils/imageAttributes.d.ts +10 -0
- package/dist/assets/utils/imageAttributes.js +33 -0
- package/dist/assets/utils/imageKind.js +1 -1
- package/dist/assets/utils/index.d.ts +1 -0
- package/dist/assets/utils/index.js +2 -0
- package/dist/assets/utils/node/emitAsset.d.ts +4 -1
- package/dist/assets/utils/node/emitAsset.js +3 -0
- package/dist/assets/utils/svg.d.ts +5 -0
- package/dist/assets/utils/svg.js +22 -0
- package/dist/assets/vite-plugin-assets.js +20 -10
- package/dist/config/index.js +2 -2
- package/dist/container/index.js +2 -1
- package/dist/content/content-layer.js +3 -3
- package/dist/content/loaders/glob.js +2 -1
- package/dist/content/runtime.d.ts +1 -0
- package/dist/content/runtime.js +10 -2
- package/dist/content/server-listeners.js +10 -1
- package/dist/content/types-generator.js +23 -30
- package/dist/content/utils.d.ts +1 -1
- package/dist/content/utils.js +12 -8
- package/dist/core/app/pipeline.js +0 -1
- package/dist/core/base-pipeline.d.ts +1 -2
- package/dist/core/base-pipeline.js +1 -7
- package/dist/core/build/generate.js +2 -7
- package/dist/core/build/pipeline.js +2 -2
- package/dist/core/build/plugins/plugin-ssr.js +0 -1
- package/dist/core/config/schema.d.ts +148 -0
- package/dist/core/config/schema.js +30 -4
- package/dist/core/constants.js +1 -1
- package/dist/core/dev/container.js +3 -1
- package/dist/core/dev/dev.js +1 -1
- package/dist/core/errors/errors-data.d.ts +5 -15
- package/dist/core/errors/errors-data.js +1 -7
- package/dist/core/messages.js +2 -2
- package/dist/core/middleware/vite-plugin.js +1 -13
- package/dist/core/render-context.js +3 -5
- package/dist/core/routing/astro-designed-error-pages.js +4 -2
- package/dist/core/routing/manifest/create.d.ts +3 -1
- package/dist/core/routing/manifest/create.js +12 -5
- package/dist/core/routing/manifest/serialization.js +2 -1
- package/dist/core/routing/match.d.ts +7 -0
- package/dist/core/routing/match.js +4 -0
- package/dist/core/server-islands/endpoint.js +2 -1
- package/dist/core/sync/index.js +12 -3
- package/dist/integrations/hooks.d.ts +5 -0
- package/dist/integrations/hooks.js +41 -1
- package/dist/runtime/server/render/server-islands.js +15 -14
- package/dist/runtime/server/render/util.d.ts +1 -0
- package/dist/runtime/server/render/util.js +1 -0
- package/dist/transitions/router.js +1 -1
- package/dist/types/astro.d.ts +3 -3
- package/dist/types/public/config.d.ts +266 -6
- package/dist/types/public/integrations.d.ts +32 -10
- package/dist/types/public/internal.d.ts +13 -0
- package/dist/vite-plugin-astro-server/plugin.js +28 -10
- package/package.json +6 -4
package/client.d.ts
CHANGED
|
@@ -47,7 +47,9 @@ declare module 'astro:assets' {
|
|
|
47
47
|
getImage: (
|
|
48
48
|
options: import('./dist/assets/types.js').UnresolvedImageTransform,
|
|
49
49
|
) => Promise<import('./dist/assets/types.js').GetImageResult>;
|
|
50
|
-
imageConfig: import('./dist/types/public/config.js').AstroConfig['image']
|
|
50
|
+
imageConfig: import('./dist/types/public/config.js').AstroConfig['image'] & {
|
|
51
|
+
experimentalResponsiveImages: boolean;
|
|
52
|
+
};
|
|
51
53
|
getConfiguredImageService: typeof import('./dist/assets/index.js').getConfiguredImageService;
|
|
52
54
|
inferRemoteSize: typeof import('./dist/assets/utils/index.js').inferRemoteSize;
|
|
53
55
|
Image: typeof import('./components/Image.astro').default;
|
|
@@ -101,14 +103,31 @@ declare module '*.webp' {
|
|
|
101
103
|
const metadata: ImageMetadata;
|
|
102
104
|
export default metadata;
|
|
103
105
|
}
|
|
104
|
-
declare module '*.svg' {
|
|
105
|
-
const metadata: ImageMetadata;
|
|
106
|
-
export default metadata;
|
|
107
|
-
}
|
|
108
106
|
declare module '*.avif' {
|
|
109
107
|
const metadata: ImageMetadata;
|
|
110
108
|
export default metadata;
|
|
111
109
|
}
|
|
110
|
+
declare module '*.svg' {
|
|
111
|
+
type Props = {
|
|
112
|
+
/**
|
|
113
|
+
* Accesible, short-text description
|
|
114
|
+
*
|
|
115
|
+
* {@link https://developer.mozilla.org/en-US/docs/Web/SVG/Element/title|MDN Reference}
|
|
116
|
+
*/
|
|
117
|
+
title?: string;
|
|
118
|
+
/**
|
|
119
|
+
* Shorthand for setting the `height` and `width` properties
|
|
120
|
+
*/
|
|
121
|
+
size?: number | string;
|
|
122
|
+
/**
|
|
123
|
+
* Override the default rendering mode for SVGs
|
|
124
|
+
*/
|
|
125
|
+
mode?: import('./dist/assets/utils/svg.js').SvgRenderMode;
|
|
126
|
+
} & astroHTML.JSX.SVGAttributes;
|
|
127
|
+
|
|
128
|
+
const Component: ((_props: Props) => any) & ImageMetadata;
|
|
129
|
+
export default Component;
|
|
130
|
+
}
|
|
112
131
|
|
|
113
132
|
declare module 'astro:transitions' {
|
|
114
133
|
type TransitionModule = typeof import('./dist/virtual-modules/transitions.js');
|
package/components/Image.astro
CHANGED
|
@@ -1,7 +1,10 @@
|
|
|
1
1
|
---
|
|
2
|
-
import { type LocalImageProps, type RemoteImageProps, getImage } from 'astro:assets';
|
|
2
|
+
import { type LocalImageProps, type RemoteImageProps, getImage, imageConfig } from 'astro:assets';
|
|
3
|
+
import type { UnresolvedImageTransform } from '../dist/assets/types';
|
|
4
|
+
import { applyResponsiveAttributes } from '../dist/assets/utils/imageAttributes.js';
|
|
3
5
|
import { AstroError, AstroErrorData } from '../dist/core/errors/index.js';
|
|
4
6
|
import type { HTMLAttributes } from '../types';
|
|
7
|
+
import './image.css';
|
|
5
8
|
|
|
6
9
|
// The TypeScript diagnostic for JSX props uses the last member of the union to suggest props, so it would be better for
|
|
7
10
|
// LocalImageProps to be last. Unfortunately, when we do this the error messages that remote images get are complete nonsense
|
|
@@ -23,7 +26,17 @@ if (typeof props.height === 'string') {
|
|
|
23
26
|
props.height = parseInt(props.height);
|
|
24
27
|
}
|
|
25
28
|
|
|
26
|
-
const
|
|
29
|
+
const layout = props.layout ?? imageConfig.experimentalLayout ?? 'none';
|
|
30
|
+
const useResponsive = imageConfig.experimentalResponsiveImages && layout !== 'none';
|
|
31
|
+
|
|
32
|
+
if (useResponsive) {
|
|
33
|
+
// Apply defaults from imageConfig if not provided
|
|
34
|
+
props.layout ??= imageConfig.experimentalLayout;
|
|
35
|
+
props.fit ??= imageConfig.experimentalObjectFit ?? 'cover';
|
|
36
|
+
props.position ??= imageConfig.experimentalObjectPosition ?? 'center';
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const image = await getImage(props as UnresolvedImageTransform);
|
|
27
40
|
|
|
28
41
|
const additionalAttributes: HTMLAttributes<'img'> = {};
|
|
29
42
|
if (image.srcSet.values.length > 0) {
|
|
@@ -33,6 +46,16 @@ if (image.srcSet.values.length > 0) {
|
|
|
33
46
|
if (import.meta.env.DEV) {
|
|
34
47
|
additionalAttributes['data-image-component'] = 'true';
|
|
35
48
|
}
|
|
49
|
+
|
|
50
|
+
const attributes = useResponsive
|
|
51
|
+
? applyResponsiveAttributes({
|
|
52
|
+
layout,
|
|
53
|
+
image,
|
|
54
|
+
props,
|
|
55
|
+
additionalAttributes,
|
|
56
|
+
})
|
|
57
|
+
: { ...additionalAttributes, ...image.attributes };
|
|
36
58
|
---
|
|
37
59
|
|
|
38
|
-
|
|
60
|
+
{/* Applying class outside of the spread prevents it from applying unnecessary astro-* classes */}
|
|
61
|
+
<img src={image.src} {...attributes} class={attributes.class} />
|
package/components/Picture.astro
CHANGED
|
@@ -1,10 +1,16 @@
|
|
|
1
1
|
---
|
|
2
|
-
import { type LocalImageProps, type RemoteImageProps, getImage } from 'astro:assets';
|
|
2
|
+
import { type LocalImageProps, type RemoteImageProps, getImage, imageConfig } from 'astro:assets';
|
|
3
3
|
import * as mime from 'mrmime';
|
|
4
|
+
import { applyResponsiveAttributes } from '../dist/assets/utils/imageAttributes';
|
|
4
5
|
import { isESMImportedImage, resolveSrc } from '../dist/assets/utils/imageKind';
|
|
5
6
|
import { AstroError, AstroErrorData } from '../dist/core/errors/index.js';
|
|
6
|
-
import type {
|
|
7
|
+
import type {
|
|
8
|
+
GetImageResult,
|
|
9
|
+
ImageOutputFormat,
|
|
10
|
+
UnresolvedImageTransform,
|
|
11
|
+
} from '../dist/types/public/index.js';
|
|
7
12
|
import type { HTMLAttributes } from '../types';
|
|
13
|
+
import './image.css';
|
|
8
14
|
|
|
9
15
|
type Props = (LocalImageProps | RemoteImageProps) & {
|
|
10
16
|
formats?: ImageOutputFormat[];
|
|
@@ -37,6 +43,17 @@ if (scopedStyleClass) {
|
|
|
37
43
|
pictureAttributes.class = scopedStyleClass;
|
|
38
44
|
}
|
|
39
45
|
}
|
|
46
|
+
|
|
47
|
+
const layout = props.layout ?? imageConfig.experimentalLayout ?? 'none';
|
|
48
|
+
const useResponsive = imageConfig.experimentalResponsiveImages && layout !== 'none';
|
|
49
|
+
|
|
50
|
+
if (useResponsive) {
|
|
51
|
+
// Apply defaults from imageConfig if not provided
|
|
52
|
+
props.layout ??= imageConfig.experimentalLayout;
|
|
53
|
+
props.fit ??= imageConfig.experimentalObjectFit ?? 'cover';
|
|
54
|
+
props.position ??= imageConfig.experimentalObjectPosition ?? 'center';
|
|
55
|
+
}
|
|
56
|
+
|
|
40
57
|
for (const key in props) {
|
|
41
58
|
if (key.startsWith('data-astro-cid')) {
|
|
42
59
|
pictureAttributes[key] = props[key];
|
|
@@ -53,7 +70,7 @@ const optimizedImages: GetImageResult[] = await Promise.all(
|
|
|
53
70
|
format: format,
|
|
54
71
|
widths: props.widths,
|
|
55
72
|
densities: props.densities,
|
|
56
|
-
}),
|
|
73
|
+
} as UnresolvedImageTransform),
|
|
57
74
|
),
|
|
58
75
|
);
|
|
59
76
|
|
|
@@ -71,7 +88,7 @@ const fallbackImage = await getImage({
|
|
|
71
88
|
format: resultFallbackFormat,
|
|
72
89
|
widths: props.widths,
|
|
73
90
|
densities: props.densities,
|
|
74
|
-
});
|
|
91
|
+
} as UnresolvedImageTransform);
|
|
75
92
|
|
|
76
93
|
const imgAdditionalAttributes: HTMLAttributes<'img'> = {};
|
|
77
94
|
const sourceAdditionalAttributes: HTMLAttributes<'source'> = {};
|
|
@@ -85,6 +102,15 @@ if (fallbackImage.srcSet.values.length > 0) {
|
|
|
85
102
|
imgAdditionalAttributes.srcset = fallbackImage.srcSet.attribute;
|
|
86
103
|
}
|
|
87
104
|
|
|
105
|
+
const attributes = useResponsive
|
|
106
|
+
? applyResponsiveAttributes({
|
|
107
|
+
layout,
|
|
108
|
+
image: fallbackImage,
|
|
109
|
+
props,
|
|
110
|
+
additionalAttributes: imgAdditionalAttributes,
|
|
111
|
+
})
|
|
112
|
+
: { ...imgAdditionalAttributes, ...fallbackImage.attributes };
|
|
113
|
+
|
|
88
114
|
if (import.meta.env.DEV) {
|
|
89
115
|
imgAdditionalAttributes['data-image-component'] = 'true';
|
|
90
116
|
}
|
|
@@ -94,7 +120,7 @@ if (import.meta.env.DEV) {
|
|
|
94
120
|
{
|
|
95
121
|
Object.entries(optimizedImages).map(([_, image]) => {
|
|
96
122
|
const srcsetAttribute =
|
|
97
|
-
props.densities || (!props.densities && !props.widths)
|
|
123
|
+
props.densities || (!props.densities && !props.widths && !useResponsive)
|
|
98
124
|
? `${image.src}${image.srcSet.values.length > 0 ? ', ' + image.srcSet.attribute : ''}`
|
|
99
125
|
: image.srcSet.attribute;
|
|
100
126
|
return (
|
|
@@ -106,5 +132,6 @@ if (import.meta.env.DEV) {
|
|
|
106
132
|
);
|
|
107
133
|
})
|
|
108
134
|
}
|
|
109
|
-
|
|
135
|
+
{/* Applying class outside of the spread prevents it from applying unnecessary astro-* classes */}
|
|
136
|
+
<img src={fallbackImage.src} {...attributes} class={attributes.class} />
|
|
110
137
|
</picture>
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
[data-astro-image] {
|
|
2
|
+
width: 100%;
|
|
3
|
+
height: auto;
|
|
4
|
+
object-fit: var(--fit);
|
|
5
|
+
object-position: var(--pos);
|
|
6
|
+
aspect-ratio: var(--w) / var(--h);
|
|
7
|
+
}
|
|
8
|
+
/* Styles for responsive layout */
|
|
9
|
+
[data-astro-image='responsive'] {
|
|
10
|
+
max-width: calc(var(--w) * 1px);
|
|
11
|
+
max-height: calc(var(--h) * 1px);
|
|
12
|
+
}
|
|
13
|
+
/* Styles for fixed layout */
|
|
14
|
+
[data-astro-image='fixed'] {
|
|
15
|
+
width: calc(var(--w) * 1px);
|
|
16
|
+
height: calc(var(--h) * 1px);
|
|
17
|
+
}
|
|
@@ -9,10 +9,11 @@ function astroIntegrationActionsRouteHandler({
|
|
|
9
9
|
name: VIRTUAL_MODULE_ID,
|
|
10
10
|
hooks: {
|
|
11
11
|
async "astro:config:setup"(params) {
|
|
12
|
-
|
|
12
|
+
settings.injectedRoutes.push({
|
|
13
13
|
pattern: ACTION_RPC_ROUTE_PATTERN,
|
|
14
14
|
entrypoint: "astro/actions/runtime/route.js",
|
|
15
|
-
prerender: false
|
|
15
|
+
prerender: false,
|
|
16
|
+
origin: "internal"
|
|
16
17
|
});
|
|
17
18
|
params.addMiddleware({
|
|
18
19
|
entrypoint: "astro/actions/runtime/middleware.js",
|
|
@@ -3,8 +3,10 @@ import { ActionCalledFromServerError } from "../../../core/errors/errors-data.js
|
|
|
3
3
|
import { AstroError } from "../../../core/errors/errors.js";
|
|
4
4
|
import { ACTION_RPC_ROUTE_PATTERN } from "../../consts.js";
|
|
5
5
|
import {
|
|
6
|
+
ACTION_API_CONTEXT_SYMBOL,
|
|
6
7
|
formContentTypes,
|
|
7
|
-
hasContentType
|
|
8
|
+
hasContentType,
|
|
9
|
+
isActionAPIContext
|
|
8
10
|
} from "../utils.js";
|
|
9
11
|
import { getAction } from "./get-action.js";
|
|
10
12
|
import {
|
|
@@ -23,7 +25,7 @@ function defineAction({
|
|
|
23
25
|
}) {
|
|
24
26
|
const serverHandler = accept === "form" ? getFormServerHandler(handler, inputSchema) : getJsonServerHandler(handler, inputSchema);
|
|
25
27
|
async function safeServerHandler(unparsedInput) {
|
|
26
|
-
if (typeof this === "function") {
|
|
28
|
+
if (typeof this === "function" || !isActionAPIContext(this)) {
|
|
27
29
|
throw new AstroError(ActionCalledFromServerError);
|
|
28
30
|
}
|
|
29
31
|
return callSafely(() => serverHandler(unparsedInput, this));
|
|
@@ -158,6 +160,7 @@ function getActionContext(context) {
|
|
|
158
160
|
redirect: _redirect,
|
|
159
161
|
...actionAPIContext
|
|
160
162
|
} = context;
|
|
163
|
+
Reflect.set(actionAPIContext, ACTION_API_CONTEXT_SYMBOL, true);
|
|
161
164
|
const handler = baseAction.bind(actionAPIContext);
|
|
162
165
|
return handler(input);
|
|
163
166
|
}
|
package/dist/actions/utils.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type fsMod from 'node:fs';
|
|
2
2
|
import type { APIContext } from '../types/public/context.js';
|
|
3
|
-
import type
|
|
3
|
+
import { type ActionAPIContext, type Locals } from './runtime/utils.js';
|
|
4
4
|
export declare function hasActionPayload(locals: APIContext['locals']): locals is Locals;
|
|
5
5
|
export declare function createGetActionResult(locals: APIContext['locals']): APIContext['getActionResult'];
|
|
6
6
|
export declare function createCallAction(context: ActionAPIContext): APIContext['callAction'];
|
package/dist/actions/utils.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import * as eslexer from "es-module-lexer";
|
|
2
|
+
import { ACTION_API_CONTEXT_SYMBOL } from "./runtime/utils.js";
|
|
2
3
|
import { deserializeActionResult, getActionQueryString } from "./runtime/virtual/shared.js";
|
|
3
4
|
function hasActionPayload(locals) {
|
|
4
5
|
return "_actionPayload" in locals;
|
|
@@ -13,6 +14,7 @@ function createGetActionResult(locals) {
|
|
|
13
14
|
}
|
|
14
15
|
function createCallAction(context) {
|
|
15
16
|
return (baseAction, input) => {
|
|
17
|
+
Reflect.set(context, ACTION_API_CONTEXT_SYMBOL, true);
|
|
16
18
|
const action = baseAction.bind(context);
|
|
17
19
|
return action(input);
|
|
18
20
|
};
|
|
@@ -83,6 +83,8 @@ async function generateImagesForPath(originalFilePath, transformsAndPath, env, q
|
|
|
83
83
|
async function generateImageInternal(filepath, options) {
|
|
84
84
|
const isLocalImage = isESMImportedImage(options.src);
|
|
85
85
|
const finalFileURL = new URL("." + filepath, env.clientRoot);
|
|
86
|
+
const finalFolderURL = new URL("./", finalFileURL);
|
|
87
|
+
await fs.promises.mkdir(finalFolderURL, { recursive: true });
|
|
86
88
|
const cacheFile = basename(filepath) + (isLocalImage ? "" : ".json");
|
|
87
89
|
const cachedFileURL = new URL(cacheFile, env.assetsCacheDir);
|
|
88
90
|
try {
|
|
@@ -113,8 +115,6 @@ async function generateImagesForPath(originalFilePath, transformsAndPath, env, q
|
|
|
113
115
|
throw new Error(`An error was encountered while reading the cache file. Error: ${e}`);
|
|
114
116
|
}
|
|
115
117
|
}
|
|
116
|
-
const finalFolderURL = new URL("./", finalFileURL);
|
|
117
|
-
await fs.promises.mkdir(finalFolderURL, { recursive: true });
|
|
118
118
|
const originalImagePath = isLocalImage ? options.src.src : options.src;
|
|
119
119
|
if (!originalImage) {
|
|
120
120
|
originalImage = await loadImage(originalFilePath, env);
|
package/dist/assets/consts.js
CHANGED
|
@@ -22,7 +22,15 @@ const VALID_SUPPORTED_FORMATS = [
|
|
|
22
22
|
];
|
|
23
23
|
const DEFAULT_OUTPUT_FORMAT = "webp";
|
|
24
24
|
const VALID_OUTPUT_FORMATS = ["avif", "png", "webp", "jpeg", "jpg", "svg"];
|
|
25
|
-
const DEFAULT_HASH_PROPS = [
|
|
25
|
+
const DEFAULT_HASH_PROPS = [
|
|
26
|
+
"src",
|
|
27
|
+
"width",
|
|
28
|
+
"height",
|
|
29
|
+
"format",
|
|
30
|
+
"quality",
|
|
31
|
+
"fit",
|
|
32
|
+
"position"
|
|
33
|
+
];
|
|
26
34
|
export {
|
|
27
35
|
DEFAULT_HASH_PROPS,
|
|
28
36
|
DEFAULT_OUTPUT_FORMAT,
|
|
@@ -2,4 +2,8 @@ import type { AstroConfig } from '../types/public/config.js';
|
|
|
2
2
|
import { type ImageService } from './services/service.js';
|
|
3
3
|
import { type GetImageResult, type UnresolvedImageTransform } from './types.js';
|
|
4
4
|
export declare function getConfiguredImageService(): Promise<ImageService>;
|
|
5
|
-
|
|
5
|
+
type ImageConfig = AstroConfig['image'] & {
|
|
6
|
+
experimentalResponsiveImages: boolean;
|
|
7
|
+
};
|
|
8
|
+
export declare function getImage(options: UnresolvedImageTransform, imageConfig: ImageConfig): Promise<GetImageResult>;
|
|
9
|
+
export {};
|
package/dist/assets/internal.js
CHANGED
|
@@ -1,6 +1,12 @@
|
|
|
1
1
|
import { isRemotePath } from "@astrojs/internal-helpers/path";
|
|
2
2
|
import { AstroError, AstroErrorData } from "../core/errors/index.js";
|
|
3
3
|
import { DEFAULT_HASH_PROPS } from "./consts.js";
|
|
4
|
+
import {
|
|
5
|
+
DEFAULT_RESOLUTIONS,
|
|
6
|
+
LIMITED_RESOLUTIONS,
|
|
7
|
+
getSizesAttribute,
|
|
8
|
+
getWidths
|
|
9
|
+
} from "./layout.js";
|
|
4
10
|
import { isLocalService } from "./services/service.js";
|
|
5
11
|
import {
|
|
6
12
|
isImageMetadata
|
|
@@ -48,10 +54,16 @@ async function getImage(options, imageConfig) {
|
|
|
48
54
|
...options,
|
|
49
55
|
src: await resolveSrc(options.src)
|
|
50
56
|
};
|
|
57
|
+
let originalWidth;
|
|
58
|
+
let originalHeight;
|
|
59
|
+
let originalFormat;
|
|
51
60
|
if (options.inferSize && isRemoteImage(resolvedOptions.src) && isRemotePath(resolvedOptions.src)) {
|
|
52
61
|
const result = await inferRemoteSize(resolvedOptions.src);
|
|
53
62
|
resolvedOptions.width ??= result.width;
|
|
54
63
|
resolvedOptions.height ??= result.height;
|
|
64
|
+
originalWidth = result.width;
|
|
65
|
+
originalHeight = result.height;
|
|
66
|
+
originalFormat = result.format;
|
|
55
67
|
delete resolvedOptions.inferSize;
|
|
56
68
|
}
|
|
57
69
|
const originalFilePath = isESMImportedImage(resolvedOptions.src) ? resolvedOptions.src.fsPath : void 0;
|
|
@@ -59,17 +71,57 @@ async function getImage(options, imageConfig) {
|
|
|
59
71
|
// @ts-expect-error - clone is a private, hidden prop
|
|
60
72
|
resolvedOptions.src.clone ?? resolvedOptions.src
|
|
61
73
|
) : resolvedOptions.src;
|
|
74
|
+
if (isESMImportedImage(clonedSrc)) {
|
|
75
|
+
originalWidth = clonedSrc.width;
|
|
76
|
+
originalHeight = clonedSrc.height;
|
|
77
|
+
originalFormat = clonedSrc.format;
|
|
78
|
+
}
|
|
79
|
+
if (originalWidth && originalHeight) {
|
|
80
|
+
const aspectRatio = originalWidth / originalHeight;
|
|
81
|
+
if (resolvedOptions.height && !resolvedOptions.width) {
|
|
82
|
+
resolvedOptions.width = Math.round(resolvedOptions.height * aspectRatio);
|
|
83
|
+
} else if (resolvedOptions.width && !resolvedOptions.height) {
|
|
84
|
+
resolvedOptions.height = Math.round(resolvedOptions.width / aspectRatio);
|
|
85
|
+
} else if (!resolvedOptions.width && !resolvedOptions.height) {
|
|
86
|
+
resolvedOptions.width = originalWidth;
|
|
87
|
+
resolvedOptions.height = originalHeight;
|
|
88
|
+
}
|
|
89
|
+
}
|
|
62
90
|
resolvedOptions.src = clonedSrc;
|
|
91
|
+
const layout = options.layout ?? imageConfig.experimentalLayout;
|
|
92
|
+
if (imageConfig.experimentalResponsiveImages && layout) {
|
|
93
|
+
resolvedOptions.widths ||= getWidths({
|
|
94
|
+
width: resolvedOptions.width,
|
|
95
|
+
layout,
|
|
96
|
+
originalWidth,
|
|
97
|
+
breakpoints: imageConfig.experimentalBreakpoints?.length ? imageConfig.experimentalBreakpoints : isLocalService(service) ? LIMITED_RESOLUTIONS : DEFAULT_RESOLUTIONS
|
|
98
|
+
});
|
|
99
|
+
resolvedOptions.sizes ||= getSizesAttribute({ width: resolvedOptions.width, layout });
|
|
100
|
+
if (resolvedOptions.priority) {
|
|
101
|
+
resolvedOptions.loading ??= "eager";
|
|
102
|
+
resolvedOptions.decoding ??= "sync";
|
|
103
|
+
resolvedOptions.fetchpriority ??= "high";
|
|
104
|
+
} else {
|
|
105
|
+
resolvedOptions.loading ??= "lazy";
|
|
106
|
+
resolvedOptions.decoding ??= "async";
|
|
107
|
+
resolvedOptions.fetchpriority ??= "auto";
|
|
108
|
+
}
|
|
109
|
+
delete resolvedOptions.priority;
|
|
110
|
+
delete resolvedOptions.densities;
|
|
111
|
+
}
|
|
63
112
|
const validatedOptions = service.validateOptions ? await service.validateOptions(resolvedOptions, imageConfig) : resolvedOptions;
|
|
64
113
|
const srcSetTransforms = service.getSrcSet ? await service.getSrcSet(validatedOptions, imageConfig) : [];
|
|
65
114
|
let imageURL = await service.getURL(validatedOptions, imageConfig);
|
|
115
|
+
const matchesOriginal = (transform) => transform.width === originalWidth && transform.height === originalHeight && transform.format === originalFormat;
|
|
66
116
|
let srcSets = await Promise.all(
|
|
67
|
-
srcSetTransforms.map(async (srcSet) =>
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
117
|
+
srcSetTransforms.map(async (srcSet) => {
|
|
118
|
+
return {
|
|
119
|
+
transform: srcSet.transform,
|
|
120
|
+
url: matchesOriginal(srcSet.transform) ? imageURL : await service.getURL(srcSet.transform, imageConfig),
|
|
121
|
+
descriptor: srcSet.descriptor,
|
|
122
|
+
attributes: srcSet.attributes
|
|
123
|
+
};
|
|
124
|
+
})
|
|
73
125
|
);
|
|
74
126
|
if (isLocalService(service) && globalThis.astroAsset.addStaticImage && !(isRemoteImage(validatedOptions.src) && imageURL === validatedOptions.src)) {
|
|
75
127
|
const propsToHash = service.propertiesToHash ?? DEFAULT_HASH_PROPS;
|
|
@@ -78,12 +130,14 @@ async function getImage(options, imageConfig) {
|
|
|
78
130
|
propsToHash,
|
|
79
131
|
originalFilePath
|
|
80
132
|
);
|
|
81
|
-
srcSets = srcSetTransforms.map((srcSet) =>
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
133
|
+
srcSets = srcSetTransforms.map((srcSet) => {
|
|
134
|
+
return {
|
|
135
|
+
transform: srcSet.transform,
|
|
136
|
+
url: matchesOriginal(srcSet.transform) ? imageURL : globalThis.astroAsset.addStaticImage(srcSet.transform, propsToHash, originalFilePath),
|
|
137
|
+
descriptor: srcSet.descriptor,
|
|
138
|
+
attributes: srcSet.attributes
|
|
139
|
+
};
|
|
140
|
+
});
|
|
87
141
|
}
|
|
88
142
|
return {
|
|
89
143
|
rawOptions: resolvedOptions,
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import type { ImageLayout } from './types.js';
|
|
2
|
+
export declare const DEFAULT_RESOLUTIONS: number[];
|
|
3
|
+
export declare const LIMITED_RESOLUTIONS: number[];
|
|
4
|
+
/**
|
|
5
|
+
* Gets the breakpoints for an image, based on the layout and width
|
|
6
|
+
*
|
|
7
|
+
* The rules are as follows:
|
|
8
|
+
*
|
|
9
|
+
* - For full-width layout we return all breakpoints smaller than the original image width
|
|
10
|
+
* - For fixed layout we return 1x and 2x the requested width, unless the original image is smaller than that.
|
|
11
|
+
* - For responsive layout we return all breakpoints smaller than 2x the requested width, unless the original image is smaller than that.
|
|
12
|
+
*/
|
|
13
|
+
export declare const getWidths: ({ width, layout, breakpoints, originalWidth, }: {
|
|
14
|
+
width?: number;
|
|
15
|
+
layout: ImageLayout;
|
|
16
|
+
breakpoints?: Array<number>;
|
|
17
|
+
originalWidth?: number;
|
|
18
|
+
}) => Array<number>;
|
|
19
|
+
/**
|
|
20
|
+
* Gets the `sizes` attribute for an image, based on the layout and width
|
|
21
|
+
*/
|
|
22
|
+
export declare const getSizesAttribute: ({ width, layout, }: {
|
|
23
|
+
width?: number;
|
|
24
|
+
layout?: ImageLayout;
|
|
25
|
+
}) => string | undefined;
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
const DEFAULT_RESOLUTIONS = [
|
|
2
|
+
640,
|
|
3
|
+
// older and lower-end phones
|
|
4
|
+
750,
|
|
5
|
+
// iPhone 6-8
|
|
6
|
+
828,
|
|
7
|
+
// iPhone XR/11
|
|
8
|
+
960,
|
|
9
|
+
// older horizontal phones
|
|
10
|
+
1080,
|
|
11
|
+
// iPhone 6-8 Plus
|
|
12
|
+
1280,
|
|
13
|
+
// 720p
|
|
14
|
+
1668,
|
|
15
|
+
// Various iPads
|
|
16
|
+
1920,
|
|
17
|
+
// 1080p
|
|
18
|
+
2048,
|
|
19
|
+
// QXGA
|
|
20
|
+
2560,
|
|
21
|
+
// WQXGA
|
|
22
|
+
3200,
|
|
23
|
+
// QHD+
|
|
24
|
+
3840,
|
|
25
|
+
// 4K
|
|
26
|
+
4480,
|
|
27
|
+
// 4.5K
|
|
28
|
+
5120,
|
|
29
|
+
// 5K
|
|
30
|
+
6016
|
|
31
|
+
// 6K
|
|
32
|
+
];
|
|
33
|
+
const LIMITED_RESOLUTIONS = [
|
|
34
|
+
640,
|
|
35
|
+
// older and lower-end phones
|
|
36
|
+
750,
|
|
37
|
+
// iPhone 6-8
|
|
38
|
+
828,
|
|
39
|
+
// iPhone XR/11
|
|
40
|
+
1080,
|
|
41
|
+
// iPhone 6-8 Plus
|
|
42
|
+
1280,
|
|
43
|
+
// 720p
|
|
44
|
+
1668,
|
|
45
|
+
// Various iPads
|
|
46
|
+
2048,
|
|
47
|
+
// QXGA
|
|
48
|
+
2560
|
|
49
|
+
// WQXGA
|
|
50
|
+
];
|
|
51
|
+
const getWidths = ({
|
|
52
|
+
width,
|
|
53
|
+
layout,
|
|
54
|
+
breakpoints = DEFAULT_RESOLUTIONS,
|
|
55
|
+
originalWidth
|
|
56
|
+
}) => {
|
|
57
|
+
const smallerThanOriginal = (w) => !originalWidth || w <= originalWidth;
|
|
58
|
+
if (layout === "full-width") {
|
|
59
|
+
return breakpoints.filter(smallerThanOriginal);
|
|
60
|
+
}
|
|
61
|
+
if (!width) {
|
|
62
|
+
return [];
|
|
63
|
+
}
|
|
64
|
+
const doubleWidth = width * 2;
|
|
65
|
+
const maxSize = originalWidth ? Math.min(doubleWidth, originalWidth) : doubleWidth;
|
|
66
|
+
if (layout === "fixed") {
|
|
67
|
+
return originalWidth && width > originalWidth ? [originalWidth] : [width, maxSize];
|
|
68
|
+
}
|
|
69
|
+
if (layout === "responsive") {
|
|
70
|
+
return [
|
|
71
|
+
// Always include the image at 1x and 2x the specified width
|
|
72
|
+
width,
|
|
73
|
+
doubleWidth,
|
|
74
|
+
...breakpoints
|
|
75
|
+
].filter((w) => w <= maxSize).sort((a, b) => a - b);
|
|
76
|
+
}
|
|
77
|
+
return [];
|
|
78
|
+
};
|
|
79
|
+
const getSizesAttribute = ({
|
|
80
|
+
width,
|
|
81
|
+
layout
|
|
82
|
+
}) => {
|
|
83
|
+
if (!width || !layout) {
|
|
84
|
+
return void 0;
|
|
85
|
+
}
|
|
86
|
+
switch (layout) {
|
|
87
|
+
case `responsive`:
|
|
88
|
+
return `(min-width: ${width}px) ${width}px, 100vw`;
|
|
89
|
+
case `fixed`:
|
|
90
|
+
return `${width}px`;
|
|
91
|
+
case `full-width`:
|
|
92
|
+
return `100vw`;
|
|
93
|
+
case "none":
|
|
94
|
+
default:
|
|
95
|
+
return void 0;
|
|
96
|
+
}
|
|
97
|
+
};
|
|
98
|
+
export {
|
|
99
|
+
DEFAULT_RESOLUTIONS,
|
|
100
|
+
LIMITED_RESOLUTIONS,
|
|
101
|
+
getSizesAttribute,
|
|
102
|
+
getWidths
|
|
103
|
+
};
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { ImageMetadata } from './types.js';
|
|
2
|
+
export interface SvgComponentProps {
|
|
3
|
+
meta: ImageMetadata;
|
|
4
|
+
attributes: Record<string, string>;
|
|
5
|
+
children: string;
|
|
6
|
+
}
|
|
7
|
+
export declare function createSvgComponent({ meta, attributes, children }: SvgComponentProps): import("../runtime/server/index.js").AstroComponentFactory & ImageMetadata;
|
|
8
|
+
type SvgAttributes = Record<string, any>;
|
|
9
|
+
export declare function dropAttributes(attributes: SvgAttributes): SvgAttributes;
|
|
10
|
+
export {};
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import {
|
|
2
|
+
createComponent,
|
|
3
|
+
render,
|
|
4
|
+
spreadAttributes,
|
|
5
|
+
unescapeHTML
|
|
6
|
+
} from "../runtime/server/index.js";
|
|
7
|
+
const ids = /* @__PURE__ */ new WeakMap();
|
|
8
|
+
let counter = 0;
|
|
9
|
+
function createSvgComponent({ meta, attributes, children }) {
|
|
10
|
+
const rendered = /* @__PURE__ */ new WeakSet();
|
|
11
|
+
const Component = createComponent((result, props) => {
|
|
12
|
+
let id;
|
|
13
|
+
if (ids.has(result)) {
|
|
14
|
+
id = ids.get(result);
|
|
15
|
+
} else {
|
|
16
|
+
counter += 1;
|
|
17
|
+
ids.set(result, counter);
|
|
18
|
+
id = counter;
|
|
19
|
+
}
|
|
20
|
+
id = `a:${id}`;
|
|
21
|
+
const {
|
|
22
|
+
title: titleProp,
|
|
23
|
+
viewBox,
|
|
24
|
+
mode,
|
|
25
|
+
...normalizedProps
|
|
26
|
+
} = normalizeProps(attributes, props);
|
|
27
|
+
const title = titleProp ? unescapeHTML(`<title>${titleProp}</title>`) : "";
|
|
28
|
+
if (mode === "sprite") {
|
|
29
|
+
let symbol = "";
|
|
30
|
+
if (!rendered.has(result.response)) {
|
|
31
|
+
symbol = unescapeHTML(`<symbol${spreadAttributes({ viewBox, id })}>${children}</symbol>`);
|
|
32
|
+
rendered.add(result.response);
|
|
33
|
+
}
|
|
34
|
+
return render`<svg${spreadAttributes(normalizedProps)}>${title}${symbol}<use href="#${id}" /></svg>`;
|
|
35
|
+
}
|
|
36
|
+
return render`<svg${spreadAttributes({ viewBox, ...normalizedProps })}>${title}${unescapeHTML(children)}</svg>`;
|
|
37
|
+
});
|
|
38
|
+
if (import.meta.env.DEV) {
|
|
39
|
+
makeNonEnumerable(Component);
|
|
40
|
+
Object.defineProperty(Component, Symbol.for("nodejs.util.inspect.custom"), {
|
|
41
|
+
value: (_, opts, inspect) => inspect(meta, opts)
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
return Object.assign(Component, meta);
|
|
45
|
+
}
|
|
46
|
+
const ATTRS_TO_DROP = ["xmlns", "xmlns:xlink", "version"];
|
|
47
|
+
const DEFAULT_ATTRS = { role: "img" };
|
|
48
|
+
function dropAttributes(attributes) {
|
|
49
|
+
for (const attr of ATTRS_TO_DROP) {
|
|
50
|
+
delete attributes[attr];
|
|
51
|
+
}
|
|
52
|
+
return attributes;
|
|
53
|
+
}
|
|
54
|
+
function normalizeProps(attributes, { size, ...props }) {
|
|
55
|
+
if (size !== void 0 && props.width === void 0 && props.height === void 0) {
|
|
56
|
+
props.height = size;
|
|
57
|
+
props.width = size;
|
|
58
|
+
}
|
|
59
|
+
return dropAttributes({ ...DEFAULT_ATTRS, ...attributes, ...props });
|
|
60
|
+
}
|
|
61
|
+
function makeNonEnumerable(object) {
|
|
62
|
+
for (const property in object) {
|
|
63
|
+
Object.defineProperty(object, property, { enumerable: false });
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
export {
|
|
67
|
+
createSvgComponent,
|
|
68
|
+
dropAttributes
|
|
69
|
+
};
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { AstroConfig } from '../../types/public/config.js';
|
|
2
|
-
import type { ImageOutputFormat, ImageTransform, UnresolvedSrcSetValue } from '../types.js';
|
|
2
|
+
import type { ImageFit, ImageOutputFormat, ImageTransform, UnresolvedSrcSetValue } from '../types.js';
|
|
3
3
|
export type ImageService = LocalImageService | ExternalImageService;
|
|
4
4
|
export declare function isLocalService(service: ImageService | undefined): service is LocalImageService;
|
|
5
5
|
export declare function parseQuality(quality: string): string | number;
|
|
@@ -76,6 +76,8 @@ export type BaseServiceTransform = {
|
|
|
76
76
|
height?: number;
|
|
77
77
|
format: string;
|
|
78
78
|
quality?: string | null;
|
|
79
|
+
fit?: ImageFit;
|
|
80
|
+
position?: string;
|
|
79
81
|
};
|
|
80
82
|
/**
|
|
81
83
|
* Basic local service using the included `_image` endpoint.
|