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.
- package/dist/assets/build/generate.js +4 -3
- package/dist/assets/fonts/core/collect-font-data.js +1 -0
- package/dist/assets/fonts/types.d.ts +1 -0
- package/dist/cli/add/index.js +1 -44
- package/dist/cli/dev/background.js +1 -1
- package/dist/cli/dev/index.js +1 -1
- package/dist/cli/flags.js +4 -6
- package/dist/cli/help/index.js +1 -2
- package/dist/cli/index.js +1 -15
- package/dist/cli/infra/build-time-astro-version-provider.js +1 -1
- package/dist/container/index.d.ts +3 -3
- package/dist/container/index.js +1 -4
- package/dist/content/content-layer.js +3 -3
- package/dist/content/runtime.d.ts +1 -1
- package/dist/content/runtime.js +1 -0
- package/dist/content/vite-plugin-content-virtual-mod.js +27 -0
- package/dist/core/app/base.d.ts +1 -1
- package/dist/core/app/base.js +14 -24
- package/dist/core/app/dev/pipeline.js +0 -9
- package/dist/core/app/manifest.d.ts +0 -2
- package/dist/core/app/manifest.js +0 -8
- package/dist/core/app/types.d.ts +1 -8
- package/dist/core/base-pipeline.d.ts +3 -9
- package/dist/core/base-pipeline.js +4 -23
- package/dist/core/build/app.d.ts +0 -2
- package/dist/core/build/app.js +0 -5
- package/dist/core/build/generate.js +0 -14
- package/dist/core/build/pipeline.js +0 -9
- package/dist/core/build/plugins/plugin-css.js +1 -0
- package/dist/core/build/plugins/plugin-manifest.js +4 -9
- package/dist/core/config/config.js +3 -2
- package/dist/core/config/schemas/base.d.ts +9 -22
- package/dist/core/config/schemas/base.js +11 -27
- package/dist/core/config/schemas/relative.d.ts +15 -36
- package/dist/core/config/validate.js +10 -2
- package/dist/core/constants.js +1 -1
- package/dist/core/dev/dev.js +1 -1
- package/dist/core/errors/default-handler.js +21 -8
- package/dist/core/fetch/fetch-state.js +3 -16
- package/dist/core/fetch/types.d.ts +1 -1
- package/dist/core/fetch/vite-plugin.js +4 -6
- package/dist/core/hono/index.d.ts +1 -0
- package/dist/core/hono/index.js +1 -0
- package/dist/core/logger/impls/node.js +0 -1
- package/dist/core/logger/load.js +3 -2
- package/dist/core/messages/runtime.js +1 -1
- package/dist/core/middleware/index.js +8 -1
- package/dist/core/middleware/vite-plugin.d.ts +1 -0
- package/dist/core/middleware/vite-plugin.js +5 -1
- package/dist/core/util/normalized-url.js +2 -5
- package/dist/core/util/pathname.d.ts +13 -7
- package/dist/core/util/pathname.js +9 -6
- package/dist/i18n/index.js +6 -2
- package/dist/manifest/serialized.js +4 -5
- package/dist/runtime/server/index.d.ts +1 -1
- package/dist/runtime/server/index.js +4 -0
- package/dist/runtime/server/jsx.js +2 -1
- package/dist/runtime/server/render/astro/render-template.d.ts +1 -1
- package/dist/runtime/server/render/astro/render.d.ts +0 -4
- package/dist/runtime/server/render/astro/render.js +76 -68
- package/dist/runtime/server/render/head.js +2 -1
- package/dist/runtime/server/render/index.d.ts +1 -0
- package/dist/runtime/server/render/index.js +2 -0
- package/dist/runtime/server/render/page.js +9 -44
- package/dist/runtime/server/render/streaming.d.ts +23 -0
- package/dist/runtime/server/render/streaming.js +238 -0
- package/dist/runtime/server/render/util.js +5 -1
- package/dist/types/public/config.d.ts +115 -123
- package/dist/types/public/context.d.ts +1 -1
- package/dist/types/public/internal.d.ts +0 -15
- package/dist/vite-plugin-app/app.js +1 -1
- package/dist/vite-plugin-app/pipeline.js +0 -9
- package/dist/vite-plugin-hmr-reload/index.js +19 -6
- package/dist/vite-plugin-html/transform/slots.js +4 -1
- package/dist/vite-plugin-pages/pages.d.ts +11 -0
- package/dist/vite-plugin-pages/pages.js +1 -3
- package/package.json +13 -7
- package/dist/cli/db/index.d.ts +0 -4
- package/dist/cli/db/index.js +0 -25
- package/dist/jsx/rehype.d.ts +0 -5
- package/dist/jsx/rehype.js +0 -241
- package/dist/runtime/server/html-string-cache.d.ts +0 -48
- package/dist/runtime/server/html-string-cache.js +0 -119
- package/dist/runtime/server/render/queue/builder.d.ts +0 -14
- package/dist/runtime/server/render/queue/builder.js +0 -182
- package/dist/runtime/server/render/queue/jsx-builder.d.ts +0 -33
- package/dist/runtime/server/render/queue/jsx-builder.js +0 -146
- package/dist/runtime/server/render/queue/pool.d.ts +0 -123
- package/dist/runtime/server/render/queue/pool.js +0 -203
- package/dist/runtime/server/render/queue/renderer.d.ts +0 -12
- package/dist/runtime/server/render/queue/renderer.js +0 -103
- package/dist/runtime/server/render/queue/types.d.ts +0 -81
- 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
|
|
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 {
|
|
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
|
|
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
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
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
|
|
15
|
-
* @returns The decoded pathname
|
|
16
|
-
* @throws
|
|
17
|
-
*
|
|
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
|
-
|
|
19
|
-
|
|
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
|
}
|
package/dist/i18n/index.js
CHANGED
|
@@ -1,4 +1,8 @@
|
|
|
1
|
-
import {
|
|
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
|
|
146
|
-
if (settings.config.
|
|
147
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
34
|
+
await renderStreaming(templateResult, result, destination);
|
|
42
35
|
return str;
|
|
43
36
|
}
|
|
44
|
-
async function
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
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>;
|