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: