summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGravatar Matthew Phillips <matthew@skypack.dev> 2023-02-02 14:21:32 -0500
committerGravatar GitHub <noreply@github.com> 2023-02-02 14:21:32 -0500
commit9bec6bc410f324a41c67e5d185fa86f78d7625f2 (patch)
tree257f3f75e904d709e7c047bffa242a727ad8d725
parent8c80e78dd5ebfe0528390f42222aadf4786a90fe (diff)
downloadastro-9bec6bc410f324a41c67e5d185fa86f78d7625f2.tar.gz
astro-9bec6bc410f324a41c67e5d185fa86f78d7625f2.tar.zst
astro-9bec6bc410f324a41c67e5d185fa86f78d7625f2.zip
Prevent eager rendering of head content in multi-level MDX layout (#6107)
* Prevent eager rendering of head content in multi-level MDX layout * Adding a changeset * Remove old comment * Keep track of slot position as well
-rw-r--r--.changeset/lucky-hounds-rhyme.md5
-rw-r--r--packages/astro/src/@types/astro.ts4
-rw-r--r--packages/astro/src/core/render/result.ts1
-rw-r--r--packages/astro/src/runtime/server/jsx.ts2
-rw-r--r--packages/astro/src/runtime/server/render/astro/factory.ts2
-rw-r--r--packages/astro/src/runtime/server/render/head.ts10
-rw-r--r--packages/astro/src/runtime/server/render/slot.ts6
-rw-r--r--packages/astro/src/runtime/server/render/util.ts6
-rw-r--r--packages/integrations/mdx/test/css-head-mdx.test.js4
9 files changed, 36 insertions, 4 deletions
diff --git a/.changeset/lucky-hounds-rhyme.md b/.changeset/lucky-hounds-rhyme.md
new file mode 100644
index 000000000..51b1c012b
--- /dev/null
+++ b/.changeset/lucky-hounds-rhyme.md
@@ -0,0 +1,5 @@
+---
+'astro': patch
+---
+
+Fixes head contents being placed in body in MDX components
diff --git a/packages/astro/src/@types/astro.ts b/packages/astro/src/@types/astro.ts
index 7fab1556f..ea3e5cd3c 100644
--- a/packages/astro/src/@types/astro.ts
+++ b/packages/astro/src/@types/astro.ts
@@ -1449,6 +1449,10 @@ export interface SSRResult {
): AstroGlobal;
resolve: (s: string) => Promise<string>;
response: ResponseInit;
+ // Bits 1 = astro, 2 = jsx, 4 = slot
+ // As rendering occurs these bits are manipulated to determine where content
+ // is within a slot. This is used for head injection.
+ scope: number;
_metadata: SSRMetadata;
}
diff --git a/packages/astro/src/core/render/result.ts b/packages/astro/src/core/render/result.ts
index 06c5e0698..3a6b0bf96 100644
--- a/packages/astro/src/core/render/result.ts
+++ b/packages/astro/src/core/render/result.ts
@@ -156,6 +156,7 @@ export function createResult(args: CreateResultArgs): SSRResult {
propagation: args.propagation ?? new Map(),
propagators: new Map(),
extraHead: [],
+ scope: 0,
cookies,
/** This function returns the `Astro` faux-global */
createAstro(
diff --git a/packages/astro/src/runtime/server/jsx.ts b/packages/astro/src/runtime/server/jsx.ts
index 651ccc945..365345a56 100644
--- a/packages/astro/src/runtime/server/jsx.ts
+++ b/packages/astro/src/runtime/server/jsx.ts
@@ -11,6 +11,7 @@ import {
voidElementNames,
} from './index.js';
import { HTMLParts } from './render/common.js';
+import { ScopeFlags } from './render/util.js';
import type { ComponentIterable } from './render/component';
const ClientOnlyPlaceholder = 'astro-client-only';
@@ -94,6 +95,7 @@ Did you forget to import the component or is it possible there is a typo?`);
props[key] = value;
}
}
+ result.scope |= ScopeFlags.JSX;
return markHTMLString(await renderToString(result, vnode.type as any, props, slots));
}
case !vnode.type && (vnode.type as any) !== 0:
diff --git a/packages/astro/src/runtime/server/render/astro/factory.ts b/packages/astro/src/runtime/server/render/astro/factory.ts
index 0d8ffb787..a1e7d0611 100644
--- a/packages/astro/src/runtime/server/render/astro/factory.ts
+++ b/packages/astro/src/runtime/server/render/astro/factory.ts
@@ -5,6 +5,7 @@ import type { RenderTemplateResult } from './render-template';
import { HTMLParts } from '../common.js';
import { isHeadAndContent } from './head-and-content.js';
import { renderAstroTemplateResult } from './render-template.js';
+import { ScopeFlags } from '../util.js';
export type AstroFactoryReturnValue = RenderTemplateResult | Response | HeadAndContent;
@@ -27,6 +28,7 @@ export async function renderToString(
props: any,
children: any
): Promise<string> {
+ result.scope |= ScopeFlags.Astro;
const factoryResult = await componentFactory(result, props, children);
if (factoryResult instanceof Response) {
diff --git a/packages/astro/src/runtime/server/render/head.ts b/packages/astro/src/runtime/server/render/head.ts
index ade6a7355..72be58623 100644
--- a/packages/astro/src/runtime/server/render/head.ts
+++ b/packages/astro/src/runtime/server/render/head.ts
@@ -1,7 +1,7 @@
import type { SSRResult } from '../../../@types/astro';
import { markHTMLString } from '../escape.js';
-import { renderElement } from './util.js';
+import { renderElement, ScopeFlags } from './util.js';
// Filter out duplicate elements in our set
const uniqueElements = (item: any, index: number, all: any[]) => {
@@ -52,6 +52,14 @@ export function* maybeRenderHead(result: SSRResult) {
return;
}
+ // Don't render the head inside of a JSX component that's inside of an Astro component
+ // as the Astro component will be the one to render the head.
+ switch(result.scope) {
+ case ScopeFlags.JSX | ScopeFlags.Slot | ScopeFlags.Astro: {
+ return;
+ }
+ }
+
// This is an instruction informing the page rendering that head might need rendering.
// This allows the page to deduplicate head injections.
yield { type: 'head', result } as const;
diff --git a/packages/astro/src/runtime/server/render/slot.ts b/packages/astro/src/runtime/server/render/slot.ts
index cd9225be4..1e2e946c3 100644
--- a/packages/astro/src/runtime/server/render/slot.ts
+++ b/packages/astro/src/runtime/server/render/slot.ts
@@ -3,6 +3,7 @@ import type { RenderInstruction } from './types.js';
import { HTMLString, markHTMLString } from '../escape.js';
import { renderChild } from './any.js';
+import { ScopeFlags } from './util.js';
const slotString = Symbol.for('astro:slot-string');
@@ -20,8 +21,9 @@ export function isSlotString(str: string): str is any {
return !!(str as any)[slotString];
}
-export async function renderSlot(_result: any, slotted: string, fallback?: any): Promise<string> {
+export async function renderSlot(result: SSRResult, slotted: string, fallback?: any): Promise<string> {
if (slotted) {
+ result.scope |= ScopeFlags.Slot;
let iterator = renderChild(slotted);
let content = '';
let instructions: null | RenderInstruction[] = null;
@@ -35,6 +37,8 @@ export async function renderSlot(_result: any, slotted: string, fallback?: any):
content += chunk;
}
}
+ // Remove the flag since we are now outside of the scope.
+ result.scope &= ~ScopeFlags.Slot;
return markHTMLString(new SlotString(content, instructions));
}
return fallback;
diff --git a/packages/astro/src/runtime/server/render/util.ts b/packages/astro/src/runtime/server/render/util.ts
index a95ef16f8..3986f9d50 100644
--- a/packages/astro/src/runtime/server/render/util.ts
+++ b/packages/astro/src/runtime/server/render/util.ts
@@ -128,3 +128,9 @@ export function renderElement(
}
return `<${name}${internalSpreadAttributes(props, shouldEscape)}>${children}</${name}>`;
}
+
+export const ScopeFlags = {
+ Astro: 1 << 0,
+ JSX: 1 << 1,
+ Slot: 1 << 2
+};
diff --git a/packages/integrations/mdx/test/css-head-mdx.test.js b/packages/integrations/mdx/test/css-head-mdx.test.js
index a6492c3ba..2b1dcdfe7 100644
--- a/packages/integrations/mdx/test/css-head-mdx.test.js
+++ b/packages/integrations/mdx/test/css-head-mdx.test.js
@@ -23,10 +23,10 @@ describe('Head injection w/ MDX', () => {
const html = await fixture.readFile('/indexThree/index.html');
const { document } = parseHTML(html);
- const links = document.querySelectorAll('link[rel=stylesheet]');
+ const links = document.querySelectorAll('head link[rel=stylesheet]');
expect(links).to.have.a.lengthOf(1);
- const scripts = document.querySelectorAll('script[type=module]');
+ const scripts = document.querySelectorAll('head script[type=module]');
expect(scripts).to.have.a.lengthOf(1);
});
});