diff --git a/examples/blog/src/pages/posts/index.md b/examples/blog/src/pages/posts/index.md index 40d329e3a..949dfb866 100644 --- a/examples/blog/src/pages/posts/index.md +++ b/examples/blog/src/pages/posts/index.md @@ -13,4 +13,4 @@ description: Just a Hello World Post! This is so cool! -Do variables work {frontmatter.value \* 2}? +Do variables work {frontmatter.value * 2}? diff --git a/packages/astro/src/core/ssr/index.ts b/packages/astro/src/core/ssr/index.ts index 3d92a2402..aabe57aa8 100644 --- a/packages/astro/src/core/ssr/index.ts +++ b/packages/astro/src/core/ssr/index.ts @@ -5,9 +5,10 @@ import type { AstroGlobal, TopLevelAstro, SSRResult, SSRElement } from '../../@t import type { LogOptions } from '../logger'; import { fileURLToPath } from 'url'; +import { Writable } from 'stream'; import fs from 'fs'; import path from 'path'; -import { renderPage, renderSlot } from '../../runtime/server/index.js'; +import { renderPageToStream, renderSlot } from '../../runtime/server/index.js'; import { canonicalURL as getCanonicalURL, codeFrame, resolveDependency } from '../util.js'; import { getStylesForID } from './css.js'; import { injectTags } from './html.js'; @@ -158,39 +159,45 @@ export async function ssr({ astroConfig, filePath, logging, mode, origin, pathna _metadata: { renderers }, }; - let html = await renderPage(result, Component, pageProps, null); + const writableStream = new Writable() + writableStream._write = (chunk, encoding, next) => { + console.log(chunk.toString()) + next() + } + + await renderPageToStream(writableStream, result, Component, pageProps, null); // inject tags - const tags: vite.HtmlTagDescriptor[] = []; + // const tags: vite.HtmlTagDescriptor[] = []; - // inject Astro HMR client (dev only) - if (mode === 'development') { - tags.push({ - tag: 'script', - attrs: { type: 'module' }, - children: `import 'astro/runtime/client/hmr.js';`, - injectTo: 'head', - }); - } + // // inject Astro HMR client (dev only) + // if (mode === 'development') { + // tags.push({ + // tag: 'script', + // attrs: { type: 'module' }, + // children: `import 'astro/runtime/client/hmr.js';`, + // injectTo: 'head', + // }); + // } - // inject CSS - [...getStylesForID(fileURLToPath(filePath), viteServer)].forEach((href) => { - tags.push({ - tag: 'link', - attrs: { type: 'text/css', rel: 'stylesheet', href }, - injectTo: 'head', - }); - }); + // // inject CSS + // [...getStylesForID(fileURLToPath(filePath), viteServer)].forEach((href) => { + // tags.push({ + // tag: 'link', + // attrs: { type: 'text/css', rel: 'stylesheet', href }, + // injectTo: 'head', + // }); + // }); - // add injected tags - html = injectTags(html, tags); + // // add injected tags + // html = injectTags(html, tags); - // run transformIndexHtml() in dev to run Vite dev transformations - if (mode === 'development') { - html = await viteServer.transformIndexHtml(fileURLToPath(filePath), html, pathname); - } + // // run transformIndexHtml() in dev to run Vite dev transformations + // if (mode === 'development') { + // html = await viteServer.transformIndexHtml(fileURLToPath(filePath), html, pathname); + // } - return html; + // return html; } catch (e: any) { viteServer.ssrFixStacktrace(e); // Astro error (thrown by esbuild so it needs to be formatted for Vite) diff --git a/packages/astro/src/runtime/server/index.ts b/packages/astro/src/runtime/server/index.ts index 6e56a336e..0fdb582ed 100644 --- a/packages/astro/src/runtime/server/index.ts +++ b/packages/astro/src/runtime/server/index.ts @@ -6,6 +6,7 @@ import { valueToEstree } from 'estree-util-value-to-estree'; import * as astring from 'astring'; import shorthash from 'shorthash'; export { createMetadata } from './metadata.js'; +export { renderPageToStream } from './stream.js'; const { generate, GENERATOR } = astring; diff --git a/packages/astro/src/runtime/server/stream.ts b/packages/astro/src/runtime/server/stream.ts new file mode 100644 index 000000000..af2ce1230 --- /dev/null +++ b/packages/astro/src/runtime/server/stream.ts @@ -0,0 +1,92 @@ +import type { AstroComponentMetadata, Renderer } from '../../@types/astro-core'; +import type { SSRResult, SSRElement } from '../../@types/astro-runtime'; +import type { Writable } from 'stream'; +import { defineScriptVars, defineStyleVars, spreadAttributes } from './index.js'; + +export interface AstroComponentFactory { + (result: any, props: any, slots: any): ReturnType; + isAstroComponentFactory?: boolean; +} + +// 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); +}; + +const sleep = (ms: number) => new Promise(res => setTimeout(res, ms)) + +export async function renderPageToStream(res: Writable, result: SSRResult, componentFactory: AstroComponentFactory, props: any, children: any) { + const component = await componentFactory(result, props, children); + + let index = 0; + let head = 0; + let rendereredScripts = new Set(); + let rendereredStyles = new Set(); + for await (const value of component) { + + for (const script of result.scripts) { + if (!rendereredScripts.has(JSON.stringify(script))) { + if (head === 1) { + const html = renderElement('script', script) + res.write(html) + rendereredScripts.add(JSON.stringify(script)); + result.scripts.delete(script); + } + } + } + + for (const style of result.styles) { + if (!rendereredStyles.has(JSON.stringify(style))) { + if (head === 1) { + const html = renderElement('style', style) + res.write(html) + rendereredStyles.add(JSON.stringify(style)); + result.styles.delete(style); + } + } + } + + if (value || value === 0) { + if (value.indexOf("") > -1 && head === 0) { + head = 1; + } + // res.write() + res.write(value); + + if (value.indexOf("") > -1 && head === 1) { + head = 2; + } + } + index++; + } + + // 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 res; +} + +function renderElement(name: string, { props: _props, children = '' }: SSRElement) { + // Do not print `hoist`, `lang`, `global` + const { lang: _, 'data-astro-id': astroId, 'define:vars': defineVars, ...props } = _props; + if (defineVars) { + if (name === 'style') { + 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; + } + } + return `<${name}${spreadAttributes(props)}>${children}`; +}