* fix(#1679): hoisted <script> rendering

* fix(#1679): do not print global for styles, but do for scripts

* fix: update ObjectSet implementation

* fix: dedupe elements in sets
This commit is contained in:
Nate Moore 2021-10-27 13:26:52 -07:00 committed by GitHub
parent 502c799b1d
commit 2da8ce211d
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
3 changed files with 34 additions and 18 deletions

View file

@ -52,8 +52,13 @@ export interface SSRMetadata {
}
export interface SSRResult {
styles: Set<string>;
scripts: Set<string>;
styles: Set<SSRElement>;
scripts: Set<SSRElement>;
createAstro(Astro: TopLevelAstro, props: Record<string, any>, slots: Record<string, any> | null): AstroGlobal;
_metadata: SSRMetadata;
}
export interface SSRElement {
props: Record<string, any>;
children: string;
}

View file

@ -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<SSRElement>(),
scripts: new Set<SSRElement>(),
/** This function returns the `Astro` faux-global */
createAstro(astroGlobal: TopLevelAstro, props: Record<string, any>, slots: Record<string, any> | null) {
const site = new URL(origin);
@ -193,3 +193,4 @@ ${frame}
throw e;
}
}

View file

@ -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 <script type="module"> to load the component */
async function generateHydrateScript(scriptOptions: HydrateScriptOptions, metadata: Required<AstroComponentMetadata>) {
async function generateHydrateScript(scriptOptions: HydrateScriptOptions, metadata: Required<AstroComponentMetadata>): Promise<SSRElement> {
const { renderer, astroId, props } = scriptOptions;
const { hydrate, componentUrl, componentExport } = metadata;
@ -168,13 +168,14 @@ async function generateHydrateScript(scriptOptions: HydrateScriptOptions, metada
return () => {};
`;
const hydrationScript = `<script type="module">
import setup from 'astro/client/${hydrate}.js';
const hydrationScript = {
props: { type: 'module' },
children: `import setup from 'astro/client/${hydrate}.js';
setup("${astroId}", {${metadata.hydrateArgs ? `value: ${JSON.stringify(metadata.hydrateArgs)}` : ''}}, async () => {
${hydrationSource}
});
</script>
`;
`,
};
return hydrationScript;
}
@ -354,16 +355,23 @@ export function defineScriptVars(vars: Record<any, any>) {
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('</head>', styles.join('\n') + scripts.join('\n') + '</head>');
}
@ -379,18 +387,20 @@ export async function renderAstroComponent(component: InstanceType<typeof AstroC
return template;
}
function renderElement(name: string, { props: _props, children = '' }: { props: Record<any, any>; 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;
}
}