Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Prevent eager rendering of head content in multi-level MDX layout #6107

Merged
merged 4 commits into from
Feb 2, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/lucky-hounds-rhyme.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'astro': patch
---

Fixes head contents being placed in body in MDX components
4 changes: 4 additions & 0 deletions packages/astro/src/@types/astro.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}

Expand Down
1 change: 1 addition & 0 deletions packages/astro/src/core/render/result.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand Down
2 changes: 2 additions & 0 deletions packages/astro/src/runtime/server/jsx.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -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:
Expand Down
2 changes: 2 additions & 0 deletions packages/astro/src/runtime/server/render/astro/factory.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand All @@ -27,6 +28,7 @@ export async function renderToString(
props: any,
children: any
): Promise<string> {
result.scope |= ScopeFlags.Astro;
bholmesdev marked this conversation as resolved.
Show resolved Hide resolved
const factoryResult = await componentFactory(result, props, children);

if (factoryResult instanceof Response) {
Expand Down
10 changes: 9 additions & 1 deletion packages/astro/src/runtime/server/render/head.ts
Original file line number Diff line number Diff line change
@@ -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[]) => {
Expand Down Expand Up @@ -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;
Expand Down
6 changes: 5 additions & 1 deletion packages/astro/src/runtime/server/render/slot.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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');

Expand All @@ -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;
Expand All @@ -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;
Expand Down
6 changes: 6 additions & 0 deletions packages/astro/src/runtime/server/render/util.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
};
4 changes: 2 additions & 2 deletions packages/integrations/mdx/test/css-head-mdx.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -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);
});
});
Expand Down