astro 2.9.3 → 2.9.5

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.
@@ -13,19 +13,40 @@ const { fallback = 'animate' } = Astro.props as Props;
13
13
  <script>
14
14
  type Fallback = 'none' | 'animate' | 'swap';
15
15
  type Direction = 'forward' | 'back';
16
+ type State = {
17
+ index: number;
18
+ scrollY: number;
19
+ };
20
+ type Events = 'astro:load' | 'astro:beforeload';
21
+
22
+ const persistState = (state: State) => history.replaceState(state, '');
16
23
 
17
24
  // The History API does not tell you if navigation is forward or back, so
18
25
  // you can figure it using an index. On pushState the index is incremented so you
19
26
  // can use that to determine popstate if going forward or back.
20
27
  let currentHistoryIndex = history.state?.index || 0;
21
28
  if (!history.state) {
22
- history.replaceState({ index: currentHistoryIndex }, document.title);
29
+ persistState({ index: currentHistoryIndex, scrollY: 0 });
23
30
  }
24
31
 
25
32
  const supportsViewTransitions = !!document.startViewTransition;
26
33
  const transitionEnabledOnThisPage = () =>
27
34
  !!document.querySelector('[name="astro-view-transitions-enabled"]');
28
- const onload = () => document.dispatchEvent(new Event('astro:load'));
35
+ const triggerEvent = (name: Events) => document.dispatchEvent(new Event(name));
36
+ const onload = () => triggerEvent('astro:load');
37
+
38
+ const throttle = (cb: (...args: any[]) => any, delay: number) => {
39
+ let wait = false;
40
+ return (...args: any[]) => {
41
+ if (wait) return;
42
+
43
+ cb(...args);
44
+ wait = true;
45
+ setTimeout(() => {
46
+ wait = false;
47
+ }, delay);
48
+ };
49
+ };
29
50
 
30
51
  async function getHTML(href: string) {
31
52
  const res = await fetch(href);
@@ -62,10 +83,18 @@ const { fallback = 'animate' } = Astro.props as Props;
62
83
 
63
84
  const parser = new DOMParser();
64
85
 
65
- async function updateDOM(dir: Direction, html: string, fallback?: Fallback) {
86
+ async function updateDOM(dir: Direction, html: string, state?: State, fallback?: Fallback) {
66
87
  const doc = parser.parseFromString(html, 'text/html');
67
88
  doc.documentElement.dataset.astroTransition = dir;
68
- const swap = () => document.documentElement.replaceWith(doc.documentElement);
89
+ const swap = () => {
90
+ document.documentElement.replaceWith(doc.documentElement);
91
+
92
+ if (state?.scrollY != null) {
93
+ scrollTo(0, state.scrollY);
94
+ }
95
+
96
+ triggerEvent('astro:beforeload');
97
+ };
69
98
 
70
99
  // Wait on links to finish, to prevent FOUC
71
100
  const links = Array.from(doc.querySelectorAll('head link[rel=stylesheet]')).map(
@@ -98,7 +127,7 @@ const { fallback = 'animate' } = Astro.props as Props;
98
127
  }
99
128
  }
100
129
 
101
- async function navigate(dir: Direction, href: string) {
130
+ async function navigate(dir: Direction, href: string, state?: State) {
102
131
  let finished: Promise<void>;
103
132
  const { html, ok } = await getHTML(href);
104
133
  // If there is a problem fetching the new page, just do an MPA navigation to it.
@@ -107,9 +136,9 @@ const { fallback = 'animate' } = Astro.props as Props;
107
136
  return;
108
137
  }
109
138
  if (supportsViewTransitions) {
110
- finished = document.startViewTransition(() => updateDOM(dir, html)).finished;
139
+ finished = document.startViewTransition(() => updateDOM(dir, html, state)).finished;
111
140
  } else {
112
- finished = updateDOM(dir, html, getFallback());
141
+ finished = updateDOM(dir, html, state, getFallback());
113
142
  }
114
143
  try {
115
144
  await finished;
@@ -148,6 +177,7 @@ const { fallback = 'animate' } = Astro.props as Props;
148
177
  link.href &&
149
178
  (!link.target || link.target === '_self') &&
150
179
  link.origin === location.origin &&
180
+ !link.hash &&
151
181
  ev.button === 0 && // left clicks only
152
182
  !ev.metaKey && // new tab (mac)
153
183
  !ev.ctrlKey && // new tab (windows)
@@ -159,19 +189,30 @@ const { fallback = 'animate' } = Astro.props as Props;
159
189
  ev.preventDefault();
160
190
  navigate('forward', link.href);
161
191
  currentHistoryIndex++;
162
- history.pushState({ index: currentHistoryIndex }, '', link.href);
192
+ const newState: State = { index: currentHistoryIndex, scrollY };
193
+ persistState({ index: currentHistoryIndex - 1, scrollY });
194
+ history.pushState(newState, '', link.href);
163
195
  }
164
196
  });
165
- window.addEventListener('popstate', () => {
197
+ addEventListener('popstate', (ev) => {
166
198
  if (!transitionEnabledOnThisPage()) {
167
199
  // The current page doesn't haven't View Transitions,
168
200
  // respect that with a full page reload
169
201
  location.reload();
170
202
  return;
171
203
  }
172
- const nextIndex = history.state?.index ?? currentHistoryIndex + 1;
204
+
205
+ // hash change creates no state.
206
+ if (ev.state === null) {
207
+ persistState({ index: currentHistoryIndex, scrollY });
208
+ ev.preventDefault();
209
+ return;
210
+ }
211
+
212
+ const state: State | undefined = history.state;
213
+ const nextIndex = state?.index ?? currentHistoryIndex + 1;
173
214
  const direction: Direction = nextIndex > currentHistoryIndex ? 'forward' : 'back';
174
- navigate(direction, location.href);
215
+ navigate(direction, location.href, state);
175
216
  currentHistoryIndex = nextIndex;
176
217
  });
177
218
 
@@ -194,5 +235,16 @@ const { fallback = 'animate' } = Astro.props as Props;
194
235
  );
195
236
  });
196
237
  addEventListener('load', onload);
238
+ // There's not a good way to record scroll position before a back button.
239
+ // So the way we do it is by listening to scroll and just continuously recording it.
240
+ addEventListener(
241
+ 'scroll',
242
+ throttle(() => {
243
+ if (history.state) {
244
+ persistState({ ...history.state, scrollY });
245
+ }
246
+ }, 300),
247
+ { passive: true }
248
+ );
197
249
  }
198
250
  </script>
@@ -1,5 +1,7 @@
1
+ import type { AstroSettings } from '../@types/astro.js';
1
2
  import { type ImageService } from './services/service.js';
2
3
  import type { GetImageResult, ImageMetadata, ImageTransform } from './types.js';
4
+ export declare function injectImageEndpoint(settings: AstroSettings): AstroSettings;
3
5
  export declare function isESMImportedImage(src: ImageMetadata | string): src is ImageMetadata;
4
6
  export declare function getConfiguredImageService(): Promise<ImageService>;
5
7
  export declare function getImage(options: ImageTransform, serviceConfig: Record<string, any>): Promise<GetImageResult>;
@@ -1,5 +1,13 @@
1
1
  import { AstroError, AstroErrorData } from "../core/errors/index.js";
2
2
  import { isLocalService } from "./services/service.js";
3
+ function injectImageEndpoint(settings) {
4
+ settings.injectedRoutes.push({
5
+ pattern: "/_image",
6
+ entryPoint: "astro/assets/image-endpoint",
7
+ prerender: false
8
+ });
9
+ return settings;
10
+ }
3
11
  function isESMImportedImage(src) {
4
12
  return typeof src === "object";
5
13
  }
@@ -44,5 +52,6 @@ async function getImage(options, serviceConfig) {
44
52
  export {
45
53
  getConfiguredImageService,
46
54
  getImage,
55
+ injectImageEndpoint,
47
56
  isESMImportedImage
48
57
  };
@@ -18,10 +18,8 @@ const qualityTable = {
18
18
  webp: baseQuality
19
19
  // Squoosh's PNG encoder does not support a quality setting, so we can skip that here
20
20
  };
21
- async function getRotationForEXIF(transform, inputBuffer) {
22
- const filePath = transform.src.slice("/@fs".length);
23
- const filePathURL = new URL("." + filePath, "file:");
24
- const meta = await imageMetadata(filePathURL, inputBuffer);
21
+ async function getRotationForEXIF(inputBuffer) {
22
+ const meta = await imageMetadata(inputBuffer);
25
23
  if (!meta)
26
24
  return void 0;
27
25
  switch (meta.orientation) {
@@ -47,7 +45,7 @@ const service = {
47
45
  if (format === "svg")
48
46
  return { data: inputBuffer, format: "svg" };
49
47
  const operations = [];
50
- const rotation = await getRotationForEXIF(transform, inputBuffer);
48
+ const rotation = await getRotationForEXIF(inputBuffer);
51
49
  if (rotation) {
52
50
  operations.push(rotation);
53
51
  }
@@ -15,7 +15,7 @@ declare global {
15
15
  };
16
16
  }
17
17
  /**
18
- * Type returned by ESM imports of images and direct calls to imageMetadata
18
+ * Type returned by ESM imports of images
19
19
  */
20
20
  export interface ImageMetadata {
21
21
  src: string;
@@ -1,4 +1,4 @@
1
- import fs from "node:fs";
1
+ import fs from "node:fs/promises";
2
2
  import path from "node:path";
3
3
  import { fileURLToPath, pathToFileURL } from "node:url";
4
4
  import { prependForwardSlash, slash } from "../../core/path.js";
@@ -8,26 +8,36 @@ async function emitESMImage(id, watchMode, fileEmitter) {
8
8
  return void 0;
9
9
  }
10
10
  const url = pathToFileURL(id);
11
- const meta = await imageMetadata(url);
12
- if (!meta) {
11
+ let fileData;
12
+ try {
13
+ fileData = await fs.readFile(url);
14
+ } catch (err) {
13
15
  return void 0;
14
16
  }
17
+ const fileMetadata = await imageMetadata(fileData);
18
+ if (!fileMetadata) {
19
+ return void 0;
20
+ }
21
+ const emittedImage = {
22
+ src: "",
23
+ ...fileMetadata
24
+ };
15
25
  if (!watchMode) {
16
26
  const pathname = decodeURI(url.pathname);
17
- const filename = path.basename(pathname, path.extname(pathname) + `.${meta.format}`);
27
+ const filename = path.basename(pathname, path.extname(pathname) + `.${fileMetadata.format}`);
18
28
  const handle = fileEmitter({
19
29
  name: filename,
20
- source: await fs.promises.readFile(url),
30
+ source: await fs.readFile(url),
21
31
  type: "asset"
22
32
  });
23
- meta.src = `__ASTRO_ASSET_IMAGE__${handle}__`;
33
+ emittedImage.src = `__ASTRO_ASSET_IMAGE__${handle}__`;
24
34
  } else {
25
- url.searchParams.append("origWidth", meta.width.toString());
26
- url.searchParams.append("origHeight", meta.height.toString());
27
- url.searchParams.append("origFormat", meta.format);
28
- meta.src = `/@fs` + prependForwardSlash(fileURLToNormalizedPath(url));
35
+ url.searchParams.append("origWidth", fileMetadata.width.toString());
36
+ url.searchParams.append("origHeight", fileMetadata.height.toString());
37
+ url.searchParams.append("origFormat", fileMetadata.format);
38
+ emittedImage.src = `/@fs` + prependForwardSlash(fileURLToNormalizedPath(url));
29
39
  }
30
- return meta;
40
+ return emittedImage;
31
41
  }
32
42
  function fileURLToNormalizedPath(filePath) {
33
43
  return slash(fileURLToPath(filePath) + filePath.search).replace(/\\/g, "/");
@@ -1,3 +1,3 @@
1
1
  /// <reference types="node" />
2
2
  import type { ImageMetadata } from '../types.js';
3
- export declare function imageMetadata(src: URL | string, data?: Buffer): Promise<ImageMetadata | undefined>;
3
+ export declare function imageMetadata(data: Buffer): Promise<Omit<ImageMetadata, 'src'> | undefined>;
@@ -1,22 +1,11 @@
1
- import fs from "node:fs/promises";
2
- import { fileURLToPath } from "node:url";
3
1
  import imageSize from "../vendor/image-size/index.js";
4
- async function imageMetadata(src, data) {
5
- let file = data;
6
- if (!file) {
7
- try {
8
- file = await fs.readFile(src);
9
- } catch (e) {
10
- return void 0;
11
- }
12
- }
13
- const { width, height, type, orientation } = imageSize(file);
2
+ async function imageMetadata(data) {
3
+ const { width, height, type, orientation } = imageSize(data);
14
4
  const isPortrait = (orientation || 0) >= 5;
15
5
  if (!width || !height || !type) {
16
6
  return void 0;
17
7
  }
18
8
  return {
19
- src: fileURLToPath(src),
20
9
  width: isPortrait ? height : width,
21
10
  height: isPortrait ? width : height,
22
11
  format: type,
@@ -86,7 +86,7 @@ async function add(names, { flags, logging }) {
86
86
  ["svelte", "astro add svelte"],
87
87
  ["solid-js", "astro add solid-js"],
88
88
  ["lit", "astro add lit"],
89
- ["alpine", "astro add alpine"]
89
+ ["alpinejs", "astro add alpinejs"]
90
90
  ],
91
91
  "SSR Adapters": [
92
92
  ["netlify", "astro add netlify"],
@@ -17,11 +17,10 @@ async function loadSettings({ cmd, flags, logging }) {
17
17
  await handleConfigError(e, { cmd, cwd: root, flags, logging });
18
18
  return {};
19
19
  });
20
- const mode = cmd === "build" ? "build" : "dev";
21
20
  if (!initialAstroConfig)
22
21
  return;
23
22
  telemetry.record(event.eventCliSession(cmd, initialUserConfig, flags));
24
- return createSettings(initialAstroConfig, mode, root);
23
+ return createSettings(initialAstroConfig, root);
25
24
  }
26
25
  async function handleConfigError(e, { cmd, cwd, flags, logging }) {
27
26
  const path = await resolveConfigPath({ cwd, flags, fs });
@@ -26,7 +26,7 @@ function getViteConfig(inlineConfig) {
26
26
  level: "info"
27
27
  };
28
28
  const { astroConfig: config } = await openConfig({ cmd });
29
- const settings = createSettings(config, cmd, inlineConfig.root);
29
+ const settings = createSettings(config, inlineConfig.root);
30
30
  await runHookConfigSetup({ settings, command: cmd, logging });
31
31
  const viteConfig = await createVite(
32
32
  {
@@ -84,11 +84,14 @@ function chunkIsPage(settings, output, internals) {
84
84
  return false;
85
85
  }
86
86
  async function generatePages(opts, internals) {
87
+ var _a, _b;
87
88
  const timer = performance.now();
88
89
  const ssr = isServerLikeOutput(opts.settings.config);
89
90
  const outFolder = ssr ? opts.settings.config.build.server : getOutDirWithinCwd(opts.settings.config.outDir);
90
- if (ssr && !hasPrerenderedPages(internals))
91
+ if (ssr && !hasPrerenderedPages(internals)) {
92
+ (_a = globalThis == null ? void 0 : globalThis.astroAsset) == null ? true : delete _a.addStaticImage;
91
93
  return;
94
+ }
92
95
  const verb = ssr ? "prerendering" : "generating";
93
96
  info(opts.logging, null, `
94
97
  ${bgGreen(black(` ${verb} static routes `))}`);
@@ -139,7 +142,7 @@ ${bgGreen(black(` generating optimized images `))}`);
139
142
  for (const imageData of getStaticImageList()) {
140
143
  await generateImage(opts, imageData[1].options, imageData[1].path);
141
144
  }
142
- delete globalThis.astroAsset.addStaticImage;
145
+ (_b = globalThis == null ? void 0 : globalThis.astroAsset) == null ? true : delete _b.addStaticImage;
143
146
  }
144
147
  await runHookBuildGenerated({
145
148
  config: opts.settings.config,
@@ -1,12 +1,14 @@
1
1
  import * as colors from "kleur/colors";
2
2
  import fs from "node:fs";
3
3
  import { performance } from "node:perf_hooks";
4
+ import { injectImageEndpoint } from "../../assets/internal.js";
4
5
  import {
5
6
  runHookBuildDone,
6
7
  runHookBuildStart,
7
8
  runHookConfigDone,
8
9
  runHookConfigSetup
9
10
  } from "../../integrations/index.js";
11
+ import { isServerLikeOutput } from "../../prerender/utils.js";
10
12
  import { createVite } from "../create-vite.js";
11
13
  import { debug, info, levels, timerMessage, warn } from "../logger/core.js";
12
14
  import { printHelp } from "../messages.js";
@@ -60,6 +62,9 @@ class AstroBuilder {
60
62
  command: "build",
61
63
  logging
62
64
  });
65
+ if (this.settings.config.experimental.assets && isServerLikeOutput(this.settings.config)) {
66
+ this.settings = injectImageEndpoint(this.settings);
67
+ }
63
68
  this.manifest = createRouteManifest({ settings: this.settings }, this.logging);
64
69
  const viteConfig = await createVite(
65
70
  {
@@ -1,4 +1,4 @@
1
1
  import type { AstroConfig, AstroSettings, AstroUserConfig } from '../../@types/astro';
2
- export declare function createBaseSettings(config: AstroConfig, mode: 'build' | 'dev'): AstroSettings;
3
- export declare function createSettings(config: AstroConfig, mode: 'build' | 'dev', cwd?: string): AstroSettings;
2
+ export declare function createBaseSettings(config: AstroConfig): AstroSettings;
3
+ export declare function createSettings(config: AstroConfig, cwd?: string): AstroSettings;
4
4
  export declare function createDefaultDevSettings(userConfig?: AstroUserConfig, root?: string | URL): Promise<AstroSettings>;
@@ -3,7 +3,6 @@ import path from "node:path";
3
3
  import { fileURLToPath, pathToFileURL } from "node:url";
4
4
  import { getContentPaths } from "../../content/index.js";
5
5
  import jsxRenderer from "../../jsx/renderer.js";
6
- import { isServerLikeOutput } from "../../prerender/utils.js";
7
6
  import { markdownContentEntryType } from "../../vite-plugin-markdown/content-entry-type.js";
8
7
  import { getDefaultClientDirectives } from "../client-directive/index.js";
9
8
  import { AstroError, AstroErrorData } from "../errors/index.js";
@@ -12,14 +11,14 @@ import { SUPPORTED_MARKDOWN_FILE_EXTENSIONS } from "./../constants.js";
12
11
  import { createDefaultDevConfig } from "./config.js";
13
12
  import { AstroTimer } from "./timer.js";
14
13
  import { loadTSConfig } from "./tsconfig.js";
15
- function createBaseSettings(config, mode) {
14
+ function createBaseSettings(config) {
16
15
  const { contentDir } = getContentPaths(config);
17
16
  return {
18
17
  config,
19
18
  tsConfig: void 0,
20
19
  tsConfigPath: void 0,
21
20
  adapter: void 0,
22
- injectedRoutes: config.experimental.assets && (isServerLikeOutput(config) || mode === "dev") ? [{ pattern: "/_image", entryPoint: "astro/assets/image-endpoint", prerender: false }] : [],
21
+ injectedRoutes: [],
23
22
  pageExtensions: [".astro", ".html", ...SUPPORTED_MARKDOWN_FILE_EXTENSIONS],
24
23
  contentEntryTypes: [markdownContentEntryType],
25
24
  dataEntryTypes: [
@@ -92,9 +91,9 @@ function createBaseSettings(config, mode) {
92
91
  timer: new AstroTimer()
93
92
  };
94
93
  }
95
- function createSettings(config, mode, cwd) {
94
+ function createSettings(config, cwd) {
96
95
  const tsconfig = loadTSConfig(cwd);
97
- const settings = createBaseSettings(config, mode);
96
+ const settings = createBaseSettings(config);
98
97
  const watchFiles = (tsconfig == null ? void 0 : tsconfig.exists) ? [tsconfig.path, ...tsconfig.extendedPaths] : [];
99
98
  if (cwd) {
100
99
  watchFiles.push(fileURLToPath(new URL("./package.json", pathToFileURL(cwd))));
@@ -109,7 +108,7 @@ async function createDefaultDevSettings(userConfig = {}, root) {
109
108
  root = fileURLToPath(root);
110
109
  }
111
110
  const config = await createDefaultDevConfig(userConfig, root);
112
- return createBaseSettings(config, "dev");
111
+ return createBaseSettings(config);
113
112
  }
114
113
  export {
115
114
  createBaseSettings,
@@ -1,4 +1,4 @@
1
- const ASTRO_VERSION = "2.9.3";
1
+ const ASTRO_VERSION = "2.9.5";
2
2
  const SUPPORTED_MARKDOWN_FILE_EXTENSIONS = [
3
3
  ".markdown",
4
4
  ".mdown",
@@ -1,5 +1,6 @@
1
1
  import nodeFs from "node:fs";
2
2
  import * as vite from "vite";
3
+ import { injectImageEndpoint } from "../../assets/internal.js";
3
4
  import {
4
5
  runHookConfigDone,
5
6
  runHookConfigSetup,
@@ -29,6 +30,9 @@ async function createContainer(params = {}) {
29
30
  logging,
30
31
  isRestart
31
32
  });
33
+ if (settings.config.experimental.assets) {
34
+ settings = injectImageEndpoint(settings);
35
+ }
32
36
  const { host, headers, open } = settings.config.server;
33
37
  const rendererClientEntries = settings.renderers.map((r) => r.clientEntrypoint).filter(Boolean);
34
38
  const viteConfig = await createVite(
@@ -54,7 +54,7 @@ async function dev(settings, options) {
54
54
  isRestart: options.isRestart
55
55
  })
56
56
  );
57
- const currentVersion = "2.9.3";
57
+ const currentVersion = "2.9.5";
58
58
  if (currentVersion.includes("-")) {
59
59
  warn(options.logging, null, msg.prerelease({ currentVersion }));
60
60
  }
@@ -62,7 +62,7 @@ async function restartContainer({
62
62
  });
63
63
  info(logging, "astro", logMsg + "\n");
64
64
  let astroConfig = newConfig.astroConfig;
65
- const settings = createSettings(astroConfig, "dev", resolvedRoot);
65
+ const settings = createSettings(astroConfig, resolvedRoot);
66
66
  await close();
67
67
  return {
68
68
  container: await createRestartedContainer(container, settings, needsStart),
@@ -47,7 +47,7 @@ function serverStart({
47
47
  base,
48
48
  isRestart = false
49
49
  }) {
50
- const version = "2.9.3";
50
+ const version = "2.9.5";
51
51
  const localPrefix = `${dim("\u2503")} Local `;
52
52
  const networkPrefix = `${dim("\u2503")} Network `;
53
53
  const emptyPrefix = " ".repeat(11);
@@ -233,7 +233,7 @@ function printHelp({
233
233
  message.push(
234
234
  linebreak(),
235
235
  ` ${bgGreen(black(` ${commandName} `))} ${green(
236
- `v${"2.9.3"}`
236
+ `v${"2.9.5"}`
237
237
  )} ${headline}`
238
238
  );
239
239
  }
@@ -15,6 +15,6 @@ export declare class AstroComponentInstance {
15
15
  init(result: SSRResult): Promise<AstroFactoryReturnValue>;
16
16
  render(destination: RenderDestination): Promise<void>;
17
17
  }
18
- export declare function createAstroComponentInstance(result: SSRResult, displayName: string, factory: AstroComponentFactory, props: ComponentProps, slots?: any): Promise<AstroComponentInstance>;
18
+ export declare function createAstroComponentInstance(result: SSRResult, displayName: string, factory: AstroComponentFactory, props: ComponentProps, slots?: any): AstroComponentInstance;
19
19
  export declare function isAstroComponentInstance(obj: unknown): obj is AstroComponentInstance;
20
20
  export {};
@@ -17,6 +17,8 @@ class AstroComponentInstance {
17
17
  }
18
18
  }
19
19
  async init(result) {
20
+ if (this.returnValue !== void 0)
21
+ return this.returnValue;
20
22
  this.returnValue = this.factory(result, this.props, this.slotValues);
21
23
  return this.returnValue;
22
24
  }
@@ -47,15 +49,11 @@ function validateComponentProps(props, displayName) {
47
49
  }
48
50
  }
49
51
  }
50
- async function createAstroComponentInstance(result, displayName, factory, props, slots = {}) {
52
+ function createAstroComponentInstance(result, displayName, factory, props, slots = {}) {
51
53
  validateComponentProps(props, displayName);
52
54
  const instance = new AstroComponentInstance(result, props, slots, factory);
53
55
  if (isAPropagatingComponent(result, factory) && !result._metadata.propagators.has(factory)) {
54
56
  result._metadata.propagators.set(factory, instance);
55
- const returnValue = await instance.init(result);
56
- if (isHeadAndContent(returnValue)) {
57
- result._metadata.extraHead.push(returnValue.head);
58
- }
59
57
  }
60
58
  return instance;
61
59
  }
@@ -42,6 +42,9 @@ async function renderToReadableStream(result, componentFactory, props, children,
42
42
  if (templateResult instanceof Response)
43
43
  return templateResult;
44
44
  let renderedFirstPageChunk = false;
45
+ if (isPage) {
46
+ await bufferHeadContent(result);
47
+ }
45
48
  return new ReadableStream({
46
49
  start(controller) {
47
50
  const destination = {
@@ -93,6 +96,19 @@ async function callComponentAsTemplateResultOrResponse(result, componentFactory,
93
96
  }
94
97
  return isHeadAndContent(factoryResult) ? factoryResult.content : factoryResult;
95
98
  }
99
+ async function bufferHeadContent(result) {
100
+ const iterator = result._metadata.propagators.values();
101
+ while (true) {
102
+ const { value, done } = iterator.next();
103
+ if (done) {
104
+ break;
105
+ }
106
+ const returnValue = await value.init(result);
107
+ if (isHeadAndContent(returnValue)) {
108
+ result._metadata.extraHead.push(returnValue.head);
109
+ }
110
+ }
111
+ }
96
112
  export {
97
113
  renderToReadableStream,
98
114
  renderToString
@@ -320,7 +320,7 @@ async function renderHTMLComponent(result, Component, _props, slots = {}) {
320
320
  };
321
321
  }
322
322
  async function renderAstroComponent(result, displayName, Component, props, slots = {}) {
323
- const instance = await createAstroComponentInstance(result, displayName, Component, props, slots);
323
+ const instance = createAstroComponentInstance(result, displayName, Component, props, slots);
324
324
  const chunks = [];
325
325
  const temporaryDestination = {
326
326
  write: (chunk) => chunks.push(chunk)
@@ -22,7 +22,7 @@ type HandleRoute = {
22
22
  incomingRequest: http.IncomingMessage;
23
23
  incomingResponse: http.ServerResponse;
24
24
  manifest: SSRManifest;
25
- status?: number;
25
+ status?: 404 | 500;
26
26
  };
27
27
  export declare function handleRoute({ matchedRoute, url, pathname, status, body, origin, env, manifestData, incomingRequest, incomingResponse, manifest, }: HandleRoute): Promise<void>;
28
28
  export {};
@@ -3,6 +3,7 @@ import { attachToResponse } from "../core/cookies/index.js";
3
3
  import { AstroErrorData, isAstroError } from "../core/errors/index.js";
4
4
  import { warn } from "../core/logger/core.js";
5
5
  import { loadMiddleware } from "../core/middleware/loadMiddleware.js";
6
+ import { isEndpointResult } from "../core/render/core.js";
6
7
  import {
7
8
  createRenderContext,
8
9
  getParamsAndProps,
@@ -151,7 +152,7 @@ async function handleRoute({
151
152
  });
152
153
  const onRequest = (_a = options.middleware) == null ? void 0 : _a.onRequest;
153
154
  const result = await tryRenderRoute(route.type, renderContext, env, mod, onRequest);
154
- if (route.type === "endpoint" && !(result instanceof Response)) {
155
+ if (isEndpointResult(result, route.type)) {
155
156
  if (result.type === "response") {
156
157
  if (result.response.headers.get("X-Astro-Response") === "Not-Found") {
157
158
  const fourOhFourRoute = await matchRoute("/404", env, manifestData);
@@ -186,7 +187,7 @@ async function handleRoute({
186
187
  attachToResponse(response, result.cookies);
187
188
  await writeWebResponse(incomingResponse, response);
188
189
  }
189
- } else if (result instanceof Response) {
190
+ } else {
190
191
  if (result.status === 404) {
191
192
  const fourOhFourRoute = await matchRoute("/404", env, manifestData);
192
193
  return handleRoute({
@@ -204,7 +205,17 @@ async function handleRoute({
204
205
  });
205
206
  }
206
207
  let response = result;
207
- if (status && response.status !== status) {
208
+ if (
209
+ // We are in a recursion, and it's possible that this function is called itself with a status code
210
+ // By default, the status code passed via parameters is computed by the matched route.
211
+ //
212
+ // By default, we should give priority to the status code passed, although it's possible that
213
+ // the `Response` emitted by the user is a redirect. If so, then return the returned response.
214
+ response.status < 400 && response.status >= 300
215
+ ) {
216
+ await writeSSRResult(request, response, incomingResponse);
217
+ return;
218
+ } else if (status && response.status !== status && (status === 404 || status === 500)) {
208
219
  response = new Response(result.body, { ...result, status });
209
220
  }
210
221
  await writeSSRResult(request, response, incomingResponse);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "astro",
3
- "version": "2.9.3",
3
+ "version": "2.9.5",
4
4
  "description": "Astro is a modern site builder with web best practices, performance, and DX front-of-mind.",
5
5
  "type": "module",
6
6
  "author": "withastro",
@@ -102,7 +102,7 @@
102
102
  "vendor"
103
103
  ],
104
104
  "dependencies": {
105
- "@astrojs/compiler": "^1.6.0",
105
+ "@astrojs/compiler": "^1.6.3",
106
106
  "@astrojs/internal-helpers": "^0.1.1",
107
107
  "@astrojs/language-server": "^1.0.0",
108
108
  "@astrojs/markdown-remark": "^2.2.1",