astro 7.0.0-alpha.1 → 7.0.0-beta.3

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.
Files changed (199) hide show
  1. package/components/Code.astro +1 -1
  2. package/dist/assets/endpoint/dev.js +1 -1
  3. package/dist/assets/endpoint/generic.js +7 -16
  4. package/dist/assets/endpoint/loadImage.d.ts +11 -0
  5. package/dist/assets/endpoint/loadImage.js +19 -0
  6. package/dist/assets/fonts/config.d.ts +4 -4
  7. package/dist/assets/fonts/core/collect-font-data.js +1 -0
  8. package/dist/assets/fonts/core/optimize-fallbacks.js +38 -13
  9. package/dist/assets/fonts/definitions.d.ts +2 -2
  10. package/dist/assets/fonts/infra/system-fallbacks-provider.d.ts +2 -2
  11. package/dist/assets/fonts/infra/system-fallbacks-provider.js +46 -9
  12. package/dist/assets/fonts/types.d.ts +2 -0
  13. package/dist/assets/internal.js +20 -16
  14. package/dist/assets/services/service.d.ts +1 -1
  15. package/dist/assets/services/service.js +9 -9
  16. package/dist/assets/services/sharp.js +48 -28
  17. package/dist/assets/utils/generateImageStylesCSS.js +26 -6
  18. package/dist/assets/utils/inferSourceFormat.d.ts +8 -3
  19. package/dist/assets/utils/inferSourceFormat.js +15 -4
  20. package/dist/assets/utils/metadata.js +1 -1
  21. package/dist/assets/utils/vendor/image-size/types/svg.js +1 -1
  22. package/dist/cli/add/index.js +0 -44
  23. package/dist/cli/dev/background.d.ts +16 -0
  24. package/dist/cli/dev/background.js +116 -0
  25. package/dist/cli/dev/index.js +82 -3
  26. package/dist/cli/dev/logs.d.ts +6 -0
  27. package/dist/cli/dev/logs.js +72 -0
  28. package/dist/cli/dev/status.d.ts +15 -0
  29. package/dist/cli/dev/status.js +27 -0
  30. package/dist/cli/dev/stop.d.ts +12 -0
  31. package/dist/cli/dev/stop.js +43 -0
  32. package/dist/cli/flags.js +4 -6
  33. package/dist/cli/help/index.js +1 -2
  34. package/dist/cli/index.js +1 -15
  35. package/dist/cli/infra/build-time-astro-version-provider.js +1 -1
  36. package/dist/container/index.js +1 -4
  37. package/dist/content/content-layer.js +16 -10
  38. package/dist/content/data-store.d.ts +1 -1
  39. package/dist/content/runtime-assets.d.ts +2 -2
  40. package/dist/content/runtime.d.ts +1 -1
  41. package/dist/content/runtime.js +9 -4
  42. package/dist/content/types-generator.js +5 -1
  43. package/dist/content/utils.d.ts +1 -1
  44. package/dist/content/utils.js +1 -1
  45. package/dist/core/app/base.d.ts +10 -1
  46. package/dist/core/app/base.js +54 -76
  47. package/dist/core/app/dev/pipeline.js +0 -9
  48. package/dist/core/app/entrypoints/node.d.ts +1 -1
  49. package/dist/core/app/entrypoints/node.js +2 -0
  50. package/dist/core/app/manifest.d.ts +0 -2
  51. package/dist/core/app/manifest.js +0 -8
  52. package/dist/core/app/node.d.ts +16 -0
  53. package/dist/core/app/node.js +59 -13
  54. package/dist/core/app/types.d.ts +1 -8
  55. package/dist/core/base-pipeline.d.ts +13 -9
  56. package/dist/core/base-pipeline.js +17 -24
  57. package/dist/core/build/app.d.ts +0 -2
  58. package/dist/core/build/app.js +0 -5
  59. package/dist/core/build/generate.js +8 -15
  60. package/dist/core/build/index.d.ts +0 -11
  61. package/dist/core/build/index.js +0 -3
  62. package/dist/core/build/internal.d.ts +7 -0
  63. package/dist/core/build/pipeline.js +0 -9
  64. package/dist/core/build/plugins/plugin-chunk-imports.d.ts +6 -0
  65. package/dist/core/build/plugins/plugin-chunk-imports.js +29 -17
  66. package/dist/core/build/plugins/plugin-css.js +33 -0
  67. package/dist/core/build/plugins/plugin-internals.js +1 -1
  68. package/dist/core/build/plugins/plugin-manifest.js +4 -9
  69. package/dist/core/build/static-build.js +22 -155
  70. package/dist/core/build/types.d.ts +0 -1
  71. package/dist/core/build/util.js +8 -1
  72. package/dist/core/build/vite-build-config.d.ts +28 -0
  73. package/dist/core/build/vite-build-config.js +165 -0
  74. package/dist/core/config/config.js +3 -2
  75. package/dist/core/config/merge.js +4 -0
  76. package/dist/core/config/schemas/base.d.ts +22 -27
  77. package/dist/core/config/schemas/base.js +31 -22
  78. package/dist/core/config/schemas/relative.d.ts +75 -72
  79. package/dist/core/config/validate.js +59 -0
  80. package/dist/core/constants.js +1 -1
  81. package/dist/core/create-vite.js +3 -1
  82. package/dist/core/csp/config.js +17 -5
  83. package/dist/core/dev/dev.d.ts +1 -0
  84. package/dist/core/dev/dev.js +4 -1
  85. package/dist/core/dev/lockfile.d.ts +54 -0
  86. package/dist/core/dev/lockfile.js +93 -0
  87. package/dist/core/errors/errors-data.d.ts +43 -38
  88. package/dist/core/errors/errors-data.js +79 -73
  89. package/dist/core/errors/zod-error-map.js +3 -1
  90. package/dist/core/fetch/fetch-state.d.ts +12 -26
  91. package/dist/core/fetch/fetch-state.js +137 -34
  92. package/dist/core/fetch/types.d.ts +19 -0
  93. package/dist/core/fetch/vite-plugin.js +10 -5
  94. package/dist/core/hono/index.d.ts +2 -1
  95. package/dist/core/hono/index.js +7 -3
  96. package/dist/core/i18n/domain.d.ts +12 -0
  97. package/dist/core/i18n/domain.js +66 -0
  98. package/dist/core/i18n/handler.js +3 -0
  99. package/dist/core/logger/core.d.ts +1 -1
  100. package/dist/core/logger/core.js +1 -1
  101. package/dist/core/logger/impls/node.js +0 -1
  102. package/dist/core/logger/load.js +3 -2
  103. package/dist/core/messages/runtime.js +1 -1
  104. package/dist/core/middleware/astro-middleware.js +3 -5
  105. package/dist/core/middleware/index.js +8 -1
  106. package/dist/core/module-loader/vite.js +1 -2
  107. package/dist/core/pages/handler.js +1 -0
  108. package/dist/core/preview/index.js +6 -5
  109. package/dist/core/preview/static-preview-server.js +5 -2
  110. package/dist/core/render/params-and-props.js +1 -1
  111. package/dist/core/render/route-cache.d.ts +1 -0
  112. package/dist/core/render/route-cache.js +4 -4
  113. package/dist/core/routing/create-manifest.js +11 -1
  114. package/dist/core/routing/handler.js +5 -6
  115. package/dist/core/routing/parse-route.js +1 -1
  116. package/dist/core/routing/rewrite.js +1 -1
  117. package/dist/core/routing/router.d.ts +8 -0
  118. package/dist/core/routing/router.js +28 -0
  119. package/dist/core/routing/validation.js +1 -1
  120. package/dist/core/server-islands/vite-plugin-server-islands.d.ts +6 -1
  121. package/dist/core/server-islands/vite-plugin-server-islands.js +13 -3
  122. package/dist/core/session/config.d.ts +1 -1
  123. package/dist/core/util/normalized-url.js +5 -2
  124. package/dist/core/util/pathname.d.ts +10 -1
  125. package/dist/core/util/pathname.js +13 -4
  126. package/dist/environments.js +1 -1
  127. package/dist/events/session.d.ts +8 -0
  128. package/dist/events/session.js +11 -0
  129. package/dist/jsx/rehype.d.ts +1 -1
  130. package/dist/manifest/serialized.js +4 -5
  131. package/dist/manifest/virtual-module.js +3 -1
  132. package/dist/markdown/index.d.ts +4 -0
  133. package/dist/markdown/index.js +14 -0
  134. package/dist/prerender/utils.js +5 -1
  135. package/dist/runtime/client/dev-toolbar/apps/audit/rules/a11y.js +9 -0
  136. package/dist/runtime/server/index.d.ts +1 -1
  137. package/dist/runtime/server/index.js +4 -0
  138. package/dist/runtime/server/jsx.js +1 -1
  139. package/dist/runtime/server/render/astro/render-template.d.ts +1 -1
  140. package/dist/runtime/server/render/astro/render.d.ts +0 -4
  141. package/dist/runtime/server/render/astro/render.js +76 -68
  142. package/dist/runtime/server/render/component.js +8 -6
  143. package/dist/runtime/server/render/head.js +1 -5
  144. package/dist/runtime/server/render/index.d.ts +1 -0
  145. package/dist/runtime/server/render/index.js +2 -0
  146. package/dist/runtime/server/render/page.js +9 -44
  147. package/dist/runtime/server/render/streaming.d.ts +23 -0
  148. package/dist/runtime/server/render/streaming.js +238 -0
  149. package/dist/runtime/server/render/util.js +1 -1
  150. package/dist/runtime/server/transition.d.ts +1 -6
  151. package/dist/runtime/server/transition.js +0 -8
  152. package/dist/transitions/events.d.ts +0 -14
  153. package/dist/transitions/events.js +0 -14
  154. package/dist/transitions/index.d.ts +0 -1
  155. package/dist/transitions/index.js +0 -2
  156. package/dist/transitions/vite-plugin-transitions.js +2 -4
  157. package/dist/types/public/config.d.ts +104 -119
  158. package/dist/types/public/content.d.ts +1 -1
  159. package/dist/types/public/context.d.ts +1 -1
  160. package/dist/types/public/index.d.ts +2 -1
  161. package/dist/types/public/integrations.d.ts +11 -3
  162. package/dist/types/public/internal.d.ts +0 -15
  163. package/dist/types/public/manifest.d.ts +1 -1
  164. package/dist/virtual-modules/i18n.d.ts +2 -2
  165. package/dist/virtual-modules/i18n.js +1 -1
  166. package/dist/vite-plugin-app/app.d.ts +9 -1
  167. package/dist/vite-plugin-app/app.js +32 -22
  168. package/dist/vite-plugin-app/createAstroServerApp.d.ts +3 -1
  169. package/dist/vite-plugin-app/createAstroServerApp.js +4 -3
  170. package/dist/vite-plugin-app/pipeline.js +0 -9
  171. package/dist/vite-plugin-astro-server/plugin.js +11 -5
  172. package/dist/vite-plugin-astro-server/route-guard.d.ts +33 -0
  173. package/dist/vite-plugin-astro-server/route-guard.js +42 -23
  174. package/dist/vite-plugin-dev-status/index.d.ts +2 -0
  175. package/dist/vite-plugin-dev-status/index.js +15 -0
  176. package/dist/vite-plugin-hmr-reload/index.d.ts +1 -1
  177. package/dist/vite-plugin-hmr-reload/index.js +23 -1
  178. package/dist/vite-plugin-integrations-container/index.js +15 -6
  179. package/dist/vite-plugin-markdown/content-entry-type.js +7 -4
  180. package/dist/vite-plugin-markdown/images.js +9 -11
  181. package/dist/vite-plugin-markdown/index.js +12 -11
  182. package/dist/vite-plugin-utils/index.js +7 -1
  183. package/package.json +13 -13
  184. package/templates/content/types.d.ts +1 -0
  185. package/types/transitions.d.ts +0 -7
  186. package/dist/cli/db/index.d.ts +0 -4
  187. package/dist/cli/db/index.js +0 -25
  188. package/dist/runtime/server/html-string-cache.d.ts +0 -48
  189. package/dist/runtime/server/html-string-cache.js +0 -119
  190. package/dist/runtime/server/render/queue/builder.d.ts +0 -14
  191. package/dist/runtime/server/render/queue/builder.js +0 -182
  192. package/dist/runtime/server/render/queue/jsx-builder.d.ts +0 -33
  193. package/dist/runtime/server/render/queue/jsx-builder.js +0 -146
  194. package/dist/runtime/server/render/queue/pool.d.ts +0 -123
  195. package/dist/runtime/server/render/queue/pool.js +0 -203
  196. package/dist/runtime/server/render/queue/renderer.d.ts +0 -12
  197. package/dist/runtime/server/render/queue/renderer.js +0 -103
  198. package/dist/runtime/server/render/queue/types.d.ts +0 -81
  199. package/dist/runtime/server/render/queue/types.js +0 -0
@@ -1,5 +1,5 @@
1
1
  ---
2
- import { createShikiHighlighter, type ThemePresets } from '@astrojs/markdown-remark/shiki';
2
+ import { createShikiHighlighter, type ThemePresets } from '@astrojs/internal-helpers/shiki';
3
3
  import type { ShikiTransformer, ThemeRegistration, ThemeRegistrationRaw } from 'shiki';
4
4
  import { bundledLanguages } from 'shiki/langs';
5
5
  import type { CodeLanguage } from '../dist/types/public/common.js';
@@ -38,7 +38,7 @@ async function loadLocalImage(src, url) {
38
38
  } else {
39
39
  const sourceUrl = new URL(src, url.origin);
40
40
  if (sourceUrl.origin !== url.origin) {
41
- returnValue = void 0;
41
+ return void 0;
42
42
  }
43
43
  return loadRemoteImage(sourceUrl);
44
44
  }
@@ -4,21 +4,7 @@ import { isRemoteAllowed } from "@astrojs/internal-helpers/remote";
4
4
  import * as mime from "mrmime";
5
5
  import { getConfiguredImageService } from "../internal.js";
6
6
  import { etag } from "../utils/etag.js";
7
- import { fetchWithRedirects } from "../utils/redirectValidation.js";
8
- async function loadRemoteImage(src, headers) {
9
- try {
10
- const res = await fetchWithRedirects({ url: src, headers, imageConfig });
11
- if (!isRemoteAllowed(res.url, imageConfig)) {
12
- return void 0;
13
- }
14
- if (!res.ok) {
15
- return void 0;
16
- }
17
- return await res.arrayBuffer();
18
- } catch {
19
- return void 0;
20
- }
21
- }
7
+ import { loadImage } from "./loadImage.js";
22
8
  const GET = async ({ request }) => {
23
9
  try {
24
10
  const imageService = await getConfiguredImageService();
@@ -39,7 +25,12 @@ const GET = async ({ request }) => {
39
25
  if (!isRemoteImage && sourceUrl.origin !== url.origin) {
40
26
  return new Response("Forbidden", { status: 403 });
41
27
  }
42
- inputBuffer = await loadRemoteImage(sourceUrl, isRemoteImage ? new Headers() : request.headers);
28
+ inputBuffer = await loadImage(
29
+ sourceUrl,
30
+ isRemoteImage ? new Headers() : request.headers,
31
+ imageConfig,
32
+ isRemoteImage
33
+ );
43
34
  if (!inputBuffer) {
44
35
  return new Response("Not Found", { status: 404 });
45
36
  }
@@ -0,0 +1,11 @@
1
+ import type { RemoteImageConfig } from '../utils/redirectValidation.js';
2
+ /**
3
+ * Fetches an image by URL. Used by the generic image endpoint for both
4
+ * remote images and local images (self-fetched from the same origin).
5
+ *
6
+ * For remote images, the final URL (after any redirects) is validated
7
+ * against `imageConfig.domains` and `imageConfig.remotePatterns`.
8
+ * Local images skip this check — they are already guarded by the
9
+ * same-origin check in the caller.
10
+ */
11
+ export declare function loadImage(src: URL, headers: Headers, imageConfig: RemoteImageConfig, isRemote: boolean, fetchFn?: typeof globalThis.fetch): Promise<ArrayBuffer | undefined>;
@@ -0,0 +1,19 @@
1
+ import { isRemoteAllowed } from "@astrojs/internal-helpers/remote";
2
+ import { fetchWithRedirects } from "../utils/redirectValidation.js";
3
+ async function loadImage(src, headers, imageConfig, isRemote, fetchFn) {
4
+ try {
5
+ const res = await fetchWithRedirects({ url: src, headers, imageConfig, fetchFn });
6
+ if (isRemote && !isRemoteAllowed(res.url, imageConfig)) {
7
+ return void 0;
8
+ }
9
+ if (!res.ok) {
10
+ return void 0;
11
+ }
12
+ return await res.arrayBuffer();
13
+ } catch {
14
+ return void 0;
15
+ }
16
+ }
17
+ export {
18
+ loadImage
19
+ };
@@ -7,11 +7,11 @@ export declare const StyleSchema: z.ZodEnum<{
7
7
  oblique: "oblique";
8
8
  }>;
9
9
  export declare const DisplaySchema: z.ZodEnum<{
10
- optional: "optional";
11
10
  auto: "auto";
11
+ optional: "optional";
12
+ fallback: "fallback";
12
13
  block: "block";
13
14
  swap: "swap";
14
- fallback: "fallback";
15
15
  }>;
16
16
  export declare const FontProviderSchema: z.ZodCustom<FontProvider<never>, FontProvider<never>>;
17
17
  export declare const FontFamilySchema: z.ZodObject<{
@@ -45,11 +45,11 @@ export declare const FontFamilySchema: z.ZodObject<{
45
45
  fallbacks: z.ZodOptional<z.ZodArray<z.ZodString>>;
46
46
  optimizedFallbacks: z.ZodOptional<z.ZodBoolean>;
47
47
  display: z.ZodOptional<z.ZodEnum<{
48
- optional: "optional";
49
48
  auto: "auto";
49
+ optional: "optional";
50
+ fallback: "fallback";
50
51
  block: "block";
51
52
  swap: "swap";
52
- fallback: "fallback";
53
53
  }>>;
54
54
  stretch: z.ZodOptional<z.ZodString>;
55
55
  featureSettings: z.ZodOptional<z.ZodString>;
@@ -7,6 +7,7 @@ function collectFontData(fontFamilyAssets) {
7
7
  fontData.push({
8
8
  weight: renderFontWeight(data.weight),
9
9
  style: data.style,
10
+ subset: data.meta?.subset,
10
11
  src: data.src.filter((src) => "url" in src).map((src) => ({
11
12
  url: src.url,
12
13
  format: src.format,
@@ -1,4 +1,17 @@
1
1
  import { isGenericFontFamily, unifontFontFaceDataToProperties } from "../utils.js";
2
+ function deriveFallbackVariant(data) {
3
+ const weight = data.weight;
4
+ if (typeof weight === "number" && weight >= 700) {
5
+ return "bold";
6
+ }
7
+ if (typeof weight === "string") {
8
+ if (weight === "bold") return "bold";
9
+ if (weight.includes(" ")) return "normal";
10
+ const n = Number.parseInt(weight, 10);
11
+ if (!Number.isNaN(n) && n >= 700) return "bold";
12
+ }
13
+ return "normal";
14
+ }
2
15
  async function optimizeFallbacks({
3
16
  family,
4
17
  fallbacks: _fallbacks,
@@ -14,28 +27,40 @@ async function optimizeFallbacks({
14
27
  if (!isGenericFontFamily(lastFallback)) {
15
28
  return null;
16
29
  }
17
- const localFonts = systemFallbacksProvider.getLocalFonts(lastFallback);
18
- if (!localFonts || localFonts.length === 0) {
30
+ const collectedWithLocalFonts = collectedFonts.map((collected) => ({
31
+ collected,
32
+ localFonts: systemFallbacksProvider.getLocalFonts(lastFallback, deriveFallbackVariant(collected.data)) ?? []
33
+ }));
34
+ const uniqueLocalFonts = [];
35
+ for (const { localFonts } of collectedWithLocalFonts) {
36
+ for (const font of localFonts) {
37
+ if (!uniqueLocalFonts.includes(font)) {
38
+ uniqueLocalFonts.push(font);
39
+ }
40
+ }
41
+ }
42
+ if (uniqueLocalFonts.length === 0) {
19
43
  return null;
20
44
  }
21
- if (localFonts.includes(family.name)) {
45
+ if (uniqueLocalFonts.includes(family.name)) {
22
46
  return null;
23
47
  }
24
- const localFontsMappings = localFonts.map((font) => ({
25
- font,
48
+ const nameForFont = (font) => (
26
49
  // We mustn't wrap in quote because that's handled by the CSS renderer
27
- name: `${family.uniqueName} fallback: ${font}`
28
- }));
29
- fallbacks = [...localFontsMappings.map((m) => m.name), ...fallbacks];
50
+ `${family.uniqueName} fallback: ${font}`
51
+ );
52
+ fallbacks = [...uniqueLocalFonts.map(nameForFont), ...fallbacks];
30
53
  let css = "";
31
- for (const { font, name } of localFontsMappings) {
32
- for (const collected of collectedFonts) {
54
+ for (const { collected, localFonts } of collectedWithLocalFonts) {
55
+ const properties = unifontFontFaceDataToProperties(collected.data);
56
+ const metrics = await fontMetricsResolver.getMetrics(family.name, collected);
57
+ for (const font of localFonts) {
33
58
  css += fontMetricsResolver.generateFontFace({
34
- metrics: await fontMetricsResolver.getMetrics(family.name, collected),
59
+ metrics,
35
60
  fallbackMetrics: systemFallbacksProvider.getMetricsForLocalFont(font),
36
61
  font,
37
- name,
38
- properties: unifontFontFaceDataToProperties(collected.data)
62
+ name: nameForFont(font),
63
+ properties
39
64
  });
40
65
  }
41
66
  }
@@ -1,6 +1,6 @@
1
1
  import type * as unifont from 'unifont';
2
2
  import type { CollectedFontForMetrics } from './core/optimize-fallbacks.js';
3
- import type { CssProperties, FontFaceMetrics, FontFileData, FontProvider, FontType, GenericFallbackName, ResolveFontOptions, Style } from './types.js';
3
+ import type { CssProperties, FallbackVariant, FontFaceMetrics, FontFileData, FontProvider, FontType, GenericFallbackName, ResolveFontOptions, Style } from './types.js';
4
4
  export interface Hasher {
5
5
  hashString: (input: string) => string;
6
6
  hashObject: (input: Record<string, any>) => string;
@@ -28,7 +28,7 @@ export interface FontMetricsResolver {
28
28
  }) => string;
29
29
  }
30
30
  export interface SystemFallbacksProvider {
31
- getLocalFonts: (fallback: GenericFallbackName) => Array<string> | null;
31
+ getLocalFonts: (fallback: GenericFallbackName, variant: FallbackVariant) => Array<string> | null;
32
32
  getMetricsForLocalFont: (family: string) => FontFaceMetrics;
33
33
  }
34
34
  export interface FontFetcher {
@@ -1,6 +1,6 @@
1
1
  import type { SystemFallbacksProvider } from '../definitions.js';
2
- import type { FontFaceMetrics, GenericFallbackName } from '../types.js';
2
+ import type { FallbackVariant, FontFaceMetrics, GenericFallbackName } from '../types.js';
3
3
  export declare class RealSystemFallbacksProvider implements SystemFallbacksProvider {
4
- getLocalFonts(fallback: GenericFallbackName): Array<string> | null;
4
+ getLocalFonts(fallback: GenericFallbackName, variant: FallbackVariant): Array<string> | null;
5
5
  getMetricsForLocalFont(family: string): FontFaceMetrics;
6
6
  }
@@ -6,6 +6,14 @@ const SYSTEM_METRICS = {
6
6
  unitsPerEm: 2048,
7
7
  xWidthAvg: 832
8
8
  },
9
+ "Times New Roman Bold": {
10
+ ascent: 1825,
11
+ descent: -443,
12
+ lineGap: 87,
13
+ unitsPerEm: 2048,
14
+ xWidthAvg: 886
15
+ },
16
+ // Times New Roman Italic almost has the same properties as Times New Roman, we don't include it
9
17
  Arial: {
10
18
  ascent: 1854,
11
19
  descent: -434,
@@ -13,6 +21,14 @@ const SYSTEM_METRICS = {
13
21
  unitsPerEm: 2048,
14
22
  xWidthAvg: 913
15
23
  },
24
+ "Arial Bold": {
25
+ ascent: 1854,
26
+ descent: -434,
27
+ lineGap: 67,
28
+ unitsPerEm: 2048,
29
+ xWidthAvg: 983
30
+ },
31
+ // Arial Italic has the same properties as Arial, we don't include it
16
32
  "Courier New": {
17
33
  ascent: 1705,
18
34
  descent: -615,
@@ -20,6 +36,8 @@ const SYSTEM_METRICS = {
20
36
  unitsPerEm: 2048,
21
37
  xWidthAvg: 1229
22
38
  },
39
+ // Courier New Bold has the same properties as Courier New, we don't include it
40
+ // Courier New Italic has the same properties as Courier New, we don't include it
23
41
  BlinkMacSystemFont: {
24
42
  ascent: 1980,
25
43
  descent: -432,
@@ -50,17 +68,36 @@ const SYSTEM_METRICS = {
50
68
  }
51
69
  };
52
70
  const DEFAULT_FALLBACKS = {
53
- serif: ["Times New Roman"],
54
- "sans-serif": ["Arial"],
55
- monospace: ["Courier New"],
56
- "system-ui": ["BlinkMacSystemFont", "Segoe UI", "Roboto", "Helvetica Neue", "Arial"],
57
- "ui-serif": ["Times New Roman"],
58
- "ui-sans-serif": ["Arial"],
59
- "ui-monospace": ["Courier New"]
71
+ serif: {
72
+ normal: ["Times New Roman"],
73
+ bold: ["Times New Roman Bold"]
74
+ },
75
+ "sans-serif": {
76
+ normal: ["Arial"],
77
+ bold: ["Arial Bold"]
78
+ },
79
+ monospace: { normal: ["Courier New"] },
80
+ "system-ui": {
81
+ normal: ["BlinkMacSystemFont", "Segoe UI", "Roboto", "Helvetica Neue", "Arial"],
82
+ bold: ["BlinkMacSystemFont", "Segoe UI", "Roboto", "Helvetica Neue", "Arial Bold"]
83
+ },
84
+ "ui-serif": {
85
+ normal: ["Times New Roman"],
86
+ bold: ["Times New Roman Bold"]
87
+ },
88
+ "ui-sans-serif": {
89
+ normal: ["Arial"],
90
+ bold: ["Arial Bold"]
91
+ },
92
+ "ui-monospace": { normal: ["Courier New"] }
60
93
  };
61
94
  class RealSystemFallbacksProvider {
62
- getLocalFonts(fallback) {
63
- return DEFAULT_FALLBACKS[fallback] ?? null;
95
+ getLocalFonts(fallback, variant) {
96
+ const entry = DEFAULT_FALLBACKS[fallback];
97
+ if (!entry) {
98
+ return null;
99
+ }
100
+ return entry[variant] ?? entry.normal ?? null;
64
101
  }
65
102
  getMetricsForLocalFont(family) {
66
103
  return SYSTEM_METRICS[family];
@@ -191,6 +191,7 @@ export interface PreloadData {
191
191
  }
192
192
  export type FontFaceMetrics = Pick<Font, 'ascent' | 'descent' | 'lineGap' | 'unitsPerEm' | 'xWidthAvg'>;
193
193
  export type GenericFallbackName = (typeof GENERIC_FALLBACK_NAMES)[number];
194
+ export type FallbackVariant = 'normal' | 'bold';
194
195
  export type Defaults = Required<Pick<ResolvedFontFamily, 'weights' | 'styles' | 'subsets' | 'fallbacks' | 'optimizedFallbacks' | 'formats'>>;
195
196
  export interface FontFileData {
196
197
  id: string;
@@ -214,6 +215,7 @@ export interface FontData {
214
215
  }>;
215
216
  weight?: string;
216
217
  style?: string;
218
+ subset?: string;
217
219
  }
218
220
  /**
219
221
  * Holds associations of CSS variables and font data to be exposed via virtual module.
@@ -13,6 +13,7 @@ import {
13
13
  isImageMetadata
14
14
  } from "./types.js";
15
15
  import { isESMImportedImage, isRemoteImage, resolveSrc } from "./utils/imageKind.js";
16
+ import { resolveDefaultOutputFormat } from "./utils/inferSourceFormat.js";
16
17
  import { inferRemoteSize } from "./utils/remoteProbe.js";
17
18
  import { createPlaceholderURL, stringifyPlaceholderURL } from "./utils/url.js";
18
19
  import { verifyOptions } from "./services/service.js";
@@ -73,6 +74,9 @@ async function getImage(options, imageConfig) {
73
74
  const result = await getRemoteSize(resolvedOptions.src);
74
75
  resolvedOptions.width ??= result.width;
75
76
  resolvedOptions.height ??= result.height;
77
+ if (result.format) {
78
+ resolvedOptions.format ??= resolveDefaultOutputFormat(result.format);
79
+ }
76
80
  originalWidth = result.width;
77
81
  originalHeight = result.height;
78
82
  }
@@ -124,24 +128,13 @@ async function getImage(options, imageConfig) {
124
128
  }
125
129
  const currentPosition = resolvedOptions.position || "center";
126
130
  resolvedOptions["data-astro-image-pos"] = currentPosition.replace(/\s+/g, "-");
127
- if (resolvedOptions.position) {
128
- if (typeof resolvedOptions.style === "object" && resolvedOptions.style !== null) {
129
- if (!("objectPosition" in resolvedOptions.style)) {
130
- resolvedOptions.style = {
131
- ...resolvedOptions.style,
132
- objectPosition: resolvedOptions.position
133
- };
134
- }
135
- } else {
136
- const existingStyle = typeof resolvedOptions.style === "string" ? resolvedOptions.style : "";
137
- if (!existingStyle.includes("object-position")) {
138
- const positionStyle = `object-position: ${resolvedOptions.position}`;
139
- resolvedOptions.style = existingStyle ? existingStyle.replace(/;?\s*$/, "; ") + positionStyle : positionStyle;
140
- }
141
- }
142
- }
143
131
  }
144
132
  const validatedOptions = service.validateOptions ? await service.validateOptions(resolvedOptions, imageConfig) : resolvedOptions;
133
+ validatedOptions.format ??= await peekRemoteFormatForStaticEmit(
134
+ validatedOptions,
135
+ imageConfig,
136
+ service
137
+ );
145
138
  const srcSetTransforms = service.getSrcSet ? await service.getSrcSet(validatedOptions, imageConfig) : [];
146
139
  const lazyImageURLFactory = (getValue) => {
147
140
  let cached = null;
@@ -203,6 +196,17 @@ async function getImage(options, imageConfig) {
203
196
  attributes: service.getHTMLAttributes !== void 0 ? await service.getHTMLAttributes(validatedOptions, imageConfig) : {}
204
197
  };
205
198
  }
199
+ async function peekRemoteFormatForStaticEmit(options, imageConfig, service) {
200
+ if (!isRemoteImage(options.src) || !isRemoteAllowed(options.src, imageConfig) || !globalThis.astroAsset?.addStaticImage || !isLocalService(service) || !service.getRemoteSize) {
201
+ return void 0;
202
+ }
203
+ try {
204
+ const probed = await service.getRemoteSize(options.src, imageConfig);
205
+ return resolveDefaultOutputFormat(probed.format);
206
+ } catch {
207
+ return void 0;
208
+ }
209
+ }
206
210
  export {
207
211
  cssFitValues,
208
212
  getConfiguredImageService,
@@ -82,7 +82,7 @@ export type BaseServiceTransform = {
82
82
  src: string;
83
83
  width?: number;
84
84
  height?: number;
85
- format: string;
85
+ format?: string;
86
86
  quality?: string | null;
87
87
  fit?: ImageFit;
88
88
  position?: string;
@@ -1,8 +1,9 @@
1
1
  import { isRemoteAllowed } from "@astrojs/internal-helpers/remote";
2
2
  import { AstroError, AstroErrorData } from "../../core/errors/index.js";
3
3
  import { isRemotePath, joinPaths } from "../../core/path.js";
4
- import { DEFAULT_HASH_PROPS, DEFAULT_OUTPUT_FORMAT, VALID_SUPPORTED_FORMATS } from "../consts.js";
4
+ import { DEFAULT_HASH_PROPS, VALID_SUPPORTED_FORMATS } from "../consts.js";
5
5
  import { isESMImportedImage, isRemoteImage } from "../utils/imageKind.js";
6
+ import { inferSourceFormat, resolveDefaultOutputFormat } from "../utils/inferSourceFormat.js";
6
7
  import { inferRemoteSize } from "../utils/remoteProbe.js";
7
8
  function isLocalService(service) {
8
9
  if (!service) {
@@ -74,10 +75,11 @@ const baseService = {
74
75
  validateOptions(options) {
75
76
  verifyOptions(options);
76
77
  if (!options.format) {
77
- if (isESMImportedImage(options.src) && options.src.format === "svg") {
78
- options.format = "svg";
78
+ if (isESMImportedImage(options.src)) {
79
+ options.format = resolveDefaultOutputFormat(options.src.format);
79
80
  } else {
80
- options.format = DEFAULT_OUTPUT_FORMAT;
81
+ const inferred = inferSourceFormat(options.src);
82
+ if (inferred) options.format = resolveDefaultOutputFormat(inferred);
81
83
  }
82
84
  }
83
85
  if (options.width) options.width = Math.round(options.width);
@@ -120,7 +122,7 @@ const baseService = {
120
122
  const { targetWidth, targetHeight } = getTargetDimensions(options);
121
123
  const aspectRatio = targetWidth / targetHeight;
122
124
  const { widths, densities } = options;
123
- const targetFormat = options.format ?? DEFAULT_OUTPUT_FORMAT;
125
+ const targetFormat = options.format;
124
126
  let transformedWidths = (widths ?? []).sort(sortNumeric);
125
127
  let imageWidth = options.width;
126
128
  let maxWidth = Number.POSITIVE_INFINITY;
@@ -164,9 +166,7 @@ const baseService = {
164
166
  return {
165
167
  transform,
166
168
  descriptor,
167
- attributes: {
168
- type: `image/${targetFormat}`
169
- }
169
+ attributes: targetFormat ? { type: `image/${targetFormat}` } : {}
170
170
  };
171
171
  });
172
172
  },
@@ -210,7 +210,7 @@ const baseService = {
210
210
  src: params.get("href"),
211
211
  width: params.has("w") ? Number.parseInt(params.get("w")) : void 0,
212
212
  height: params.has("h") ? Number.parseInt(params.get("h")) : void 0,
213
- format: params.get("f"),
213
+ format: params.has("f") ? params.get("f") : void 0,
214
214
  quality: params.get("q"),
215
215
  fit: params.get("fit"),
216
216
  position: params.get("position") ?? void 0,
@@ -1,4 +1,5 @@
1
1
  import { AstroError, AstroErrorData } from "../../core/errors/index.js";
2
+ import { resolveDefaultOutputFormat } from "../utils/inferSourceFormat.js";
2
3
  import { detector } from "../utils/vendor/image-size/detector.js";
3
4
  import {
4
5
  baseService,
@@ -21,6 +22,9 @@ function resolveSharpQuality(quality) {
21
22
  }
22
23
  function resolveSharpEncoderOptions(transform, inputFormat, serviceConfig = {}) {
23
24
  const quality = resolveSharpQuality(transform.quality);
25
+ if (transform.format === void 0) {
26
+ return quality === void 0 ? void 0 : { quality };
27
+ }
24
28
  switch (transform.format) {
25
29
  case "jpg":
26
30
  case "jpeg":
@@ -82,11 +86,27 @@ const sharpService = {
82
86
  if (!sharp) sharp = await loadSharp();
83
87
  const transform = transformOptions;
84
88
  const kernel = config.service.config.kernel;
85
- if (transform.format === "svg") return { data: inputBuffer, format: "svg" };
86
- if (detector(inputBuffer) === "svg" && !config.dangerouslyProcessSVG) {
89
+ const bufferFormat = detector(inputBuffer);
90
+ const outputFormat = transform.format ?? resolveDefaultOutputFormat(bufferFormat);
91
+ if (outputFormat === "svg") {
92
+ if (bufferFormat && bufferFormat !== "svg") {
93
+ console.warn(
94
+ `\u26A0\uFE0F Astro expected an SVG for "${transform.src}" but the source is ${bufferFormat}. Passing it through as ${bufferFormat} instead.`
95
+ );
96
+ return { data: inputBuffer, format: bufferFormat };
97
+ }
98
+ return { data: inputBuffer, format: "svg" };
99
+ }
100
+ if (!bufferFormat) {
101
+ throw new AstroError({
102
+ ...AstroErrorData.NoImageMetadata,
103
+ message: AstroErrorData.NoImageMetadata.message(transform.src)
104
+ });
105
+ }
106
+ if (bufferFormat === "svg" && !config.dangerouslyProcessSVG) {
87
107
  throw new AstroError({
88
108
  ...AstroErrorData.UnsupportedImageFormat,
89
- message: "SVG image processing is disabled. Set `image.dangerouslyProcessSVG: true` to allow processing of SVG sources."
109
+ message: `SVG image processing is disabled, but the source for "${transform.src}" is an SVG. Pass it through unchanged by setting \`format="svg"\` on the component, or set \`image.dangerouslyProcessSVG: true\` to rasterize SVG sources.`
90
110
  });
91
111
  }
92
112
  const result = sharp(inputBuffer, {
@@ -95,15 +115,6 @@ const sharpService = {
95
115
  limitInputPixels: config.service.config.limitInputPixels
96
116
  });
97
117
  result.rotate();
98
- let format;
99
- try {
100
- ({ format } = await result.metadata());
101
- } catch {
102
- console.warn(
103
- `\u26A0\uFE0F Astro could not optimize image "${transform.src}". Sharp doesn't support this format. The image will be used unoptimized. Consider converting to WebP or placing in the public/ folder.`
104
- );
105
- return { data: inputBuffer, format: transform.format };
106
- }
107
118
  if (transform.width && transform.height) {
108
119
  const fit = transform.fit ? fitMap[transform.fit] ?? "inside" : void 0;
109
120
  result.resize({
@@ -130,23 +141,32 @@ const sharpService = {
130
141
  if (transform.background) {
131
142
  result.flatten({ background: transform.background });
132
143
  }
133
- if (transform.format) {
134
- const encoderOptions = resolveSharpEncoderOptions(transform, format, config.service.config);
135
- if (transform.format === "webp" && format === "gif") {
136
- result.webp(encoderOptions);
137
- } else if (transform.format === "webp") {
138
- result.webp(encoderOptions);
139
- } else if (transform.format === "png") {
140
- result.png(encoderOptions);
141
- } else if (transform.format === "avif") {
142
- result.avif(encoderOptions);
143
- } else if (transform.format === "jpeg" || transform.format === "jpg") {
144
- result.jpeg(encoderOptions);
145
- } else {
146
- result.toFormat(transform.format, encoderOptions);
147
- }
144
+ const encoderOptions = resolveSharpEncoderOptions(
145
+ { format: outputFormat, quality: transform.quality },
146
+ bufferFormat,
147
+ config.service.config
148
+ );
149
+ if (outputFormat === "webp") {
150
+ result.webp(encoderOptions);
151
+ } else if (outputFormat === "png") {
152
+ result.png(encoderOptions);
153
+ } else if (outputFormat === "avif") {
154
+ result.avif(encoderOptions);
155
+ } else if (outputFormat === "jpeg" || outputFormat === "jpg") {
156
+ result.jpeg(encoderOptions);
157
+ } else {
158
+ result.toFormat(outputFormat, encoderOptions);
159
+ }
160
+ let data;
161
+ let info;
162
+ try {
163
+ ({ data, info } = await result.toBuffer({ resolveWithObject: true }));
164
+ } catch {
165
+ console.warn(
166
+ `\u26A0\uFE0F Astro could not optimize image "${transform.src}". Sharp doesn't support this format. The image will be used unoptimized. Consider converting to WebP or placing in the public/ folder.`
167
+ );
168
+ return { data: inputBuffer, format: bufferFormat };
148
169
  }
149
- const { data, info } = await result.toBuffer({ resolveWithObject: true });
150
170
  const needsCopy = "buffer" in data && data.buffer instanceof SharedArrayBuffer;
151
171
  return {
152
172
  data: needsCopy ? new Uint8Array(data) : data,
@@ -1,4 +1,20 @@
1
1
  import { cssFitValues } from "../internal.js";
2
+ const POSITION_KEYWORDS = ["top", "bottom", "left", "right", "center"];
3
+ function getPositionEntries() {
4
+ const entries = [];
5
+ for (const kw of POSITION_KEYWORDS) {
6
+ entries.push([kw, kw]);
7
+ }
8
+ for (const a of POSITION_KEYWORDS) {
9
+ for (const b of POSITION_KEYWORDS) {
10
+ if (a === b) continue;
11
+ const cssValue = `${a} ${b}`;
12
+ const dataAttr = `${a}-${b}`;
13
+ entries.push([dataAttr, cssValue]);
14
+ }
15
+ }
16
+ return entries;
17
+ }
2
18
  function generateImageStylesCSS(defaultObjectFit, defaultObjectPosition) {
3
19
  const fitStyles = cssFitValues.map(
4
20
  (fit) => `
@@ -10,11 +26,14 @@ function generateImageStylesCSS(defaultObjectFit, defaultObjectPosition) {
10
26
  :where([data-astro-image]:not([data-astro-image-fit])) {
11
27
  object-fit: ${defaultObjectFit};
12
28
  }` : "";
13
- const positionStyle = defaultObjectPosition ? `
14
- [data-astro-image-pos="${defaultObjectPosition.replace(/\s+/g, "-")}"] {
15
- object-position: ${defaultObjectPosition};
16
- }
17
-
29
+ const positionEntries = getPositionEntries();
30
+ const positionStyles = positionEntries.map(
31
+ ([dataAttr, cssValue]) => `
32
+ [data-astro-image-pos="${dataAttr}"] {
33
+ object-position: ${cssValue};
34
+ }`
35
+ ).join("\n");
36
+ const defaultPositionStyle = defaultObjectPosition ? `
18
37
  :where([data-astro-image]:not([data-astro-image-pos])) {
19
38
  object-position: ${defaultObjectPosition};
20
39
  }` : "";
@@ -30,7 +49,8 @@ function generateImageStylesCSS(defaultObjectFit, defaultObjectPosition) {
30
49
  }
31
50
  ${fitStyles}
32
51
  ${defaultFitStyle}
33
- ${positionStyle}
52
+ ${positionStyles}
53
+ ${defaultPositionStyle}
34
54
  `.trim();
35
55
  }
36
56
  export {
@@ -1,6 +1,11 @@
1
1
  /**
2
- * Infer the image format from a source path or URL by examining
3
- * the file extension. For data: URIs, the MIME type is extracted.
4
- * Returns undefined if the format cannot be determined.
2
+ * Infer the image format from a source path or URL.
3
+ *
4
+ * For `data:` URIs the MIME is read up to the first `;` or `,` (whichever comes first),
5
+ * so both `data:image/svg+xml;base64,...` and `data:image/svg+xml,<svg>...` work.
6
+ * `image/svg+xml` normalizes to `svg`; otherwise the subtype after the slash is returned.
7
+ *
8
+ * Returns undefined when the format cannot be determined.
5
9
  */
6
10
  export declare function inferSourceFormat(src: string): string | undefined;
11
+ export declare function resolveDefaultOutputFormat(sourceFormat: string | undefined): string;