WIP streaming rendering demo

This commit is contained in:
Nate Moore 2021-10-29 22:25:37 -05:00
parent e0d9a7627d
commit b96c4045aa
4 changed files with 128 additions and 28 deletions

View file

@ -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}?

View file

@ -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)

View file

@ -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;

View file

@ -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<any>;
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("<head>") > -1 && head === 0) {
head = 1;
}
// res.write()
res.write(value);
if (value.indexOf("</head>") > -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}</${name}>`;
}