feat: scaffold Astro + Tailwind project
This commit is contained in:
14
node_modules/astro/dist/runtime/server/render/queue/builder.d.ts
generated
vendored
Normal file
14
node_modules/astro/dist/runtime/server/render/queue/builder.d.ts
generated
vendored
Normal file
@@ -0,0 +1,14 @@
|
||||
import type { SSRResult } from '../../../../types/public/internal.js';
|
||||
import type { RenderQueue } from './types.js';
|
||||
import type { NodePool } from './pool.js';
|
||||
/**
|
||||
* Builds a render queue from a component tree.
|
||||
* This function traverses the tree depth-first and creates a flat queue
|
||||
* of nodes to be rendered, with parent tracking.
|
||||
*
|
||||
* @param root - The root component/value to render
|
||||
* @param result - SSR result context
|
||||
* @param pool
|
||||
* @returns A render queue ready for rendering
|
||||
*/
|
||||
export declare function buildRenderQueue(root: any, result: SSRResult, pool: NodePool): Promise<RenderQueue>;
|
||||
182
node_modules/astro/dist/runtime/server/render/queue/builder.js
generated
vendored
Normal file
182
node_modules/astro/dist/runtime/server/render/queue/builder.js
generated
vendored
Normal file
@@ -0,0 +1,182 @@
|
||||
import { isPromise } from "../../util.js";
|
||||
import { isHTMLString, markHTMLString } from "../../escape.js";
|
||||
import { isAstroComponentFactory, isAPropagatingComponent } from "../astro/factory.js";
|
||||
import { createAstroComponentInstance, isAstroComponentInstance } from "../astro/instance.js";
|
||||
import { isRenderInstance } from "../common.js";
|
||||
import { isRenderInstruction } from "../instruction.js";
|
||||
import { SlotString } from "../slot.js";
|
||||
import { isRenderTemplateResult } from "../astro/render-template.js";
|
||||
import { isHeadAndContent } from "../astro/head-and-content.js";
|
||||
import { isVNode } from "../../../../jsx-runtime/index.js";
|
||||
import { renderJSXToQueue } from "./jsx-builder.js";
|
||||
async function buildRenderQueue(root, result, pool) {
|
||||
const queue = {
|
||||
nodes: [],
|
||||
result,
|
||||
pool,
|
||||
htmlStringCache: result._experimentalQueuedRendering?.htmlStringCache
|
||||
};
|
||||
const stack = [{ node: root, parent: null }];
|
||||
while (stack.length > 0) {
|
||||
const item = stack.pop();
|
||||
if (!item) {
|
||||
continue;
|
||||
}
|
||||
let { node, parent } = item;
|
||||
if (isPromise(node)) {
|
||||
try {
|
||||
const resolved = await node;
|
||||
stack.push({ node: resolved, parent, metadata: item.metadata });
|
||||
} catch (error) {
|
||||
throw error;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
if (node == null || node === false) {
|
||||
continue;
|
||||
}
|
||||
if (typeof node === "string") {
|
||||
const queueNode = pool.acquire("text", node);
|
||||
queueNode.content = node;
|
||||
queue.nodes.push(queueNode);
|
||||
continue;
|
||||
}
|
||||
if (typeof node === "number" || typeof node === "boolean") {
|
||||
const str = String(node);
|
||||
const queueNode = pool.acquire("text", str);
|
||||
queueNode.content = str;
|
||||
queue.nodes.push(queueNode);
|
||||
continue;
|
||||
}
|
||||
if (isHTMLString(node)) {
|
||||
const html = node.toString();
|
||||
const queueNode = pool.acquire("html-string", html);
|
||||
queueNode.html = html;
|
||||
queue.nodes.push(queueNode);
|
||||
continue;
|
||||
}
|
||||
if (node instanceof SlotString) {
|
||||
const html = node.toString();
|
||||
const queueNode = pool.acquire("html-string", html);
|
||||
queueNode.html = html;
|
||||
queue.nodes.push(queueNode);
|
||||
continue;
|
||||
}
|
||||
if (isVNode(node)) {
|
||||
renderJSXToQueue(node, result, queue, pool, stack, parent, item.metadata);
|
||||
continue;
|
||||
}
|
||||
if (Array.isArray(node)) {
|
||||
for (const n of node) {
|
||||
stack.push({ node: n, parent, metadata: item.metadata });
|
||||
}
|
||||
continue;
|
||||
}
|
||||
if (isRenderInstruction(node)) {
|
||||
const queueNode = pool.acquire("instruction");
|
||||
queueNode.instruction = node;
|
||||
queue.nodes.push(queueNode);
|
||||
continue;
|
||||
}
|
||||
if (isRenderTemplateResult(node)) {
|
||||
const htmlParts = node["htmlParts"];
|
||||
const expressions = node["expressions"];
|
||||
if (htmlParts[0]) {
|
||||
const htmlString = queue.htmlStringCache ? queue.htmlStringCache.getOrCreate(htmlParts[0]) : markHTMLString(htmlParts[0]);
|
||||
stack.push({
|
||||
node: htmlString,
|
||||
parent,
|
||||
metadata: item.metadata
|
||||
});
|
||||
}
|
||||
for (let i = 0; i < expressions.length; i = i + 1) {
|
||||
stack.push({ node: expressions[i], parent, metadata: item.metadata });
|
||||
if (htmlParts[i + 1]) {
|
||||
const htmlString = queue.htmlStringCache ? queue.htmlStringCache.getOrCreate(htmlParts[i + 1]) : markHTMLString(htmlParts[i + 1]);
|
||||
stack.push({
|
||||
node: htmlString,
|
||||
parent,
|
||||
metadata: item.metadata
|
||||
});
|
||||
}
|
||||
}
|
||||
continue;
|
||||
}
|
||||
if (isAstroComponentInstance(node)) {
|
||||
const queueNode = pool.acquire("component");
|
||||
queueNode.instance = node;
|
||||
queue.nodes.push(queueNode);
|
||||
continue;
|
||||
}
|
||||
if (isAstroComponentFactory(node)) {
|
||||
const factory = node;
|
||||
const props = item.metadata?.props || {};
|
||||
const slots = item.metadata?.slots || {};
|
||||
const displayName = item.metadata?.displayName || factory.name || "Anonymous";
|
||||
const instance = createAstroComponentInstance(result, displayName, factory, props, slots);
|
||||
const queueNode = pool.acquire("component");
|
||||
queueNode.instance = instance;
|
||||
if (isAPropagatingComponent(result, factory)) {
|
||||
try {
|
||||
const returnValue = await instance.init(result);
|
||||
if (isHeadAndContent(returnValue) && returnValue.head) {
|
||||
result._metadata.extraHead.push(returnValue.head);
|
||||
}
|
||||
} catch (error) {
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
queue.nodes.push(queueNode);
|
||||
continue;
|
||||
}
|
||||
if (isRenderInstance(node)) {
|
||||
const queueNode = pool.acquire("component");
|
||||
queueNode.instance = node;
|
||||
queue.nodes.push(queueNode);
|
||||
continue;
|
||||
}
|
||||
if (typeof node === "object" && Symbol.iterator in node) {
|
||||
const items = Array.from(node);
|
||||
for (const iterItem of items) {
|
||||
stack.push({ node: iterItem, parent, metadata: item.metadata });
|
||||
}
|
||||
continue;
|
||||
}
|
||||
if (typeof node === "object" && Symbol.asyncIterator in node) {
|
||||
try {
|
||||
const items = [];
|
||||
for await (const asyncItem of node) {
|
||||
items.push(asyncItem);
|
||||
}
|
||||
for (const iterItem of items) {
|
||||
stack.push({ node: iterItem, parent, metadata: item.metadata });
|
||||
}
|
||||
} catch (error) {
|
||||
throw error;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
if (node instanceof Response) {
|
||||
const queueNode = pool.acquire("html-string", "");
|
||||
queueNode.html = "";
|
||||
queue.nodes.push(queueNode);
|
||||
continue;
|
||||
}
|
||||
if (isHTMLString(node)) {
|
||||
const html = String(node);
|
||||
const queueNode = pool.acquire("html-string", html);
|
||||
queueNode.html = html;
|
||||
queue.nodes.push(queueNode);
|
||||
} else {
|
||||
const str = String(node);
|
||||
const queueNode = pool.acquire("text", str);
|
||||
queueNode.content = str;
|
||||
queue.nodes.push(queueNode);
|
||||
}
|
||||
}
|
||||
queue.nodes.reverse();
|
||||
return queue;
|
||||
}
|
||||
export {
|
||||
buildRenderQueue
|
||||
};
|
||||
33
node_modules/astro/dist/runtime/server/render/queue/jsx-builder.d.ts
generated
vendored
Normal file
33
node_modules/astro/dist/runtime/server/render/queue/jsx-builder.d.ts
generated
vendored
Normal file
@@ -0,0 +1,33 @@
|
||||
import type { SSRResult } from '../../../../types/public/internal.js';
|
||||
import type { RenderQueue, StackItem, QueueNode } from './types.js';
|
||||
import type { NodePool } from './pool.js';
|
||||
/**
|
||||
* Get JSX queue rendering statistics
|
||||
*/
|
||||
export declare function getJSXQueueStats(): {
|
||||
vnodeCount: number;
|
||||
elementCount: number;
|
||||
componentCount: number;
|
||||
hasLogged: boolean;
|
||||
};
|
||||
/**
|
||||
* Reset JSX queue rendering statistics
|
||||
*/
|
||||
export declare function resetJSXQueueStats(): void;
|
||||
/**
|
||||
* Processes JSX VNodes and adds them to the render queue.
|
||||
* Unlike renderJSX(), this doesn't build strings recursively -
|
||||
* it pushes nodes directly to the queue for batching and memory efficiency.
|
||||
*
|
||||
* This function handles JSX created by astro:jsx (JSX in .astro files).
|
||||
* It converts VNodes to queue nodes, enabling content-aware pooling and batching.
|
||||
*
|
||||
* @param vnode - JSX VNode to process
|
||||
* @param result - SSR result context
|
||||
* @param queue - Queue to append nodes to
|
||||
* @param pool - Node pool for memory efficiency
|
||||
* @param stack - Stack for depth-first traversal
|
||||
* @param parent - Parent queue node (for tracking)
|
||||
* @param metadata - Metadata passed through stack (props, slots, displayName)
|
||||
*/
|
||||
export declare function renderJSXToQueue(vnode: any, result: SSRResult, queue: RenderQueue, pool: NodePool, stack: StackItem[], parent: QueueNode | null, metadata?: StackItem['metadata']): void;
|
||||
146
node_modules/astro/dist/runtime/server/render/queue/jsx-builder.js
generated
vendored
Normal file
146
node_modules/astro/dist/runtime/server/render/queue/jsx-builder.js
generated
vendored
Normal file
@@ -0,0 +1,146 @@
|
||||
import { isVNode } from "../../../../jsx-runtime/index.js";
|
||||
import { HTMLString, markHTMLString, spreadAttributes, voidElementNames } from "../../index.js";
|
||||
import { isAstroComponentFactory } from "../astro/factory.js";
|
||||
import { createAstroComponentInstance } from "../astro/instance.js";
|
||||
import { renderJSX } from "../../jsx.js";
|
||||
const ClientOnlyPlaceholder = "astro-client-only";
|
||||
let jsxQueueStats = {
|
||||
vnodeCount: 0,
|
||||
elementCount: 0,
|
||||
componentCount: 0,
|
||||
hasLogged: false
|
||||
};
|
||||
function getJSXQueueStats() {
|
||||
return { ...jsxQueueStats };
|
||||
}
|
||||
function resetJSXQueueStats() {
|
||||
jsxQueueStats = {
|
||||
vnodeCount: 0,
|
||||
elementCount: 0,
|
||||
componentCount: 0,
|
||||
hasLogged: false
|
||||
};
|
||||
}
|
||||
function renderJSXToQueue(vnode, result, queue, pool, stack, parent, metadata) {
|
||||
jsxQueueStats.vnodeCount = jsxQueueStats.vnodeCount + 1;
|
||||
if (vnode instanceof HTMLString) {
|
||||
const html = vnode.toString();
|
||||
if (html.trim() === "") return;
|
||||
const node = pool.acquire("html-string", html);
|
||||
node.html = html;
|
||||
queue.nodes.push(node);
|
||||
return;
|
||||
}
|
||||
if (typeof vnode === "string") {
|
||||
const node = pool.acquire("text", vnode);
|
||||
node.content = vnode;
|
||||
queue.nodes.push(node);
|
||||
return;
|
||||
}
|
||||
if (typeof vnode === "number" || typeof vnode === "boolean") {
|
||||
const str = String(vnode);
|
||||
const node = pool.acquire("text", str);
|
||||
node.content = str;
|
||||
queue.nodes.push(node);
|
||||
return;
|
||||
}
|
||||
if (vnode == null || vnode === false) {
|
||||
return;
|
||||
}
|
||||
if (Array.isArray(vnode)) {
|
||||
for (let i = vnode.length - 1; i >= 0; i = i - 1) {
|
||||
stack.push({ node: vnode[i], parent, metadata });
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (!isVNode(vnode)) {
|
||||
const str = String(vnode);
|
||||
const node = pool.acquire("text", str);
|
||||
node.content = str;
|
||||
queue.nodes.push(node);
|
||||
return;
|
||||
}
|
||||
handleVNode(vnode, result, queue, pool, stack, parent, metadata);
|
||||
}
|
||||
function handleVNode(vnode, result, queue, pool, stack, parent, metadata) {
|
||||
if (!vnode.type) {
|
||||
throw new Error(
|
||||
`Unable to render ${result.pathname} because it contains an undefined Component!
|
||||
Did you forget to import the component or is it possible there is a typo?`
|
||||
);
|
||||
}
|
||||
if (vnode.type === /* @__PURE__ */ Symbol.for("astro:fragment")) {
|
||||
stack.push({ node: vnode.props?.children, parent, metadata });
|
||||
return;
|
||||
}
|
||||
if (isAstroComponentFactory(vnode.type)) {
|
||||
jsxQueueStats.componentCount = jsxQueueStats.componentCount + 1;
|
||||
const factory = vnode.type;
|
||||
let props = {};
|
||||
let slots = {};
|
||||
for (const [key, value] of Object.entries(vnode.props ?? {})) {
|
||||
if (key === "children" || value && typeof value === "object" && value["$$slot"]) {
|
||||
slots[key === "children" ? "default" : key] = () => renderJSX(result, value);
|
||||
} else {
|
||||
props[key] = value;
|
||||
}
|
||||
}
|
||||
const displayName = metadata?.displayName || factory.name || "Anonymous";
|
||||
const instance = createAstroComponentInstance(result, displayName, factory, props, slots);
|
||||
const queueNode = pool.acquire("component");
|
||||
queueNode.instance = instance;
|
||||
queue.nodes.push(queueNode);
|
||||
return;
|
||||
}
|
||||
if (typeof vnode.type === "string" && vnode.type !== ClientOnlyPlaceholder) {
|
||||
jsxQueueStats.elementCount = jsxQueueStats.elementCount + 1;
|
||||
renderHTMLElement(vnode, result, queue, pool, stack, parent, metadata);
|
||||
return;
|
||||
}
|
||||
if (typeof vnode.type === "function") {
|
||||
if (vnode.props?.["server:root"]) {
|
||||
const output3 = vnode.type(vnode.props ?? {});
|
||||
stack.push({ node: output3, parent, metadata });
|
||||
return;
|
||||
}
|
||||
const output2 = vnode.type(vnode.props ?? {});
|
||||
stack.push({ node: output2, parent, metadata });
|
||||
return;
|
||||
}
|
||||
const output = renderJSX(result, vnode);
|
||||
stack.push({ node: output, parent, metadata });
|
||||
}
|
||||
function renderHTMLElement(vnode, _result, queue, pool, stack, parent, metadata) {
|
||||
const tag = vnode.type;
|
||||
const { children, ...props } = vnode.props ?? {};
|
||||
const attrs = spreadAttributes(props);
|
||||
const isVoidElement = (children == null || children === "") && voidElementNames.test(tag);
|
||||
if (isVoidElement) {
|
||||
const html = `<${tag}${attrs}/>`;
|
||||
const node = pool.acquire("html-string", html);
|
||||
node.html = html;
|
||||
queue.nodes.push(node);
|
||||
return;
|
||||
}
|
||||
const openTag = `<${tag}${attrs}>`;
|
||||
const openTagHtml = queue.htmlStringCache ? queue.htmlStringCache.getOrCreate(openTag) : markHTMLString(openTag);
|
||||
stack.push({ node: openTagHtml, parent, metadata });
|
||||
if (children != null && children !== "") {
|
||||
const processedChildren = prerenderElementChildren(tag, children, queue.htmlStringCache);
|
||||
stack.push({ node: processedChildren, parent, metadata });
|
||||
}
|
||||
const closeTag = `</${tag}>`;
|
||||
const closeTagHtml = queue.htmlStringCache ? queue.htmlStringCache.getOrCreate(closeTag) : markHTMLString(closeTag);
|
||||
stack.push({ node: closeTagHtml, parent, metadata });
|
||||
}
|
||||
function prerenderElementChildren(tag, children, htmlStringCache) {
|
||||
if (typeof children === "string" && (tag === "style" || tag === "script")) {
|
||||
return htmlStringCache ? htmlStringCache.getOrCreate(children) : markHTMLString(children);
|
||||
}
|
||||
return children;
|
||||
}
|
||||
export {
|
||||
getJSXQueueStats,
|
||||
renderJSXToQueue,
|
||||
resetJSXQueueStats
|
||||
};
|
||||
123
node_modules/astro/dist/runtime/server/render/queue/pool.d.ts
generated
vendored
Normal file
123
node_modules/astro/dist/runtime/server/render/queue/pool.d.ts
generated
vendored
Normal file
@@ -0,0 +1,123 @@
|
||||
import type { QueueNode } from './types.js';
|
||||
import type { SSRManifest } from '../../../../core/app/types.js';
|
||||
/**
|
||||
* Raw statistics tracked by the node pool.
|
||||
*/
|
||||
export interface PoolStats {
|
||||
/** Number of times a node was successfully acquired from the pool */
|
||||
acquireFromPool: number;
|
||||
/** Number of times a new node had to be created (pool was empty) */
|
||||
acquireNew: number;
|
||||
/** Number of nodes successfully returned to the pool */
|
||||
released: number;
|
||||
/** Number of nodes that couldn't be returned (pool was full) */
|
||||
releasedDropped: number;
|
||||
}
|
||||
/**
|
||||
* Extended statistics report with computed metrics.
|
||||
* Returned by NodePool.getStats() for debugging and monitoring.
|
||||
*/
|
||||
export interface PoolStatsReport extends PoolStats {
|
||||
/** Current number of nodes available in the pool */
|
||||
poolSize: number;
|
||||
/** Maximum pool capacity */
|
||||
maxSize: number;
|
||||
/** Pool hit rate as a percentage (0-100) - higher is better */
|
||||
hitRate: number;
|
||||
}
|
||||
/**
|
||||
* Object pool for `QueueNode` instances to reduce allocations and GC pressure.
|
||||
*
|
||||
* Uses type-aware sub-pools so that released nodes are reused by the same
|
||||
* node type, preserving V8 hidden classes and avoiding shape transitions.
|
||||
* Nodes are acquired from the pool, used during queue building, and released
|
||||
* back to the pool for reuse across renders.
|
||||
*
|
||||
* String deduplication is handled separately by `HTMLStringCache`.
|
||||
*/
|
||||
export declare class NodePool {
|
||||
private textPool;
|
||||
private htmlStringPool;
|
||||
private componentPool;
|
||||
private instructionPool;
|
||||
readonly maxSize: number;
|
||||
private readonly enableStats;
|
||||
private stats;
|
||||
/**
|
||||
* Creates a new object pool for queue nodes.
|
||||
*
|
||||
* @param maxSize - Maximum number of nodes to keep in the pool (default: 1000).
|
||||
* The cap is shared across all typed sub-pools.
|
||||
* @param enableStats - Enable statistics tracking (default: false for performance)
|
||||
*/
|
||||
constructor(maxSize?: number, enableStats?: boolean);
|
||||
/**
|
||||
* Acquires a queue node from the pool or creates a new one if the pool is empty.
|
||||
* Pops from the type-specific sub-pool to reuse an existing object when available.
|
||||
*
|
||||
* @param type - The type of queue node to acquire
|
||||
* @param content - Optional content to set on the node (for text or html-string types)
|
||||
* @returns A queue node ready to be populated with data
|
||||
*/
|
||||
acquire(type: QueueNode['type'], content?: string): QueueNode;
|
||||
/**
|
||||
* Creates a new node of the specified type with the given content.
|
||||
* Helper method to reduce branching in acquire().
|
||||
*/
|
||||
private createNode;
|
||||
/**
|
||||
* Pops a node from the type-specific sub-pool.
|
||||
* Returns undefined if the sub-pool for the requested type is empty.
|
||||
*/
|
||||
private popFromTypedPool;
|
||||
/**
|
||||
* Resets the content/value field on a reused pooled node.
|
||||
* The type discriminant is already correct since we pop from the matching sub-pool.
|
||||
*/
|
||||
private resetNodeContent;
|
||||
/**
|
||||
* Returns the total number of nodes across all typed sub-pools.
|
||||
*/
|
||||
private totalPoolSize;
|
||||
/**
|
||||
* Releases a queue node back to the pool for reuse.
|
||||
* If the pool is at max capacity, the node is discarded (will be GC'd).
|
||||
*
|
||||
* @param node - The node to release back to the pool
|
||||
*/
|
||||
release(node: QueueNode): void;
|
||||
/**
|
||||
* Releases all nodes in an array back to the pool.
|
||||
* This is a convenience method for releasing multiple nodes at once.
|
||||
*
|
||||
* @param nodes - Array of nodes to release
|
||||
*/
|
||||
releaseAll(nodes: QueueNode[]): void;
|
||||
/**
|
||||
* Clears all typed sub-pools, discarding all cached nodes.
|
||||
* This can be useful if you want to free memory after a large render.
|
||||
*/
|
||||
clear(): void;
|
||||
/**
|
||||
* Gets the current total number of nodes across all typed sub-pools.
|
||||
* Useful for monitoring pool usage and tuning maxSize.
|
||||
*
|
||||
* @returns Number of nodes currently available in the pool
|
||||
*/
|
||||
size(): number;
|
||||
/**
|
||||
* Gets pool statistics for debugging.
|
||||
*
|
||||
* @returns Pool usage statistics including computed metrics
|
||||
*/
|
||||
getStats(): PoolStatsReport;
|
||||
/**
|
||||
* Resets pool statistics.
|
||||
*/
|
||||
resetStats(): void;
|
||||
}
|
||||
/**
|
||||
* Returns an instance of the `NodePool` based on its configuration.
|
||||
* @param config - The queued rendering configuration from the SSR manifest
|
||||
*/
|
||||
export declare function newNodePool(config: NonNullable<SSRManifest['experimentalQueuedRendering']>): NodePool;
|
||||
203
node_modules/astro/dist/runtime/server/render/queue/pool.js
generated
vendored
Normal file
203
node_modules/astro/dist/runtime/server/render/queue/pool.js
generated
vendored
Normal file
@@ -0,0 +1,203 @@
|
||||
import { queuePoolSize } from "../../../../core/app/manifest.js";
|
||||
class NodePool {
|
||||
textPool = [];
|
||||
htmlStringPool = [];
|
||||
componentPool = [];
|
||||
instructionPool = [];
|
||||
maxSize;
|
||||
enableStats;
|
||||
stats = {
|
||||
acquireFromPool: 0,
|
||||
acquireNew: 0,
|
||||
released: 0,
|
||||
releasedDropped: 0
|
||||
};
|
||||
/**
|
||||
* Creates a new object pool for queue nodes.
|
||||
*
|
||||
* @param maxSize - Maximum number of nodes to keep in the pool (default: 1000).
|
||||
* The cap is shared across all typed sub-pools.
|
||||
* @param enableStats - Enable statistics tracking (default: false for performance)
|
||||
*/
|
||||
constructor(maxSize = 1e3, enableStats = false) {
|
||||
this.maxSize = maxSize;
|
||||
this.enableStats = enableStats;
|
||||
}
|
||||
/**
|
||||
* Acquires a queue node from the pool or creates a new one if the pool is empty.
|
||||
* Pops from the type-specific sub-pool to reuse an existing object when available.
|
||||
*
|
||||
* @param type - The type of queue node to acquire
|
||||
* @param content - Optional content to set on the node (for text or html-string types)
|
||||
* @returns A queue node ready to be populated with data
|
||||
*/
|
||||
acquire(type, content) {
|
||||
const pooledNode = this.popFromTypedPool(type);
|
||||
if (pooledNode) {
|
||||
if (this.enableStats) {
|
||||
this.stats.acquireFromPool = this.stats.acquireFromPool + 1;
|
||||
}
|
||||
this.resetNodeContent(pooledNode, type, content);
|
||||
return pooledNode;
|
||||
}
|
||||
if (this.enableStats) {
|
||||
this.stats.acquireNew = this.stats.acquireNew + 1;
|
||||
}
|
||||
return this.createNode(type, content);
|
||||
}
|
||||
/**
|
||||
* Creates a new node of the specified type with the given content.
|
||||
* Helper method to reduce branching in acquire().
|
||||
*/
|
||||
createNode(type, content = "") {
|
||||
switch (type) {
|
||||
case "text":
|
||||
return { type: "text", content };
|
||||
case "html-string":
|
||||
return { type: "html-string", html: content };
|
||||
case "component":
|
||||
return { type: "component", instance: void 0 };
|
||||
case "instruction":
|
||||
return { type: "instruction", instruction: void 0 };
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Pops a node from the type-specific sub-pool.
|
||||
* Returns undefined if the sub-pool for the requested type is empty.
|
||||
*/
|
||||
popFromTypedPool(type) {
|
||||
switch (type) {
|
||||
case "text":
|
||||
return this.textPool.pop();
|
||||
case "html-string":
|
||||
return this.htmlStringPool.pop();
|
||||
case "component":
|
||||
return this.componentPool.pop();
|
||||
case "instruction":
|
||||
return this.instructionPool.pop();
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Resets the content/value field on a reused pooled node.
|
||||
* The type discriminant is already correct since we pop from the matching sub-pool.
|
||||
*/
|
||||
resetNodeContent(node, type, content) {
|
||||
switch (type) {
|
||||
case "text":
|
||||
node.content = content ?? "";
|
||||
break;
|
||||
case "html-string":
|
||||
node.html = content ?? "";
|
||||
break;
|
||||
case "component":
|
||||
node.instance = void 0;
|
||||
break;
|
||||
case "instruction":
|
||||
node.instruction = void 0;
|
||||
break;
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Returns the total number of nodes across all typed sub-pools.
|
||||
*/
|
||||
totalPoolSize() {
|
||||
return this.textPool.length + this.htmlStringPool.length + this.componentPool.length + this.instructionPool.length;
|
||||
}
|
||||
/**
|
||||
* Releases a queue node back to the pool for reuse.
|
||||
* If the pool is at max capacity, the node is discarded (will be GC'd).
|
||||
*
|
||||
* @param node - The node to release back to the pool
|
||||
*/
|
||||
release(node) {
|
||||
if (this.totalPoolSize() >= this.maxSize) {
|
||||
if (this.enableStats) {
|
||||
this.stats.releasedDropped = this.stats.releasedDropped + 1;
|
||||
}
|
||||
return;
|
||||
}
|
||||
switch (node.type) {
|
||||
case "text":
|
||||
node.content = "";
|
||||
this.textPool.push(node);
|
||||
break;
|
||||
case "html-string":
|
||||
node.html = "";
|
||||
this.htmlStringPool.push(node);
|
||||
break;
|
||||
case "component":
|
||||
node.instance = void 0;
|
||||
this.componentPool.push(node);
|
||||
break;
|
||||
case "instruction":
|
||||
node.instruction = void 0;
|
||||
this.instructionPool.push(node);
|
||||
break;
|
||||
}
|
||||
if (this.enableStats) {
|
||||
this.stats.released = this.stats.released + 1;
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Releases all nodes in an array back to the pool.
|
||||
* This is a convenience method for releasing multiple nodes at once.
|
||||
*
|
||||
* @param nodes - Array of nodes to release
|
||||
*/
|
||||
releaseAll(nodes) {
|
||||
for (const node of nodes) {
|
||||
this.release(node);
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Clears all typed sub-pools, discarding all cached nodes.
|
||||
* This can be useful if you want to free memory after a large render.
|
||||
*/
|
||||
clear() {
|
||||
this.textPool.length = 0;
|
||||
this.htmlStringPool.length = 0;
|
||||
this.componentPool.length = 0;
|
||||
this.instructionPool.length = 0;
|
||||
}
|
||||
/**
|
||||
* Gets the current total number of nodes across all typed sub-pools.
|
||||
* Useful for monitoring pool usage and tuning maxSize.
|
||||
*
|
||||
* @returns Number of nodes currently available in the pool
|
||||
*/
|
||||
size() {
|
||||
return this.totalPoolSize();
|
||||
}
|
||||
/**
|
||||
* Gets pool statistics for debugging.
|
||||
*
|
||||
* @returns Pool usage statistics including computed metrics
|
||||
*/
|
||||
getStats() {
|
||||
return {
|
||||
...this.stats,
|
||||
poolSize: this.totalPoolSize(),
|
||||
maxSize: this.maxSize,
|
||||
hitRate: this.stats.acquireFromPool + this.stats.acquireNew > 0 ? this.stats.acquireFromPool / (this.stats.acquireFromPool + this.stats.acquireNew) * 100 : 0
|
||||
};
|
||||
}
|
||||
/**
|
||||
* Resets pool statistics.
|
||||
*/
|
||||
resetStats() {
|
||||
this.stats = {
|
||||
acquireFromPool: 0,
|
||||
acquireNew: 0,
|
||||
released: 0,
|
||||
releasedDropped: 0
|
||||
};
|
||||
}
|
||||
}
|
||||
function newNodePool(config) {
|
||||
const poolSize = queuePoolSize(config);
|
||||
return new NodePool(poolSize);
|
||||
}
|
||||
export {
|
||||
NodePool,
|
||||
newNodePool
|
||||
};
|
||||
12
node_modules/astro/dist/runtime/server/render/queue/renderer.d.ts
generated
vendored
Normal file
12
node_modules/astro/dist/runtime/server/render/queue/renderer.d.ts
generated
vendored
Normal file
@@ -0,0 +1,12 @@
|
||||
import { type RenderDestination } from '../common.js';
|
||||
import type { RenderQueue } from './types.js';
|
||||
/**
|
||||
* Renders a queue of nodes to a destination.
|
||||
* This function processes nodes sequentially with batching optimization.
|
||||
* Consecutive batchable nodes (text, HTML-string, simple elements) are
|
||||
* combined into a single write to reduce overhead.
|
||||
*
|
||||
* @param queue - The render queue to process
|
||||
* @param destination - Where to write the output
|
||||
*/
|
||||
export declare function renderQueue(queue: RenderQueue, destination: RenderDestination): Promise<void>;
|
||||
103
node_modules/astro/dist/runtime/server/render/queue/renderer.js
generated
vendored
Normal file
103
node_modules/astro/dist/runtime/server/render/queue/renderer.js
generated
vendored
Normal file
@@ -0,0 +1,103 @@
|
||||
import { markHTMLString, escapeHTML } from "../../escape.js";
|
||||
import { chunkToString } from "../common.js";
|
||||
async function renderQueue(queue, destination) {
|
||||
const result = queue.result;
|
||||
const pool = queue.pool;
|
||||
const cache = queue.htmlStringCache;
|
||||
let batchBuffer = "";
|
||||
let i = 0;
|
||||
while (i < queue.nodes.length) {
|
||||
const node = queue.nodes[i];
|
||||
try {
|
||||
if (canBatch(node)) {
|
||||
const batchStart = i;
|
||||
while (i < queue.nodes.length && canBatch(queue.nodes[i])) {
|
||||
batchBuffer += renderNodeToString(queue.nodes[i]);
|
||||
i = i + 1;
|
||||
}
|
||||
if (batchBuffer) {
|
||||
const htmlString = cache ? cache.getOrCreate(batchBuffer) : markHTMLString(batchBuffer);
|
||||
destination.write(htmlString);
|
||||
batchBuffer = "";
|
||||
}
|
||||
if (pool) {
|
||||
for (let j = batchStart; j < i; j++) {
|
||||
pool.release(queue.nodes[j]);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
await renderNode(node, destination, result);
|
||||
if (pool) {
|
||||
pool.release(node);
|
||||
}
|
||||
i = i + 1;
|
||||
}
|
||||
} catch (error) {
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
if (batchBuffer) {
|
||||
const htmlString = cache ? cache.getOrCreate(batchBuffer) : markHTMLString(batchBuffer);
|
||||
destination.write(htmlString);
|
||||
}
|
||||
}
|
||||
function canBatch(node) {
|
||||
return node.type === "text" || node.type === "html-string";
|
||||
}
|
||||
function renderNodeToString(node) {
|
||||
switch (node.type) {
|
||||
case "text":
|
||||
return node.content ? escapeHTML(node.content) : "";
|
||||
case "html-string":
|
||||
return node.html || "";
|
||||
case "component":
|
||||
case "instruction": {
|
||||
return "";
|
||||
}
|
||||
}
|
||||
}
|
||||
async function renderNode(node, destination, result) {
|
||||
const cache = result._experimentalQueuedRendering?.htmlStringCache;
|
||||
switch (node.type) {
|
||||
case "text": {
|
||||
if (node.content) {
|
||||
const escaped = escapeHTML(node.content);
|
||||
const htmlString = cache ? cache.getOrCreate(escaped) : markHTMLString(escaped);
|
||||
destination.write(htmlString);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case "html-string": {
|
||||
if (node.html) {
|
||||
const htmlString = cache ? cache.getOrCreate(node.html) : markHTMLString(node.html);
|
||||
destination.write(htmlString);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case "instruction": {
|
||||
if (node.instruction) {
|
||||
destination.write(node.instruction);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case "component": {
|
||||
if (node.instance) {
|
||||
let componentHtml = "";
|
||||
const componentDestination = {
|
||||
write(chunk) {
|
||||
if (chunk instanceof Response) return;
|
||||
componentHtml += chunkToString(result, chunk);
|
||||
}
|
||||
};
|
||||
await node.instance.render(componentDestination);
|
||||
if (componentHtml) {
|
||||
destination.write(componentHtml);
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
export {
|
||||
renderQueue
|
||||
};
|
||||
81
node_modules/astro/dist/runtime/server/render/queue/types.d.ts
generated
vendored
Normal file
81
node_modules/astro/dist/runtime/server/render/queue/types.d.ts
generated
vendored
Normal file
@@ -0,0 +1,81 @@
|
||||
import type { SSRResult } from '../../../../types/public/internal.js';
|
||||
import type { AstroComponentInstance } from '../astro/instance.js';
|
||||
import type { RenderInstruction } from '../instruction.js';
|
||||
import type { ServerIslandComponent } from '../server-islands.js';
|
||||
import type { NodePool } from './pool.js';
|
||||
import type { HTMLStringCache } from '../../html-string-cache.js';
|
||||
/**
|
||||
* Text node containing plain text content that will be HTML-escaped during rendering
|
||||
*/
|
||||
export interface TextNode {
|
||||
type: 'text';
|
||||
content: string;
|
||||
}
|
||||
/**
|
||||
* HTML string node containing pre-rendered HTML markup that is already safe
|
||||
*/
|
||||
export interface HtmlStringNode {
|
||||
type: 'html-string';
|
||||
html: string;
|
||||
}
|
||||
/**
|
||||
* Component node containing an Astro component instance to be rendered
|
||||
*/
|
||||
export interface ComponentNode {
|
||||
type: 'component';
|
||||
instance: AstroComponentInstance | ServerIslandComponent;
|
||||
}
|
||||
/**
|
||||
* Instruction node containing rendering instructions (head content, hydration scripts, etc.)
|
||||
*/
|
||||
export interface InstructionNode {
|
||||
type: 'instruction';
|
||||
instruction: RenderInstruction;
|
||||
}
|
||||
/**
|
||||
* Discriminated union of all queue node types.
|
||||
* TypeScript will narrow the type based on the 'type' field.
|
||||
*/
|
||||
export type QueueNode = TextNode | HtmlStringNode | ComponentNode | InstructionNode;
|
||||
/**
|
||||
* The render queue containing all nodes to be rendered
|
||||
*/
|
||||
export interface RenderQueue {
|
||||
/**
|
||||
* All nodes in rendering order (after reversing the built queue)
|
||||
*/
|
||||
nodes: QueueNode[];
|
||||
/**
|
||||
* SSRResult context
|
||||
*/
|
||||
result: SSRResult;
|
||||
/**
|
||||
* Object pool instance used for node acquisition
|
||||
*/
|
||||
pool?: NodePool;
|
||||
/**
|
||||
* HTMLString cache instance for reducing memory allocations
|
||||
*/
|
||||
htmlStringCache?: HTMLStringCache;
|
||||
}
|
||||
/**
|
||||
* Stack item used during queue building (internal use only)
|
||||
*/
|
||||
export interface StackItem {
|
||||
/**
|
||||
* The value to process
|
||||
*/
|
||||
node: any;
|
||||
/**
|
||||
* Parent queue node (tracked but not used during rendering)
|
||||
*/
|
||||
parent: QueueNode | null;
|
||||
/**
|
||||
* Additional metadata passed through the stack (component props, slots, displayName)
|
||||
*/
|
||||
metadata?: {
|
||||
displayName?: string;
|
||||
props?: Record<string, any>;
|
||||
slots?: any;
|
||||
};
|
||||
}
|
||||
0
node_modules/astro/dist/runtime/server/render/queue/types.js
generated
vendored
Normal file
0
node_modules/astro/dist/runtime/server/render/queue/types.js
generated
vendored
Normal file
Reference in New Issue
Block a user