From fa7ed3f3a9ce89c1c46e637b584271a6e199d211 Mon Sep 17 00:00:00 2001 From: Matthew Phillips Date: Thu, 23 Jun 2022 15:37:55 -0400 Subject: [PATCH] Remove post-rendering head injection (#3679) * Remove post-rendering head injection * Adds a changeset * Use a layout component for vue --- .changeset/tasty-hornets-return.md | 11 +++++ .../src/components/Layout.astro | 4 ++ .../preact-component/src/pages/markdown.md | 1 + .../src/components/Layout.astro | 4 ++ .../react-component/src/pages/markdown.md | 1 + .../src/components/Layout.astro | 4 ++ .../solid-component/src/pages/markdown.md | 1 + .../src/components/Layout.astro | 4 ++ .../svelte-component/src/pages/markdown.md | 1 + .../vue-component/src/components/Layout.astro | 4 ++ .../vue-component/src/pages/markdown.md | 1 + packages/astro/package.json | 2 +- packages/astro/src/@types/astro.ts | 1 - packages/astro/src/core/render/core.ts | 6 --- packages/astro/src/core/render/result.ts | 1 - packages/astro/src/runtime/server/index.ts | 43 +++++++------------ packages/astro/src/runtime/server/scripts.ts | 2 +- packages/astro/test/0-css.test.js | 2 +- .../astro/test/astro-partial-html.test.js | 6 +++ .../src/pages/with-head.astro | 9 ++++ packages/webapi/mod.d.ts | 2 +- pnpm-lock.yaml | 8 ++-- 22 files changed, 74 insertions(+), 44 deletions(-) create mode 100644 .changeset/tasty-hornets-return.md create mode 100644 packages/astro/e2e/fixtures/preact-component/src/components/Layout.astro create mode 100644 packages/astro/e2e/fixtures/react-component/src/components/Layout.astro create mode 100644 packages/astro/e2e/fixtures/solid-component/src/components/Layout.astro create mode 100644 packages/astro/e2e/fixtures/svelte-component/src/components/Layout.astro create mode 100644 packages/astro/e2e/fixtures/vue-component/src/components/Layout.astro create mode 100644 packages/astro/test/fixtures/astro-partial-html/src/pages/with-head.astro diff --git a/.changeset/tasty-hornets-return.md b/.changeset/tasty-hornets-return.md new file mode 100644 index 000000000..285230311 --- /dev/null +++ b/.changeset/tasty-hornets-return.md @@ -0,0 +1,11 @@ +--- +'astro': patch +--- + +Moves head injection to happen during rendering + +This change makes it so that head injection; to insert component stylesheets, hoisted scripts, for example, to happen during rendering than as a post-rendering step. + +This is to enable streaming. This change will only be noticeable if you are rendering your `` element inside of a framework component. If that is the case then the head items will be injected before the first non-head element in an Astro file instead. + +In the future we may offer a `` component as a way to control where these scripts/styles are inserted. diff --git a/packages/astro/e2e/fixtures/preact-component/src/components/Layout.astro b/packages/astro/e2e/fixtures/preact-component/src/components/Layout.astro new file mode 100644 index 000000000..3c3cf4e4d --- /dev/null +++ b/packages/astro/e2e/fixtures/preact-component/src/components/Layout.astro @@ -0,0 +1,4 @@ + + Preact component + + diff --git a/packages/astro/e2e/fixtures/preact-component/src/pages/markdown.md b/packages/astro/e2e/fixtures/preact-component/src/pages/markdown.md index c05e2ae52..7c521de77 100644 --- a/packages/astro/e2e/fixtures/preact-component/src/pages/markdown.md +++ b/packages/astro/e2e/fixtures/preact-component/src/pages/markdown.md @@ -1,4 +1,5 @@ --- +layout: ../components/Layout.astro setup: | import Counter from '../components/Counter.jsx'; import PreactComponent from '../components/JSXComponent.jsx'; diff --git a/packages/astro/e2e/fixtures/react-component/src/components/Layout.astro b/packages/astro/e2e/fixtures/react-component/src/components/Layout.astro new file mode 100644 index 000000000..7c166b532 --- /dev/null +++ b/packages/astro/e2e/fixtures/react-component/src/components/Layout.astro @@ -0,0 +1,4 @@ + + React component + + diff --git a/packages/astro/e2e/fixtures/react-component/src/pages/markdown.md b/packages/astro/e2e/fixtures/react-component/src/pages/markdown.md index 5461fc48a..fbc685a5b 100644 --- a/packages/astro/e2e/fixtures/react-component/src/pages/markdown.md +++ b/packages/astro/e2e/fixtures/react-component/src/pages/markdown.md @@ -1,4 +1,5 @@ --- +layout: ../components/Layout.astro setup: | import Counter from '../components/Counter.jsx'; import ReactComponent from '../components/JSXComponent.jsx'; diff --git a/packages/astro/e2e/fixtures/solid-component/src/components/Layout.astro b/packages/astro/e2e/fixtures/solid-component/src/components/Layout.astro new file mode 100644 index 000000000..63e0ff449 --- /dev/null +++ b/packages/astro/e2e/fixtures/solid-component/src/components/Layout.astro @@ -0,0 +1,4 @@ + + Solid component + + diff --git a/packages/astro/e2e/fixtures/solid-component/src/pages/markdown.md b/packages/astro/e2e/fixtures/solid-component/src/pages/markdown.md index 22d546481..21a779c9d 100644 --- a/packages/astro/e2e/fixtures/solid-component/src/pages/markdown.md +++ b/packages/astro/e2e/fixtures/solid-component/src/pages/markdown.md @@ -1,4 +1,5 @@ --- +layout: ../components/Layout.astro setup: | import Counter from '../components/Counter.jsx'; import SolidComponent from '../components/SolidComponent.jsx'; diff --git a/packages/astro/e2e/fixtures/svelte-component/src/components/Layout.astro b/packages/astro/e2e/fixtures/svelte-component/src/components/Layout.astro new file mode 100644 index 000000000..63e0ff449 --- /dev/null +++ b/packages/astro/e2e/fixtures/svelte-component/src/components/Layout.astro @@ -0,0 +1,4 @@ + + Solid component + + diff --git a/packages/astro/e2e/fixtures/svelte-component/src/pages/markdown.md b/packages/astro/e2e/fixtures/svelte-component/src/pages/markdown.md index 0030bccd1..ebc4d8795 100644 --- a/packages/astro/e2e/fixtures/svelte-component/src/pages/markdown.md +++ b/packages/astro/e2e/fixtures/svelte-component/src/pages/markdown.md @@ -1,4 +1,5 @@ --- +layout: ../components/Layout.astro setup: | import Counter from '../components/Counter.svelte'; import SvelteComponent from '../components/SvelteComponent.svelte'; diff --git a/packages/astro/e2e/fixtures/vue-component/src/components/Layout.astro b/packages/astro/e2e/fixtures/vue-component/src/components/Layout.astro new file mode 100644 index 000000000..285bc56e2 --- /dev/null +++ b/packages/astro/e2e/fixtures/vue-component/src/components/Layout.astro @@ -0,0 +1,4 @@ + + Vue component + + diff --git a/packages/astro/e2e/fixtures/vue-component/src/pages/markdown.md b/packages/astro/e2e/fixtures/vue-component/src/pages/markdown.md index 22698931a..3ae0470af 100644 --- a/packages/astro/e2e/fixtures/vue-component/src/pages/markdown.md +++ b/packages/astro/e2e/fixtures/vue-component/src/pages/markdown.md @@ -1,4 +1,5 @@ --- +layout: ../components/Layout.astro setup: | import Counter from '../components/Counter.vue'; import VueComponent from '../components/VueComponent.vue'; diff --git a/packages/astro/package.json b/packages/astro/package.json index d145c4979..326e64fa6 100644 --- a/packages/astro/package.json +++ b/packages/astro/package.json @@ -78,7 +78,7 @@ "test:e2e:match": "playwright test -g" }, "dependencies": { - "@astrojs/compiler": "^0.16.1", + "@astrojs/compiler": "^0.17.0", "@astrojs/language-server": "^0.13.4", "@astrojs/markdown-remark": "^0.11.3", "@astrojs/prism": "0.4.1", diff --git a/packages/astro/src/@types/astro.ts b/packages/astro/src/@types/astro.ts index d2ef92365..48cac0d12 100644 --- a/packages/astro/src/@types/astro.ts +++ b/packages/astro/src/@types/astro.ts @@ -1004,7 +1004,6 @@ export interface SSRElement { export interface SSRMetadata { renderers: SSRLoadedRenderer[]; pathname: string; - needsHydrationStyles: boolean; } export interface SSRResult { diff --git a/packages/astro/src/core/render/core.ts b/packages/astro/src/core/render/core.ts index 48d362924..32641c020 100644 --- a/packages/astro/src/core/render/core.ts +++ b/packages/astro/src/core/render/core.ts @@ -161,12 +161,6 @@ export async function render( } let html = page.html; - // handle final head injection if it hasn't happened already - if (html.indexOf('') == -1) { - html = (await renderHead(result)) + html; - } - // cleanup internal state flags - html = html.replace('', ''); // inject if missing (TODO: is a more robust check needed for comments, etc.?) if (!/ ); - result._metadata.needsHydrationStyles = true; // Render template if not all astro fragments are provided. let unrenderedSlots: string[] = []; @@ -590,16 +589,6 @@ Update your code to remove this warning.`); return handler.call(mod, proxy, request); } -async function replaceHeadInjection(result: SSRResult, html: string): Promise { - let template = html; - // injected by compiler - // Must be handled at the end of the rendering process - if (template.indexOf('') > -1) { - template = template.replace('', await renderHead(result)); - } - return template; -} - // Calls a component and renders it into a string of HTML export async function renderToString( result: SSRResult, @@ -627,8 +616,7 @@ export async function renderPage( const response = await componentFactory(result, props, children); if (isAstroComponent(response)) { - let template = await renderAstroComponent(response); - const html = await replaceHeadInjection(result, template); + let html = await renderAstroComponent(response); return { type: 'html', html, @@ -660,37 +648,36 @@ const uniqueElements = (item: any, index: number, all: any[]) => { ); }; -// Renders a page to completion by first calling the factory callback, waiting for its result, and then appending -// styles and scripts into the head. +const alreadyHeadRenderedResults = new WeakSet(); export async function renderHead(result: SSRResult): Promise { + alreadyHeadRenderedResults.add(result); const styles = Array.from(result.styles) .filter(uniqueElements) .map((style) => renderElement('style', style)); - let needsHydrationStyles = result._metadata.needsHydrationStyles; const scripts = Array.from(result.scripts) .filter(uniqueElements) .map((script, i) => { - if ('data-astro-component-hydration' in script.props) { - needsHydrationStyles = true; - } return renderElement('script', script); }); - if (needsHydrationStyles) { - styles.push( - renderElement('style', { - props: {}, - children: 'astro-island, astro-slot { display: contents; }', - }) - ); - } const links = Array.from(result.links) .filter(uniqueElements) .map((link) => renderElement('link', link, false)); return markHTMLString( - links.join('\n') + styles.join('\n') + scripts.join('\n') + '\n' + '' + links.join('\n') + styles.join('\n') + scripts.join('\n') ); } +// This function is called by Astro components that do not contain a component +// This accomodates the fact that using a is optional in Astro, so this +// is called before a component's first non-head HTML element. If the head was +// already injected it is a noop. +export function maybeRenderHead(result: SSRResult): string | Promise { + if(alreadyHeadRenderedResults.has(result)) { + return ''; + } + return renderHead(result); +} + export async function renderAstroComponent(component: InstanceType) { let template = []; diff --git a/packages/astro/src/runtime/server/scripts.ts b/packages/astro/src/runtime/server/scripts.ts index 0446ed2c7..4fe7b9057 100644 --- a/packages/astro/src/runtime/server/scripts.ts +++ b/packages/astro/src/runtime/server/scripts.ts @@ -59,7 +59,7 @@ export function getPrescripts(type: PrescriptType, directive: string): string { // deps to be loaded immediately. switch (type) { case 'both': - return ``; + return ``; case 'directive': return ``; } diff --git a/packages/astro/test/0-css.test.js b/packages/astro/test/0-css.test.js index e1b317f32..4b2862470 100644 --- a/packages/astro/test/0-css.test.js +++ b/packages/astro/test/0-css.test.js @@ -65,7 +65,7 @@ describe('CSS', function () { it('Using hydrated components adds astro-island styles', async () => { const inline = $('style').html(); - expect(inline).to.include('display: contents'); + expect(inline).to.include('display:contents'); }); it(' + + +

testing

+ + diff --git a/packages/webapi/mod.d.ts b/packages/webapi/mod.d.ts index a3c49dc5c..b385e82a5 100644 --- a/packages/webapi/mod.d.ts +++ b/packages/webapi/mod.d.ts @@ -1,5 +1,5 @@ export { pathToPosix } from './lib/utils'; -export { AbortController, AbortSignal, alert, atob, Blob, btoa, ByteLengthQueuingStrategy, cancelAnimationFrame, cancelIdleCallback, CanvasRenderingContext2D, CharacterData, clearTimeout, Comment, CountQueuingStrategy, CSSStyleSheet, CustomElementRegistry, CustomEvent, Document, DocumentFragment, DOMException, Element, Event, EventTarget, fetch, File, FormData, Headers, HTMLBodyElement, HTMLCanvasElement, HTMLDivElement, HTMLDocument, HTMLElement, HTMLHeadElement, HTMLHtmlElement, HTMLImageElement, HTMLSpanElement, HTMLStyleElement, HTMLTemplateElement, HTMLUnknownElement, Image, ImageData, IntersectionObserver, MediaQueryList, MutationObserver, Node, NodeFilter, NodeIterator, OffscreenCanvas, ReadableByteStreamController, ReadableStream, ReadableStreamBYOBReader, ReadableStreamBYOBRequest, ReadableStreamDefaultController, ReadableStreamDefaultReader, Request, requestAnimationFrame, requestIdleCallback, ResizeObserver, Response, setTimeout, ShadowRoot, structuredClone, StyleSheet, Text, TransformStream, TreeWalker, URLPattern, Window, WritableStream, WritableStreamDefaultController, WritableStreamDefaultWriter } from './mod.js'; +export { AbortController, AbortSignal, alert, atob, Blob, btoa, ByteLengthQueuingStrategy, cancelAnimationFrame, cancelIdleCallback, CanvasRenderingContext2D, CharacterData, clearTimeout, Comment, CountQueuingStrategy, CSSStyleSheet, CustomElementRegistry, CustomEvent, Document, DocumentFragment, DOMException, Element, Event, EventTarget, fetch, File, FormData, Headers, HTMLBodyElement, HTMLCanvasElement, HTMLDivElement, HTMLDocument, HTMLElement, HTMLHeadElement, HTMLHtmlElement, HTMLImageElement, HTMLSpanElement, HTMLStyleElement, HTMLTemplateElement, HTMLUnknownElement, Image, ImageData, IntersectionObserver, MediaQueryList, MutationObserver, Node, NodeFilter, NodeIterator, OffscreenCanvas, ReadableByteStreamController, ReadableStream, ReadableStreamBYOBReader, ReadableStreamBYOBRequest, ReadableStreamDefaultController, ReadableStreamDefaultReader, Request, requestAnimationFrame, requestIdleCallback, ResizeObserver, Response, setTimeout, ShadowRoot, structuredClone, StyleSheet, Text, TransformStream, TreeWalker, URLPattern, Window, WritableStream, WritableStreamDefaultController, WritableStreamDefaultWriter, } from './mod.js'; export declare const polyfill: { (target: any, options?: PolyfillOptions): any; internals(target: any, name: string): any; diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 70337edc6..c92742962 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -463,7 +463,7 @@ importers: packages/astro: specifiers: - '@astrojs/compiler': ^0.16.1 + '@astrojs/compiler': ^0.17.0 '@astrojs/language-server': ^0.13.4 '@astrojs/markdown-remark': ^0.11.3 '@astrojs/prism': 0.4.1 @@ -547,7 +547,7 @@ importers: yargs-parser: ^21.0.1 zod: ^3.17.3 dependencies: - '@astrojs/compiler': 0.16.1 + '@astrojs/compiler': 0.17.0 '@astrojs/language-server': 0.13.4 '@astrojs/markdown-remark': link:../markdown/remark '@astrojs/prism': link:../astro-prism @@ -2439,8 +2439,8 @@ packages: leven: 3.1.0 dev: true - /@astrojs/compiler/0.16.1: - resolution: {integrity: sha512-6l5j9b/sEdyqRUvwJpp+SmlAkNO5WeISuNEXnyH9aGwzIAdqgLB2boAJef9lWadlOjG8rSPO29WHRa3qS2Okew==} + /@astrojs/compiler/0.17.0: + resolution: {integrity: sha512-3q6Yw6CGDfUwheDS29cHjQxn57ql0X98DskU6ym3bw/FdD8RMbGi0Es1Evlh+WHig948LUcYq19EHAMZO3bP3w==} dev: false /@astrojs/language-server/0.13.4: