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!
|
||||
|
||||
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 { 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)
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
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…
Reference in a new issue