* 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 { export interface SSRResult {
styles: Set<string>; styles: Set<SSRElement>;
scripts: Set<string>; scripts: Set<SSRElement>;
createAstro(Astro: TopLevelAstro, props: Record<string, any>, slots: Record<string, any> | null): AstroGlobal; createAstro(Astro: TopLevelAstro, props: Record<string, any>, slots: Record<string, any> | null): AstroGlobal;
_metadata: SSRMetadata; _metadata: SSRMetadata;
} }
export interface SSRElement {
props: Record<string, any>;
children: string;
}

View file

@ -1,7 +1,7 @@
import type { BuildResult } from 'esbuild'; import type { BuildResult } from 'esbuild';
import type { ViteDevServer } from '../vite'; import type { ViteDevServer } from '../vite';
import type { AstroConfig, ComponentInstance, GetStaticPathsResult, Params, Props, Renderer, RouteCache, RouteData, RuntimeMode, SSRError } from '../../@types/astro-core'; 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 type { LogOptions } from '../logger';
import { fileURLToPath } from 'url'; 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 // 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. // calling the render() function will populate the object with scripts, styles, etc.
const result: SSRResult = { const result: SSRResult = {
styles: new Set(), styles: new Set<SSRElement>(),
scripts: new Set(), scripts: new Set<SSRElement>(),
/** This function returns the `Astro` faux-global */ /** This function returns the `Astro` faux-global */
createAstro(astroGlobal: TopLevelAstro, props: Record<string, any>, slots: Record<string, any> | null) { createAstro(astroGlobal: TopLevelAstro, props: Record<string, any>, slots: Record<string, any> | null) {
const site = new URL(origin); const site = new URL(origin);
@ -193,3 +193,4 @@ ${frame}
throw e; throw e;
} }
} }

View file

@ -1,5 +1,5 @@
import type { AstroComponentMetadata, Renderer } from '../../@types/astro-core'; 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 type { TopLevelAstro } from '../../@types/astro-runtime';
import { valueToEstree } from 'estree-util-value-to-estree'; 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 */ /** 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 { renderer, astroId, props } = scriptOptions;
const { hydrate, componentUrl, componentExport } = metadata; const { hydrate, componentUrl, componentExport } = metadata;
@ -168,13 +168,14 @@ async function generateHydrateScript(scriptOptions: HydrateScriptOptions, metada
return () => {}; return () => {};
`; `;
const hydrationScript = `<script type="module"> const hydrationScript = {
import setup from 'astro/client/${hydrate}.js'; props: { type: 'module' },
children: `import setup from 'astro/client/${hydrate}.js';
setup("${astroId}", {${metadata.hydrateArgs ? `value: ${JSON.stringify(metadata.hydrateArgs)}` : ''}}, async () => { setup("${astroId}", {${metadata.hydrateArgs ? `value: ${JSON.stringify(metadata.hydrateArgs)}` : ''}}, async () => {
${hydrationSource} ${hydrationSource}
}); });
</script> `,
`; };
return hydrationScript; return hydrationScript;
} }
@ -354,16 +355,23 @@ export function defineScriptVars(vars: Record<any, any>) {
return output; 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); const Component = await componentFactory(result, props, children);
let template = await renderAstroComponent(Component); let template = await renderAstroComponent(Component);
return template; 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 template = await renderToString(result, Component, props, children);
const styles = Array.from(result.styles).map((style: any) => renderElement('style', style)); const styles = Array.from(result.styles).filter(uniqueElements).map((style) => renderElement('style', style));
const scripts = Array.from(result.scripts); const scripts = Array.from(result.scripts).filter(uniqueElements).map((script) => renderElement('script', script));
return template.replace('</head>', styles.join('\n') + scripts.join('\n') + '</head>'); 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; 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` // 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 (defineVars) {
if (name === 'style') { if (name === 'style') {
if (global) { if (props.global) {
children = defineStyleVars(`:root`, defineVars) + '\n' + children; children = defineStyleVars(`:root`, defineVars) + '\n' + children;
} else { } else {
children = defineStyleVars(`.astro-${astroId}`, defineVars) + '\n' + children; children = defineStyleVars(`.astro-${astroId}`, defineVars) + '\n' + children;
} }
delete props.global;
} }
if (name === 'script') { if (name === 'script') {
delete props.hoist;
children = defineScriptVars(defineVars) + '\n' + children; children = defineScriptVars(defineVars) + '\n' + children;
} }
} }