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
This commit is contained in:
parent
cd425bf448
commit
c8d601ebeb
9 changed files with 36 additions and 4 deletions
5
.changeset/lucky-hounds-rhyme.md
Normal file
5
.changeset/lucky-hounds-rhyme.md
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
---
|
||||||
|
'astro': patch
|
||||||
|
---
|
||||||
|
|
||||||
|
Fixes head contents being placed in body in MDX components
|
|
@ -1449,6 +1449,10 @@ export interface SSRResult {
|
||||||
): AstroGlobal;
|
): AstroGlobal;
|
||||||
resolve: (s: string) => Promise<string>;
|
resolve: (s: string) => Promise<string>;
|
||||||
response: ResponseInit;
|
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;
|
_metadata: SSRMetadata;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -156,6 +156,7 @@ export function createResult(args: CreateResultArgs): SSRResult {
|
||||||
propagation: args.propagation ?? new Map(),
|
propagation: args.propagation ?? new Map(),
|
||||||
propagators: new Map(),
|
propagators: new Map(),
|
||||||
extraHead: [],
|
extraHead: [],
|
||||||
|
scope: 0,
|
||||||
cookies,
|
cookies,
|
||||||
/** This function returns the `Astro` faux-global */
|
/** This function returns the `Astro` faux-global */
|
||||||
createAstro(
|
createAstro(
|
||||||
|
|
|
@ -11,6 +11,7 @@ import {
|
||||||
voidElementNames,
|
voidElementNames,
|
||||||
} from './index.js';
|
} from './index.js';
|
||||||
import { HTMLParts } from './render/common.js';
|
import { HTMLParts } from './render/common.js';
|
||||||
|
import { ScopeFlags } from './render/util.js';
|
||||||
import type { ComponentIterable } from './render/component';
|
import type { ComponentIterable } from './render/component';
|
||||||
|
|
||||||
const ClientOnlyPlaceholder = 'astro-client-only';
|
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;
|
props[key] = value;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
result.scope |= ScopeFlags.JSX;
|
||||||
return markHTMLString(await renderToString(result, vnode.type as any, props, slots));
|
return markHTMLString(await renderToString(result, vnode.type as any, props, slots));
|
||||||
}
|
}
|
||||||
case !vnode.type && (vnode.type as any) !== 0:
|
case !vnode.type && (vnode.type as any) !== 0:
|
||||||
|
|
|
@ -5,6 +5,7 @@ import type { RenderTemplateResult } from './render-template';
|
||||||
import { HTMLParts } from '../common.js';
|
import { HTMLParts } from '../common.js';
|
||||||
import { isHeadAndContent } from './head-and-content.js';
|
import { isHeadAndContent } from './head-and-content.js';
|
||||||
import { renderAstroTemplateResult } from './render-template.js';
|
import { renderAstroTemplateResult } from './render-template.js';
|
||||||
|
import { ScopeFlags } from '../util.js';
|
||||||
|
|
||||||
export type AstroFactoryReturnValue = RenderTemplateResult | Response | HeadAndContent;
|
export type AstroFactoryReturnValue = RenderTemplateResult | Response | HeadAndContent;
|
||||||
|
|
||||||
|
@ -27,6 +28,7 @@ export async function renderToString(
|
||||||
props: any,
|
props: any,
|
||||||
children: any
|
children: any
|
||||||
): Promise<string> {
|
): Promise<string> {
|
||||||
|
result.scope |= ScopeFlags.Astro;
|
||||||
const factoryResult = await componentFactory(result, props, children);
|
const factoryResult = await componentFactory(result, props, children);
|
||||||
|
|
||||||
if (factoryResult instanceof Response) {
|
if (factoryResult instanceof Response) {
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import type { SSRResult } from '../../../@types/astro';
|
import type { SSRResult } from '../../../@types/astro';
|
||||||
|
|
||||||
import { markHTMLString } from '../escape.js';
|
import { markHTMLString } from '../escape.js';
|
||||||
import { renderElement } from './util.js';
|
import { renderElement, ScopeFlags } from './util.js';
|
||||||
|
|
||||||
// Filter out duplicate elements in our set
|
// Filter out duplicate elements in our set
|
||||||
const uniqueElements = (item: any, index: number, all: any[]) => {
|
const uniqueElements = (item: any, index: number, all: any[]) => {
|
||||||
|
@ -52,6 +52,14 @@ export function* maybeRenderHead(result: SSRResult) {
|
||||||
return;
|
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 is an instruction informing the page rendering that head might need rendering.
|
||||||
// This allows the page to deduplicate head injections.
|
// This allows the page to deduplicate head injections.
|
||||||
yield { type: 'head', result } as const;
|
yield { type: 'head', result } as const;
|
||||||
|
|
|
@ -3,6 +3,7 @@ import type { RenderInstruction } from './types.js';
|
||||||
|
|
||||||
import { HTMLString, markHTMLString } from '../escape.js';
|
import { HTMLString, markHTMLString } from '../escape.js';
|
||||||
import { renderChild } from './any.js';
|
import { renderChild } from './any.js';
|
||||||
|
import { ScopeFlags } from './util.js';
|
||||||
|
|
||||||
const slotString = Symbol.for('astro:slot-string');
|
const slotString = Symbol.for('astro:slot-string');
|
||||||
|
|
||||||
|
@ -20,8 +21,9 @@ export function isSlotString(str: string): str is any {
|
||||||
return !!(str as any)[slotString];
|
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) {
|
if (slotted) {
|
||||||
|
result.scope |= ScopeFlags.Slot;
|
||||||
let iterator = renderChild(slotted);
|
let iterator = renderChild(slotted);
|
||||||
let content = '';
|
let content = '';
|
||||||
let instructions: null | RenderInstruction[] = null;
|
let instructions: null | RenderInstruction[] = null;
|
||||||
|
@ -35,6 +37,8 @@ export async function renderSlot(_result: any, slotted: string, fallback?: any):
|
||||||
content += chunk;
|
content += chunk;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// Remove the flag since we are now outside of the scope.
|
||||||
|
result.scope &= ~ScopeFlags.Slot;
|
||||||
return markHTMLString(new SlotString(content, instructions));
|
return markHTMLString(new SlotString(content, instructions));
|
||||||
}
|
}
|
||||||
return fallback;
|
return fallback;
|
||||||
|
|
|
@ -128,3 +128,9 @@ export function renderElement(
|
||||||
}
|
}
|
||||||
return `<${name}${internalSpreadAttributes(props, shouldEscape)}>${children}</${name}>`;
|
return `<${name}${internalSpreadAttributes(props, shouldEscape)}>${children}</${name}>`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const ScopeFlags = {
|
||||||
|
Astro: 1 << 0,
|
||||||
|
JSX: 1 << 1,
|
||||||
|
Slot: 1 << 2
|
||||||
|
};
|
||||||
|
|
|
@ -23,10 +23,10 @@ describe('Head injection w/ MDX', () => {
|
||||||
const html = await fixture.readFile('/indexThree/index.html');
|
const html = await fixture.readFile('/indexThree/index.html');
|
||||||
const { document } = parseHTML(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);
|
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);
|
expect(scripts).to.have.a.lengthOf(1);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
Loading…
Add table
Reference in a new issue