astro 7.0.0-alpha.2 → 7.0.0-beta.4

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 (93) hide show
  1. package/dist/assets/build/generate.js +4 -3
  2. package/dist/assets/fonts/core/collect-font-data.js +1 -0
  3. package/dist/assets/fonts/types.d.ts +1 -0
  4. package/dist/cli/add/index.js +1 -44
  5. package/dist/cli/dev/background.js +1 -1
  6. package/dist/cli/dev/index.js +1 -1
  7. package/dist/cli/flags.js +4 -6
  8. package/dist/cli/help/index.js +1 -2
  9. package/dist/cli/index.js +1 -15
  10. package/dist/cli/infra/build-time-astro-version-provider.js +1 -1
  11. package/dist/container/index.d.ts +3 -3
  12. package/dist/container/index.js +1 -4
  13. package/dist/content/content-layer.js +3 -3
  14. package/dist/content/runtime.d.ts +1 -1
  15. package/dist/content/runtime.js +1 -0
  16. package/dist/content/vite-plugin-content-virtual-mod.js +27 -0
  17. package/dist/core/app/base.d.ts +1 -1
  18. package/dist/core/app/base.js +14 -24
  19. package/dist/core/app/dev/pipeline.js +0 -9
  20. package/dist/core/app/manifest.d.ts +0 -2
  21. package/dist/core/app/manifest.js +0 -8
  22. package/dist/core/app/types.d.ts +1 -8
  23. package/dist/core/base-pipeline.d.ts +3 -9
  24. package/dist/core/base-pipeline.js +4 -23
  25. package/dist/core/build/app.d.ts +0 -2
  26. package/dist/core/build/app.js +0 -5
  27. package/dist/core/build/generate.js +0 -14
  28. package/dist/core/build/pipeline.js +0 -9
  29. package/dist/core/build/plugins/plugin-css.js +1 -0
  30. package/dist/core/build/plugins/plugin-manifest.js +4 -9
  31. package/dist/core/config/config.js +3 -2
  32. package/dist/core/config/schemas/base.d.ts +9 -22
  33. package/dist/core/config/schemas/base.js +11 -27
  34. package/dist/core/config/schemas/relative.d.ts +15 -36
  35. package/dist/core/config/validate.js +10 -2
  36. package/dist/core/constants.js +1 -1
  37. package/dist/core/dev/dev.js +1 -1
  38. package/dist/core/errors/default-handler.js +21 -8
  39. package/dist/core/fetch/fetch-state.js +3 -16
  40. package/dist/core/fetch/types.d.ts +1 -1
  41. package/dist/core/fetch/vite-plugin.js +4 -6
  42. package/dist/core/hono/index.d.ts +1 -0
  43. package/dist/core/hono/index.js +1 -0
  44. package/dist/core/logger/impls/node.js +0 -1
  45. package/dist/core/logger/load.js +3 -2
  46. package/dist/core/messages/runtime.js +1 -1
  47. package/dist/core/middleware/index.js +8 -1
  48. package/dist/core/middleware/vite-plugin.d.ts +1 -0
  49. package/dist/core/middleware/vite-plugin.js +5 -1
  50. package/dist/core/util/normalized-url.js +2 -5
  51. package/dist/core/util/pathname.d.ts +13 -7
  52. package/dist/core/util/pathname.js +9 -6
  53. package/dist/i18n/index.js +6 -2
  54. package/dist/manifest/serialized.js +4 -5
  55. package/dist/runtime/server/index.d.ts +1 -1
  56. package/dist/runtime/server/index.js +4 -0
  57. package/dist/runtime/server/jsx.js +2 -1
  58. package/dist/runtime/server/render/astro/render-template.d.ts +1 -1
  59. package/dist/runtime/server/render/astro/render.d.ts +0 -4
  60. package/dist/runtime/server/render/astro/render.js +76 -68
  61. package/dist/runtime/server/render/head.js +2 -1
  62. package/dist/runtime/server/render/index.d.ts +1 -0
  63. package/dist/runtime/server/render/index.js +2 -0
  64. package/dist/runtime/server/render/page.js +9 -44
  65. package/dist/runtime/server/render/streaming.d.ts +23 -0
  66. package/dist/runtime/server/render/streaming.js +238 -0
  67. package/dist/runtime/server/render/util.js +5 -1
  68. package/dist/types/public/config.d.ts +115 -123
  69. package/dist/types/public/context.d.ts +1 -1
  70. package/dist/types/public/internal.d.ts +0 -15
  71. package/dist/vite-plugin-app/app.js +1 -1
  72. package/dist/vite-plugin-app/pipeline.js +0 -9
  73. package/dist/vite-plugin-hmr-reload/index.js +19 -6
  74. package/dist/vite-plugin-html/transform/slots.js +4 -1
  75. package/dist/vite-plugin-pages/pages.d.ts +11 -0
  76. package/dist/vite-plugin-pages/pages.js +1 -3
  77. package/package.json +13 -7
  78. package/dist/cli/db/index.d.ts +0 -4
  79. package/dist/cli/db/index.js +0 -25
  80. package/dist/jsx/rehype.d.ts +0 -5
  81. package/dist/jsx/rehype.js +0 -241
  82. package/dist/runtime/server/html-string-cache.d.ts +0 -48
  83. package/dist/runtime/server/html-string-cache.js +0 -119
  84. package/dist/runtime/server/render/queue/builder.d.ts +0 -14
  85. package/dist/runtime/server/render/queue/builder.js +0 -182
  86. package/dist/runtime/server/render/queue/jsx-builder.d.ts +0 -33
  87. package/dist/runtime/server/render/queue/jsx-builder.js +0 -146
  88. package/dist/runtime/server/render/queue/pool.d.ts +0 -123
  89. package/dist/runtime/server/render/queue/pool.js +0 -203
  90. package/dist/runtime/server/render/queue/renderer.d.ts +0 -12
  91. package/dist/runtime/server/render/queue/renderer.js +0 -103
  92. package/dist/runtime/server/render/queue/types.d.ts +0 -81
  93. package/dist/runtime/server/render/queue/types.js +0 -0
@@ -11,6 +11,9 @@ import { normalizePath } from "../viteUtils.js";
11
11
  const MIDDLEWARE_MODULE_ID = "virtual:astro:middleware";
12
12
  const MIDDLEWARE_RESOLVED_MODULE_ID = "\0" + MIDDLEWARE_MODULE_ID;
13
13
  const NOOP_MIDDLEWARE = "\0noop-middleware";
14
+ function isMiddlewarePath(relativePath) {
15
+ return relativePath.startsWith(`${MIDDLEWARE_PATH_SEGMENT_NAME}.`) || relativePath.startsWith(`${MIDDLEWARE_PATH_SEGMENT_NAME}/`);
16
+ }
14
17
  function vitePluginMiddleware({ settings }) {
15
18
  let resolvedMiddlewareId = void 0;
16
19
  const hasIntegrationMiddleware = settings.middlewares.pre.length > 0 || settings.middlewares.post.length > 0;
@@ -26,7 +29,7 @@ function vitePluginMiddleware({ settings }) {
26
29
  const normalizedPath = viteNormalizePath(path);
27
30
  if (!normalizedPath.startsWith(normalizedSrcDir)) return;
28
31
  const relativePath = normalizedPath.slice(normalizedSrcDir.length);
29
- if (!relativePath.startsWith(`${MIDDLEWARE_PATH_SEGMENT_NAME}.`)) return;
32
+ if (!isMiddlewarePath(relativePath)) return;
30
33
  for (const name of [
31
34
  ASTRO_VITE_ENVIRONMENT_NAMES.ssr,
32
35
  ASTRO_VITE_ENVIRONMENT_NAMES.astro
@@ -135,6 +138,7 @@ function vitePluginMiddlewareBuild(opts, internals) {
135
138
  }
136
139
  export {
137
140
  MIDDLEWARE_MODULE_ID,
141
+ isMiddlewarePath,
138
142
  vitePluginMiddleware,
139
143
  vitePluginMiddlewareBuild
140
144
  };
@@ -1,15 +1,12 @@
1
1
  import { collapseDuplicateSlashes } from "@astrojs/internal-helpers/path";
2
- import { MultiLevelEncodingError, validateAndDecodePathname } from "./pathname.js";
2
+ import { validateAndDecodePathname } from "./pathname.js";
3
3
  function createNormalizedUrl(requestUrl) {
4
4
  return normalizeUrl(new URL(requestUrl));
5
5
  }
6
6
  function normalizeUrl(url) {
7
7
  try {
8
8
  url.pathname = validateAndDecodePathname(url.pathname);
9
- } catch (e) {
10
- if (e instanceof MultiLevelEncodingError) {
11
- throw e;
12
- }
9
+ } catch {
13
10
  try {
14
11
  url.pathname = decodeURI(url.pathname);
15
12
  } catch {
@@ -2,18 +2,24 @@
2
2
  * Error thrown when multi-level URL encoding is detected in a pathname.
3
3
  * This is a distinct error type so callers can handle it specifically
4
4
  * (e.g., returning a 400 response) rather than falling back to partial decoding.
5
+ *
6
+ * @deprecated No longer thrown internally — multi-level encoding is now
7
+ * decoded iteratively instead of rejected. Kept for backwards compatibility
8
+ * in case third-party code references the class.
5
9
  */
6
10
  export declare class MultiLevelEncodingError extends Error {
7
11
  constructor();
8
12
  }
9
13
  /**
10
- * Validates that a pathname is not multi-level encoded.
11
- * Detects if a pathname contains encoding that was encoded again (e.g., %2561dmin where %25 decodes to %).
12
- * This prevents double/triple encoding bypasses of security checks.
14
+ * Decodes a pathname iteratively until stable, collapsing all levels of
15
+ * percent-encoding into a single canonical form. This prevents
16
+ * double/triple encoding from bypassing middleware authorization checks
17
+ * (CVE-2025-66202) — instead of rejecting multi-level encoding, we
18
+ * fully resolve it so middleware always sees the true decoded path.
13
19
  *
14
- * @param pathname - The pathname to validate
15
- * @returns The decoded pathname if valid
16
- * @throws MultiLevelEncodingError if multi-level encoding is detected
17
- * @throws Error if the pathname contains invalid URL encoding
20
+ * @param pathname - The pathname to decode
21
+ * @returns The fully decoded pathname
22
+ * @throws Error if the pathname contains invalid URL encoding that
23
+ * cannot be decoded at all (e.g., a bare `%` not followed by hex digits)
18
24
  */
19
25
  export declare function validateAndDecodePathname(pathname: string): string;
@@ -4,19 +4,22 @@ class MultiLevelEncodingError extends Error {
4
4
  this.name = "MultiLevelEncodingError";
5
5
  }
6
6
  }
7
- const ENCODING_REGEX = /%25[0-9a-fA-F]{2}/;
8
7
  function validateAndDecodePathname(pathname) {
9
- if (ENCODING_REGEX.test(pathname)) {
10
- throw new MultiLevelEncodingError();
11
- }
12
8
  let decoded;
13
9
  try {
14
10
  decoded = decodeURI(pathname);
15
11
  } catch (_e) {
16
12
  throw new Error("Invalid URL encoding");
17
13
  }
18
- if (ENCODING_REGEX.test(decoded)) {
19
- throw new MultiLevelEncodingError();
14
+ let iterations = 0;
15
+ while (decoded !== pathname && iterations < 10) {
16
+ pathname = decoded;
17
+ try {
18
+ decoded = decodeURI(pathname);
19
+ } catch {
20
+ break;
21
+ }
22
+ iterations++;
20
23
  }
21
24
  return decoded;
22
25
  }
@@ -1,4 +1,8 @@
1
- import { appendForwardSlash, joinPaths } from "@astrojs/internal-helpers/path";
1
+ import {
2
+ appendForwardSlash,
3
+ joinPaths,
4
+ removeTrailingForwardSlash
5
+ } from "@astrojs/internal-helpers/path";
2
6
  import { shouldAppendForwardSlash } from "../core/build/util.js";
3
7
  import { REROUTE_DIRECTIVE_HEADER } from "../core/constants.js";
4
8
  import { i18nNoLocaleFoundInPath, MissingLocale } from "../core/errors/errors-data.js";
@@ -55,7 +59,7 @@ function getLocaleRelativeUrl({
55
59
  if (shouldAppendForwardSlash(trailingSlash, format)) {
56
60
  relativePath = appendForwardSlash(joinPaths(...pathsToJoin));
57
61
  } else {
58
- relativePath = joinPaths(...pathsToJoin);
62
+ relativePath = removeTrailingForwardSlash(joinPaths(...pathsToJoin));
59
63
  }
60
64
  if (relativePath === "") {
61
65
  return "/";
@@ -142,9 +142,9 @@ async function createSerializedManifest(settings, encodedKey) {
142
142
  isStrictDynamic: getStrictDynamic(settings.config.security.csp)
143
143
  };
144
144
  }
145
- let experimentalLogger = void 0;
146
- if (settings.config.experimental.logger) {
147
- experimentalLogger = settings.config.experimental.logger;
145
+ let loggerConfig = void 0;
146
+ if (settings.config.logger) {
147
+ loggerConfig = settings.config.logger;
148
148
  }
149
149
  return {
150
150
  rootDir: settings.config.root.toString(),
@@ -199,8 +199,7 @@ async function createSerializedManifest(settings, encodedKey) {
199
199
  },
200
200
  logLevel: settings.logLevel,
201
201
  shouldInjectCspMetaTags: false,
202
- experimentalQueuedRendering: settings.config.experimental?.queuedRendering,
203
- experimentalLogger
202
+ loggerConfig
204
203
  };
205
204
  }
206
205
  export {
@@ -4,7 +4,7 @@ export { renderEndpoint } from './endpoint.js';
4
4
  export { escapeHTML, HTMLBytes, HTMLString, isHTMLString, markHTMLString, unescapeHTML, } from './escape.js';
5
5
  export { renderJSX } from './jsx.js';
6
6
  export type { AstroComponentFactory, AstroComponentInstance, ComponentSlots, RenderInstruction, } from './render/index.js';
7
- export { addAttribute, createHeadAndContent, defineScriptVars, Fragment, maybeRenderHead, Renderer as Renderer, renderComponent, renderHead, renderHTMLElement, renderPage, renderScript, renderScriptElement, renderSlot, renderSlotToString, renderTemplate as render, renderTemplate, renderToString, renderUniqueStylesheet, voidElementNames, } from './render/index.js';
7
+ export { addAttribute, chunkToString, createHeadAndContent, defineScriptVars, Fragment, maybeRenderHead, Renderer as Renderer, renderComponent, renderHead, renderHTMLElement, renderPage, renderScript, renderScriptElement, renderSlot, renderSlotToString, renderStreaming, renderTemplate as render, renderTemplate, renderToString, renderUniqueStylesheet, voidElementNames, } from './render/index.js';
8
8
  export type { ServerIslandComponent } from './render/server-islands.js';
9
9
  export { templateEnter, templateExit } from './render/template-depth.js';
10
10
  export { createTransitionScope, renderTransition } from './transition.js';
@@ -12,6 +12,7 @@ import {
12
12
  import { renderJSX } from "./jsx.js";
13
13
  import {
14
14
  addAttribute,
15
+ chunkToString,
15
16
  createHeadAndContent,
16
17
  defineScriptVars,
17
18
  Fragment,
@@ -25,6 +26,7 @@ import {
25
26
  renderScriptElement,
26
27
  renderSlot,
27
28
  renderSlotToString,
29
+ renderStreaming,
28
30
  renderTemplate,
29
31
  renderTemplate as renderTemplate2,
30
32
  renderToString,
@@ -91,6 +93,7 @@ export {
91
93
  Renderer,
92
94
  __astro_tag_component__,
93
95
  addAttribute,
96
+ chunkToString,
94
97
  createAstro,
95
98
  createComponent,
96
99
  createHeadAndContent,
@@ -113,6 +116,7 @@ export {
113
116
  renderScriptElement,
114
117
  renderSlot,
115
118
  renderSlotToString,
119
+ renderStreaming,
116
120
  renderTemplate2 as renderTemplate,
117
121
  renderToString,
118
122
  renderTransition,
@@ -87,7 +87,7 @@ Did you forget to import the component or is it possible there is a typo?`);
87
87
  _slots.default.push(child);
88
88
  return;
89
89
  }
90
- if ("slot" in child.props) {
90
+ if ("slot" in child.props && !isCustomElement) {
91
91
  _slots[child.props.slot] = [..._slots[child.props.slot] ?? [], child];
92
92
  delete child.props.slot;
93
93
  return;
@@ -116,6 +116,7 @@ Did you forget to import the component or is it possible there is a typo?`);
116
116
  const _slots = {
117
117
  default: []
118
118
  };
119
+ const isCustomElement = typeof vnode.type === "string" && vnode.type.includes("-");
119
120
  extractSlots2(children);
120
121
  for (const [key, value] of Object.entries(props)) {
121
122
  if (value?.["$$slot"]) {
@@ -2,7 +2,7 @@ import type { RenderDestination } from '../common.js';
2
2
  declare const renderTemplateResultSym: unique symbol;
3
3
  export declare class RenderTemplateResult {
4
4
  [renderTemplateResultSym]: boolean;
5
- private htmlParts;
5
+ readonly htmlParts: TemplateStringsArray;
6
6
  expressions: any[];
7
7
  private error;
8
8
  constructor(htmlParts: TemplateStringsArray, expressions: unknown[]);
@@ -1,9 +1,5 @@
1
1
  import type { RouteData, SSRResult } from '../../../../types/public/internal.js';
2
2
  import type { AstroComponentFactory } from './factory.js';
3
- /**
4
- * Queue-based rendering to AsyncIterable
5
- * NOTE: Currently disabled for .astro files. Kept for potential future use.
6
- */
7
3
  export declare function renderToString(result: SSRResult, componentFactory: AstroComponentFactory, props: any, children: any, isPage?: boolean, route?: RouteData): Promise<string | Response>;
8
4
  export declare function renderToReadableStream(result: SSRResult, componentFactory: AstroComponentFactory, props: any, children: any, isPage?: boolean, route?: RouteData): Promise<ReadableStream | Response>;
9
5
  export declare function bufferHeadContent(result: SSRResult): Promise<void>;
@@ -8,18 +8,11 @@ import {
8
8
  } from "../common.js";
9
9
  import { promiseWithResolvers } from "../util.js";
10
10
  import { bufferPropagatedHead } from "../head-propagation/runtime.js";
11
+ import { renderStreaming } from "../streaming.js";
11
12
  import { isHeadAndContent } from "./head-and-content.js";
12
13
  import { isRenderTemplateResult } from "./render-template.js";
13
14
  const DOCTYPE_EXP = /<!doctype html/i;
14
- async function renderToString(result, componentFactory, props, children, isPage = false, route) {
15
- const templateResult = await callComponentAsTemplateResultOrResponse(
16
- result,
17
- componentFactory,
18
- props,
19
- children,
20
- route
21
- );
22
- if (templateResult instanceof Response) return templateResult;
15
+ async function renderStreamToString(result, templateResult, isPage) {
23
16
  let str = "";
24
17
  let renderedFirstPageChunk = false;
25
18
  if (isPage) {
@@ -38,18 +31,10 @@ async function renderToString(result, componentFactory, props, children, isPage
38
31
  str += chunkToString(result, chunk);
39
32
  }
40
33
  };
41
- await templateResult.render(destination);
34
+ await renderStreaming(templateResult, result, destination);
42
35
  return str;
43
36
  }
44
- async function renderToReadableStream(result, componentFactory, props, children, isPage = false, route) {
45
- const templateResult = await callComponentAsTemplateResultOrResponse(
46
- result,
47
- componentFactory,
48
- props,
49
- children,
50
- route
51
- );
52
- if (templateResult instanceof Response) return templateResult;
37
+ async function renderStreamToStream(result, templateResult, isPage, route) {
53
38
  let renderedFirstPageChunk = false;
54
39
  if (isPage) {
55
40
  await bufferHeadContent(result);
@@ -76,7 +61,7 @@ async function renderToReadableStream(result, componentFactory, props, children,
76
61
  };
77
62
  (async () => {
78
63
  try {
79
- await templateResult.render(destination);
64
+ await renderStreaming(templateResult, result, destination);
80
65
  controller.close();
81
66
  } catch (e) {
82
67
  if (AstroError.is(e) && !e.loc) {
@@ -93,55 +78,15 @@ async function renderToReadableStream(result, componentFactory, props, children,
93
78
  }
94
79
  });
95
80
  }
96
- async function callComponentAsTemplateResultOrResponse(result, componentFactory, props, children, route) {
97
- const factoryResult = await componentFactory(result, props, children);
98
- if (factoryResult instanceof Response) {
99
- return factoryResult;
100
- } else if (isHeadAndContent(factoryResult)) {
101
- if (!isRenderTemplateResult(factoryResult.content)) {
102
- throw new AstroError({
103
- ...AstroErrorData.OnlyResponseCanBeReturned,
104
- message: AstroErrorData.OnlyResponseCanBeReturned.message(
105
- route?.route,
106
- typeof factoryResult
107
- ),
108
- location: {
109
- file: route?.component
110
- }
111
- });
112
- }
113
- return factoryResult.content;
114
- } else if (!isRenderTemplateResult(factoryResult)) {
115
- throw new AstroError({
116
- ...AstroErrorData.OnlyResponseCanBeReturned,
117
- message: AstroErrorData.OnlyResponseCanBeReturned.message(route?.route, typeof factoryResult),
118
- location: {
119
- file: route?.component
120
- }
121
- });
122
- }
123
- return factoryResult;
124
- }
125
- async function bufferHeadContent(result) {
126
- await bufferPropagatedHead(result);
127
- }
128
- async function renderToAsyncIterable(result, componentFactory, props, children, isPage = false, route) {
129
- const templateResult = await callComponentAsTemplateResultOrResponse(
130
- result,
131
- componentFactory,
132
- props,
133
- children,
134
- route
135
- );
136
- if (templateResult instanceof Response) return templateResult;
81
+ async function renderStreamToAsyncIterable(result, templateResult, isPage, _route) {
137
82
  let renderedFirstPageChunk = false;
138
- if (isPage) {
139
- await bufferHeadContent(result);
140
- }
141
83
  let error = null;
142
84
  let next = null;
143
85
  const buffer = [];
144
86
  let renderingComplete = false;
87
+ if (isPage) {
88
+ await bufferHeadContent(result);
89
+ }
145
90
  const iterator = {
146
91
  async next() {
147
92
  if (result.cancelled) return { done: true, value: void 0 };
@@ -176,7 +121,7 @@ async function renderToAsyncIterable(result, componentFactory, props, children,
176
121
  length += bufferEntry.length;
177
122
  }
178
123
  }
179
- let mergedArray = new Uint8Array(length);
124
+ const mergedArray = new Uint8Array(length);
180
125
  let offset = 0;
181
126
  for (let i = 0, len = buffer.length; i < len; i++) {
182
127
  const item = buffer[i];
@@ -188,8 +133,6 @@ async function renderToAsyncIterable(result, componentFactory, props, children,
188
133
  }
189
134
  buffer.length = 0;
190
135
  const returnValue = {
191
- // The iterator is done when rendering has finished
192
- // and there are no more chunks to return.
193
136
  done: length === 0 && renderingComplete,
194
137
  value: mergedArray
195
138
  };
@@ -221,7 +164,7 @@ async function renderToAsyncIterable(result, componentFactory, props, children,
221
164
  }
222
165
  }
223
166
  };
224
- const renderResult = toPromise(() => templateResult.render(destination));
167
+ const renderResult = toPromise(() => renderStreaming(templateResult, result, destination));
225
168
  renderResult.catch((err) => {
226
169
  error = err;
227
170
  }).finally(() => {
@@ -234,6 +177,71 @@ async function renderToAsyncIterable(result, componentFactory, props, children,
234
177
  }
235
178
  };
236
179
  }
180
+ async function renderToString(result, componentFactory, props, children, isPage = false, route) {
181
+ const templateResult = await callComponentAsTemplateResultOrResponse(
182
+ result,
183
+ componentFactory,
184
+ props,
185
+ children,
186
+ route
187
+ );
188
+ if (templateResult instanceof Response) return templateResult;
189
+ return await renderStreamToString(result, templateResult, isPage);
190
+ }
191
+ async function renderToReadableStream(result, componentFactory, props, children, isPage = false, route) {
192
+ const templateResult = await callComponentAsTemplateResultOrResponse(
193
+ result,
194
+ componentFactory,
195
+ props,
196
+ children,
197
+ route
198
+ );
199
+ if (templateResult instanceof Response) return templateResult;
200
+ return await renderStreamToStream(result, templateResult, isPage, route);
201
+ }
202
+ async function callComponentAsTemplateResultOrResponse(result, componentFactory, props, children, route) {
203
+ const factoryResult = await componentFactory(result, props, children);
204
+ if (factoryResult instanceof Response) {
205
+ return factoryResult;
206
+ } else if (isHeadAndContent(factoryResult)) {
207
+ if (!isRenderTemplateResult(factoryResult.content)) {
208
+ throw new AstroError({
209
+ ...AstroErrorData.OnlyResponseCanBeReturned,
210
+ message: AstroErrorData.OnlyResponseCanBeReturned.message(
211
+ route?.route,
212
+ typeof factoryResult
213
+ ),
214
+ location: {
215
+ file: route?.component
216
+ }
217
+ });
218
+ }
219
+ return factoryResult.content;
220
+ } else if (!isRenderTemplateResult(factoryResult)) {
221
+ throw new AstroError({
222
+ ...AstroErrorData.OnlyResponseCanBeReturned,
223
+ message: AstroErrorData.OnlyResponseCanBeReturned.message(route?.route, typeof factoryResult),
224
+ location: {
225
+ file: route?.component
226
+ }
227
+ });
228
+ }
229
+ return factoryResult;
230
+ }
231
+ async function bufferHeadContent(result) {
232
+ await bufferPropagatedHead(result);
233
+ }
234
+ async function renderToAsyncIterable(result, componentFactory, props, children, isPage = false, route) {
235
+ const templateResult = await callComponentAsTemplateResultOrResponse(
236
+ result,
237
+ componentFactory,
238
+ props,
239
+ children,
240
+ route
241
+ );
242
+ if (templateResult instanceof Response) return templateResult;
243
+ return await renderStreamToAsyncIterable(result, templateResult, isPage, route);
244
+ }
237
245
  function toPromise(fn) {
238
246
  try {
239
247
  const result = fn();
@@ -51,7 +51,8 @@ function renderAllHeadContent(result) {
51
51
  const links = deduplicateElements(Array.from(result.links)).map(
52
52
  (link) => renderElement("link", link, false)
53
53
  );
54
- content += styles.join("\n") + links.join("\n") + scripts.join("\n");
54
+ const sep = result.compressHTML === true || result.compressHTML === "jsx" ? "" : "\n";
55
+ content += styles.join(sep) + links.join(sep) + scripts.join(sep);
55
56
  content += result._metadata.extraHead.join("");
56
57
  return markHTMLString(content);
57
58
  }
@@ -8,5 +8,6 @@ export type { RenderInstruction } from './instruction.js';
8
8
  export { renderPage } from './page.js';
9
9
  export { renderScript } from './script.js';
10
10
  export { type ComponentSlots, renderSlot, renderSlotToString } from './slot.js';
11
+ export { renderStreaming } from './streaming.js';
11
12
  export { renderScriptElement, renderUniqueStylesheet } from './tags.js';
12
13
  export { addAttribute, defineScriptVars, voidElementNames } from './util.js';
@@ -6,6 +6,7 @@ import { maybeRenderHead, renderHead } from "./head.js";
6
6
  import { renderPage } from "./page.js";
7
7
  import { renderScript } from "./script.js";
8
8
  import { renderSlot, renderSlotToString } from "./slot.js";
9
+ import { renderStreaming } from "./streaming.js";
9
10
  import { renderScriptElement, renderUniqueStylesheet } from "./tags.js";
10
11
  import { addAttribute, defineScriptVars, voidElementNames } from "./util.js";
11
12
  export {
@@ -26,6 +27,7 @@ export {
26
27
  renderScriptElement,
27
28
  renderSlot,
28
29
  renderSlotToString,
30
+ renderStreaming,
29
31
  renderTemplate,
30
32
  renderToString,
31
33
  renderUniqueStylesheet,
@@ -1,57 +1,22 @@
1
1
  import { renderToAsyncIterable, renderToReadableStream, renderToString } from "./astro/render.js";
2
2
  import { encoder } from "./common.js";
3
3
  import { renderComponentToString } from "./component.js";
4
- import { markHTMLString } from "../escape.js";
5
4
  import { renderCspContent } from "./csp.js";
6
5
  import { isDeno, isNode } from "./util.js";
7
6
  import { isAstroComponentFactory } from "./astro/factory.js";
8
- import { buildRenderQueue } from "./queue/builder.js";
9
- import { renderQueue } from "./queue/renderer.js";
10
- import { chunkToString } from "./common.js";
11
7
  async function renderPage(result, componentFactory, props, children, streaming, route) {
12
8
  if (!isAstroComponentFactory(componentFactory)) {
13
9
  result._metadata.headInTree = result.componentMetadata.get(componentFactory.moduleId)?.containsHead ?? false;
14
10
  const pageProps = { ...props ?? {}, "server:root": true };
15
- let str;
16
- if (result._experimentalQueuedRendering && result._experimentalQueuedRendering.enabled) {
17
- let vnode = await componentFactory(pageProps);
18
- if (componentFactory["astro:html"] && typeof vnode === "string") {
19
- vnode = markHTMLString(vnode);
20
- }
21
- const queue = await buildRenderQueue(
22
- vnode,
23
- result,
24
- result._experimentalQueuedRendering.pool
25
- );
26
- let html = "";
27
- let renderedFirst = false;
28
- const destination = {
29
- write(chunk) {
30
- if (chunk instanceof Response) return;
31
- if (!renderedFirst && !result.partial) {
32
- renderedFirst = true;
33
- const chunkStr = String(chunk);
34
- if (!/<!doctype html/i.test(chunkStr)) {
35
- const doctype = result.compressHTML ? "<!DOCTYPE html>" : "<!DOCTYPE html>\n";
36
- html += doctype;
37
- }
38
- }
39
- html += chunkToString(result, chunk);
40
- }
41
- };
42
- await renderQueue(queue, destination);
43
- str = html;
44
- } else {
45
- str = await renderComponentToString(
46
- result,
47
- componentFactory.name,
48
- componentFactory,
49
- pageProps,
50
- {},
51
- true,
52
- route
53
- );
54
- }
11
+ const str = await renderComponentToString(
12
+ result,
13
+ componentFactory.name,
14
+ componentFactory,
15
+ pageProps,
16
+ {},
17
+ true,
18
+ route
19
+ );
55
20
  const bytes = encoder.encode(str);
56
21
  const headers2 = new Headers([
57
22
  ["Content-Type", "text/html"],
@@ -0,0 +1,23 @@
1
+ import type { SSRResult } from '../../../types/public/internal.js';
2
+ import { type RenderDestination } from './common.js';
3
+ /**
4
+ * Streaming render engine.
5
+ *
6
+ * Walks the component tree in a single forward pass (explicit stack), batching
7
+ * consecutive static fragments into one write. The static structure
8
+ * (RenderTemplateResult HTML parts, arrays, primitives) never allocates a node
9
+ * object and never wraps HTML parts in `markHTMLString`, so static-heavy pages
10
+ * render with minimal overhead.
11
+ *
12
+ * Dynamic subtrees (components, render instances, promises, JSX) are rendered
13
+ * via `renderChild`. While their output stays synchronous, the engine streams
14
+ * it straight to `destination`. As soon as a dynamic node renders
15
+ * asynchronously, the engine switches to a buffered tail: every remaining
16
+ * dynamic node is started eagerly (so async work runs in parallel) and the
17
+ * buffers are flushed in order. This mirrors `RenderTemplateResult.render`, so
18
+ * async components render concurrently rather than serially.
19
+ *
20
+ * Head propagation is handled by the caller (`bufferHeadContent`) before this
21
+ * runs.
22
+ */
23
+ export declare function renderStreaming(root: unknown, result: SSRResult, destination: RenderDestination): Promise<void>;