diff --git a/packages/astro/src/@types/astro-runtime.ts b/packages/astro/src/@types/astro-runtime.ts index 7fe7dc568..f600fa4e0 100644 --- a/packages/astro/src/@types/astro-runtime.ts +++ b/packages/astro/src/@types/astro-runtime.ts @@ -52,8 +52,13 @@ export interface SSRMetadata { } export interface SSRResult { - styles: Set; - scripts: Set; + styles: Set; + scripts: Set; createAstro(Astro: TopLevelAstro, props: Record, slots: Record | null): AstroGlobal; _metadata: SSRMetadata; } + +export interface SSRElement { + props: Record; + children: string; +} diff --git a/packages/astro/src/core/ssr/index.ts b/packages/astro/src/core/ssr/index.ts index 4407db834..974c55275 100644 --- a/packages/astro/src/core/ssr/index.ts +++ b/packages/astro/src/core/ssr/index.ts @@ -1,7 +1,7 @@ import type { BuildResult } from 'esbuild'; import type { ViteDevServer } from '../vite'; import type { AstroConfig, ComponentInstance, GetStaticPathsResult, Params, Props, Renderer, RouteCache, RouteData, RuntimeMode, SSRError } from '../../@types/astro-core'; -import type { AstroGlobal, TopLevelAstro, SSRResult } from '../../@types/astro-runtime'; +import type { AstroGlobal, TopLevelAstro, SSRResult, SSRElement } from '../../@types/astro-runtime'; import type { LogOptions } from '../logger'; import { fileURLToPath } from 'url'; @@ -118,8 +118,8 @@ export async function ssr({ astroConfig, filePath, logging, mode, origin, pathna // This object starts here as an empty shell (not yet the result) but then // calling the render() function will populate the object with scripts, styles, etc. const result: SSRResult = { - styles: new Set(), - scripts: new Set(), + styles: new Set(), + scripts: new Set(), /** This function returns the `Astro` faux-global */ createAstro(astroGlobal: TopLevelAstro, props: Record, slots: Record | null) { const site = new URL(origin); @@ -193,3 +193,4 @@ ${frame} throw e; } } + diff --git a/packages/astro/src/runtime/server/index.ts b/packages/astro/src/runtime/server/index.ts index 827676452..7a41ef7ab 100644 --- a/packages/astro/src/runtime/server/index.ts +++ b/packages/astro/src/runtime/server/index.ts @@ -1,5 +1,5 @@ import type { AstroComponentMetadata, Renderer } from '../../@types/astro-core'; -import type { SSRResult } from '../../@types/astro-runtime'; +import type { SSRResult, SSRElement } from '../../@types/astro-runtime'; import type { TopLevelAstro } from '../../@types/astro-runtime'; import { valueToEstree } from 'estree-util-value-to-estree'; @@ -147,7 +147,7 @@ interface HydrateScriptOptions { } /** For hydrated components, generate a -`; +`, + }; return hydrationScript; } @@ -354,16 +355,23 @@ export function defineScriptVars(vars: Record) { return output; } -export async function renderToString(result: any, componentFactory: AstroComponentFactory, props: any, children: any) { +export async function renderToString(result: SSRResult, componentFactory: AstroComponentFactory, props: any, children: any) { const Component = await componentFactory(result, props, children); let template = await renderAstroComponent(Component); return template; } -export async function renderPage(result: any, Component: AstroComponentFactory, props: any, children: any) { +// Filter out duplicate elements in our set +const uniqueElements = (item: any, index: number, all: any[]) => { + const props = JSON.stringify(item.props); + const children = item.children; + return index === all.findIndex(i => JSON.stringify(i.props) === props && i.children == children) +} + +export async function renderPage(result: SSRResult, Component: AstroComponentFactory, props: any, children: any) { const template = await renderToString(result, Component, props, children); - const styles = Array.from(result.styles).map((style: any) => renderElement('style', style)); - const scripts = Array.from(result.scripts); + const styles = Array.from(result.styles).filter(uniqueElements).map((style) => renderElement('style', style)); + const scripts = Array.from(result.scripts).filter(uniqueElements).map((script) => renderElement('script', script)); return template.replace('', styles.join('\n') + scripts.join('\n') + ''); } @@ -379,18 +387,20 @@ export async function renderAstroComponent(component: InstanceType; children?: string }) { +function renderElement(name: string, { props: _props, children = '' }: SSRElement) { // Do not print `hoist`, `lang`, `global` - const { hoist: _0, lang: _1, global = false, 'data-astro-id': astroId, 'define:vars': defineVars, ...props } = _props; + const { lang: _, 'data-astro-id': astroId, 'define:vars': defineVars, ...props } = _props; if (defineVars) { if (name === 'style') { - if (global) { + if (props.global) { children = defineStyleVars(`:root`, defineVars) + '\n' + children; } else { children = defineStyleVars(`.astro-${astroId}`, defineVars) + '\n' + children; } + delete props.global; } if (name === 'script') { + delete props.hoist; children = defineScriptVars(defineVars) + '\n' + children; } }