WIP streaming rendering demo
This commit is contained in:
parent
e0d9a7627d
commit
b96c4045aa
4 changed files with 128 additions and 28 deletions
|
@ -13,4 +13,4 @@ description: Just a Hello World Post!
|
||||||
|
|
||||||
This is so cool!
|
This is so cool!
|
||||||
|
|
||||||
Do variables work {frontmatter.value \* 2}?
|
Do variables work {frontmatter.value * 2}?
|
||||||
|
|
|
@ -5,9 +5,10 @@ import type { AstroGlobal, TopLevelAstro, SSRResult, SSRElement } from '../../@t
|
||||||
import type { LogOptions } from '../logger';
|
import type { LogOptions } from '../logger';
|
||||||
|
|
||||||
import { fileURLToPath } from 'url';
|
import { fileURLToPath } from 'url';
|
||||||
|
import { Writable } from 'stream';
|
||||||
import fs from 'fs';
|
import fs from 'fs';
|
||||||
import path from 'path';
|
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 { canonicalURL as getCanonicalURL, codeFrame, resolveDependency } from '../util.js';
|
||||||
import { getStylesForID } from './css.js';
|
import { getStylesForID } from './css.js';
|
||||||
import { injectTags } from './html.js';
|
import { injectTags } from './html.js';
|
||||||
|
@ -158,39 +159,45 @@ export async function ssr({ astroConfig, filePath, logging, mode, origin, pathna
|
||||||
_metadata: { renderers },
|
_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
|
// inject tags
|
||||||
const tags: vite.HtmlTagDescriptor[] = [];
|
// const tags: vite.HtmlTagDescriptor[] = [];
|
||||||
|
|
||||||
// inject Astro HMR client (dev only)
|
// // inject Astro HMR client (dev only)
|
||||||
if (mode === 'development') {
|
// if (mode === 'development') {
|
||||||
tags.push({
|
// tags.push({
|
||||||
tag: 'script',
|
// tag: 'script',
|
||||||
attrs: { type: 'module' },
|
// attrs: { type: 'module' },
|
||||||
children: `import 'astro/runtime/client/hmr.js';`,
|
// children: `import 'astro/runtime/client/hmr.js';`,
|
||||||
injectTo: 'head',
|
// injectTo: 'head',
|
||||||
});
|
// });
|
||||||
}
|
// }
|
||||||
|
|
||||||
// inject CSS
|
// // inject CSS
|
||||||
[...getStylesForID(fileURLToPath(filePath), viteServer)].forEach((href) => {
|
// [...getStylesForID(fileURLToPath(filePath), viteServer)].forEach((href) => {
|
||||||
tags.push({
|
// tags.push({
|
||||||
tag: 'link',
|
// tag: 'link',
|
||||||
attrs: { type: 'text/css', rel: 'stylesheet', href },
|
// attrs: { type: 'text/css', rel: 'stylesheet', href },
|
||||||
injectTo: 'head',
|
// injectTo: 'head',
|
||||||
});
|
// });
|
||||||
});
|
// });
|
||||||
|
|
||||||
// add injected tags
|
// // add injected tags
|
||||||
html = injectTags(html, tags);
|
// html = injectTags(html, tags);
|
||||||
|
|
||||||
// run transformIndexHtml() in dev to run Vite dev transformations
|
// // run transformIndexHtml() in dev to run Vite dev transformations
|
||||||
if (mode === 'development') {
|
// if (mode === 'development') {
|
||||||
html = await viteServer.transformIndexHtml(fileURLToPath(filePath), html, pathname);
|
// html = await viteServer.transformIndexHtml(fileURLToPath(filePath), html, pathname);
|
||||||
}
|
// }
|
||||||
|
|
||||||
return html;
|
// return html;
|
||||||
} catch (e: any) {
|
} catch (e: any) {
|
||||||
viteServer.ssrFixStacktrace(e);
|
viteServer.ssrFixStacktrace(e);
|
||||||
// Astro error (thrown by esbuild so it needs to be formatted for Vite)
|
// Astro error (thrown by esbuild so it needs to be formatted for Vite)
|
||||||
|
|
|
@ -6,6 +6,7 @@ import { valueToEstree } from 'estree-util-value-to-estree';
|
||||||
import * as astring from 'astring';
|
import * as astring from 'astring';
|
||||||
import shorthash from 'shorthash';
|
import shorthash from 'shorthash';
|
||||||
export { createMetadata } from './metadata.js';
|
export { createMetadata } from './metadata.js';
|
||||||
|
export { renderPageToStream } from './stream.js';
|
||||||
|
|
||||||
const { generate, GENERATOR } = astring;
|
const { generate, GENERATOR } = astring;
|
||||||
|
|
||||||
|
|
92
packages/astro/src/runtime/server/stream.ts
Normal file
92
packages/astro/src/runtime/server/stream.ts
Normal 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}>`;
|
||||||
|
}
|
Loading…
Add table
Reference in a new issue