Fix head injection misplacement with Astro.slots.render() (#6196)
* Fix head injection misplacement with Astro.slots.render() * Adding a changeset * Fix case of JSX with no layout * missing break
This commit is contained in:
parent
0c3485ab07
commit
3390cb8444
19 changed files with 216 additions and 32 deletions
5
.changeset/thirty-bugs-film.md
Normal file
5
.changeset/thirty-bugs-film.md
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
---
|
||||||
|
'astro': patch
|
||||||
|
---
|
||||||
|
|
||||||
|
Fix head injection misplacement with Astro.slots.render()
|
|
@ -9,7 +9,7 @@ import {
|
||||||
PrescriptType,
|
PrescriptType,
|
||||||
} from '../scripts.js';
|
} from '../scripts.js';
|
||||||
import { renderAllHeadContent } from './head.js';
|
import { renderAllHeadContent } from './head.js';
|
||||||
import { ScopeFlags } from './scope.js';
|
import { hasScopeFlag, ScopeFlags } from './scope.js';
|
||||||
import { isSlotString, type SlotString } from './slot.js';
|
import { isSlotString, type SlotString } from './slot.js';
|
||||||
|
|
||||||
export const Fragment = Symbol.for('astro:fragment');
|
export const Fragment = Symbol.for('astro:fragment');
|
||||||
|
@ -63,7 +63,23 @@ export function stringifyChunk(result: SSRResult, chunk: string | SlotString | R
|
||||||
return '';
|
return '';
|
||||||
}
|
}
|
||||||
|
|
||||||
// Astro.slots.render('default') should never render head content.
|
// Astro rendered within JSX, head will be injected by the page itself.
|
||||||
|
case ScopeFlags.JSX | ScopeFlags.Astro: {
|
||||||
|
if(hasScopeFlag(result, ScopeFlags.JSX)) {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// If the current scope is with Astro.slots.render()
|
||||||
|
case ScopeFlags.Slot: {
|
||||||
|
if(hasScopeFlag(result, ScopeFlags.RenderSlot)) {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Astro.slots.render() should never render head content.
|
||||||
case ScopeFlags.RenderSlot | ScopeFlags.Astro:
|
case ScopeFlags.RenderSlot | ScopeFlags.Astro:
|
||||||
case ScopeFlags.RenderSlot | ScopeFlags.Astro | ScopeFlags.JSX:
|
case ScopeFlags.RenderSlot | ScopeFlags.Astro | ScopeFlags.JSX:
|
||||||
case ScopeFlags.RenderSlot | ScopeFlags.Astro | ScopeFlags.JSX | ScopeFlags.HeadBuffer: {
|
case ScopeFlags.RenderSlot | ScopeFlags.Astro | ScopeFlags.JSX | ScopeFlags.HeadBuffer: {
|
||||||
|
|
|
@ -18,6 +18,10 @@ export function removeScopeFlag(result: SSRResult, flag: ScopeFlagValues) {
|
||||||
result.scope &= ~flag;
|
result.scope &= ~flag;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function hasScopeFlag(result: SSRResult, flag: ScopeFlagValues) {
|
||||||
|
return (result.scope & flag) === flag;
|
||||||
|
}
|
||||||
|
|
||||||
export function createScopedResult(result: SSRResult, flag?: ScopeFlagValues): SSRResult {
|
export function createScopedResult(result: SSRResult, flag?: ScopeFlagValues): SSRResult {
|
||||||
const scopedResult = Object.create(result, {
|
const scopedResult = Object.create(result, {
|
||||||
scope: {
|
scope: {
|
||||||
|
|
|
@ -4,7 +4,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 { createScopedResult, ScopeFlags } from './scope.js';
|
import { createScopedResult, hasScopeFlag, ScopeFlags } from './scope.js';
|
||||||
|
|
||||||
type RenderTemplateResult = ReturnType<typeof renderTemplate>;
|
type RenderTemplateResult = ReturnType<typeof renderTemplate>;
|
||||||
export type ComponentSlots = Record<string, ComponentSlotValue>;
|
export type ComponentSlots = Record<string, ComponentSlotValue>;
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
{
|
{
|
||||||
"name": "@test/head-injection-md",
|
"name": "@test/head-injection",
|
||||||
"version": "0.0.0",
|
"version": "0.0.0",
|
||||||
"private": true,
|
"private": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
8
packages/astro/test/fixtures/head-injection/src/components/RegularSlot.astro
vendored
Normal file
8
packages/astro/test/fixtures/head-injection/src/components/RegularSlot.astro
vendored
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
<style>
|
||||||
|
div {
|
||||||
|
font-weight: bolder;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
<div>
|
||||||
|
<slot />
|
||||||
|
</div>
|
12
packages/astro/test/fixtures/head-injection/src/components/SlotRenderComponent.astro
vendored
Normal file
12
packages/astro/test/fixtures/head-injection/src/components/SlotRenderComponent.astro
vendored
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
---
|
||||||
|
const html = await Astro.slots.render('slot-name');
|
||||||
|
---
|
||||||
|
<div class="p-sample">
|
||||||
|
<Fragment set:html={html} />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.p-sample {
|
||||||
|
color: red;
|
||||||
|
}
|
||||||
|
</style>
|
7
packages/astro/test/fixtures/head-injection/src/components/SlotRenderLayout.astro
vendored
Normal file
7
packages/astro/test/fixtures/head-injection/src/components/SlotRenderLayout.astro
vendored
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head> </head>
|
||||||
|
<body>
|
||||||
|
<slot />
|
||||||
|
</body>
|
||||||
|
</html>
|
25
packages/astro/test/fixtures/head-injection/src/components/SlotsRender.astro
vendored
Normal file
25
packages/astro/test/fixtures/head-injection/src/components/SlotsRender.astro
vendored
Normal file
|
@ -0,0 +1,25 @@
|
||||||
|
---
|
||||||
|
export interface Props {
|
||||||
|
title: string;
|
||||||
|
subtitle: string;
|
||||||
|
content?: string;
|
||||||
|
}
|
||||||
|
const {
|
||||||
|
title,
|
||||||
|
subtitle = await Astro.slots.render("subtitle"),
|
||||||
|
content = await Astro.slots.render("content"),
|
||||||
|
} = Astro.props;
|
||||||
|
---
|
||||||
|
|
||||||
|
<style>
|
||||||
|
section {
|
||||||
|
background: slategrey;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
<section>
|
||||||
|
<div>
|
||||||
|
{title && <h1>{title}</h1>}
|
||||||
|
{subtitle && <p set:html={subtitle} />}
|
||||||
|
{content && <div set:html={content} />}
|
||||||
|
</div>
|
||||||
|
</section>
|
24
packages/astro/test/fixtures/head-injection/src/pages/with-slot-in-render-slot.astro
vendored
Normal file
24
packages/astro/test/fixtures/head-injection/src/pages/with-slot-in-render-slot.astro
vendored
Normal file
|
@ -0,0 +1,24 @@
|
||||||
|
---
|
||||||
|
import Layout from '../components/Layout.astro';
|
||||||
|
import SlotsRender from '../components/SlotsRender.astro';
|
||||||
|
---
|
||||||
|
|
||||||
|
<Layout>
|
||||||
|
<SlotsRender
|
||||||
|
title="Lorem ipsum lorem"
|
||||||
|
subtitle="At vero eos et accusamus et iusto odio dignissimos ducimus qui blanditiis praesentium voluptatum deleniti"
|
||||||
|
>
|
||||||
|
<Fragment slot="content">
|
||||||
|
<p>
|
||||||
|
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore
|
||||||
|
magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo
|
||||||
|
consequat.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<p class="mt-4">
|
||||||
|
Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur
|
||||||
|
sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.
|
||||||
|
</p>
|
||||||
|
</Fragment>
|
||||||
|
</SlotsRender>
|
||||||
|
</Layout>
|
11
packages/astro/test/fixtures/head-injection/src/pages/with-slot-in-slot.astro
vendored
Normal file
11
packages/astro/test/fixtures/head-injection/src/pages/with-slot-in-slot.astro
vendored
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
---
|
||||||
|
import Layout from "../components/SlotRenderLayout.astro";
|
||||||
|
import RegularSlot from "../components/RegularSlot.astro"
|
||||||
|
---
|
||||||
|
<Layout>
|
||||||
|
<RegularSlot>
|
||||||
|
<RegularSlot>
|
||||||
|
<p slot="slot-name">Paragraph.</p>
|
||||||
|
</RegularSlot>
|
||||||
|
</RegularSlot>
|
||||||
|
</Layout>
|
9
packages/astro/test/fixtures/head-injection/src/pages/with-slot-render.astro
vendored
Normal file
9
packages/astro/test/fixtures/head-injection/src/pages/with-slot-render.astro
vendored
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
---
|
||||||
|
import Layout from "../components/SlotRenderLayout.astro";
|
||||||
|
import Component from "../components/SlotRenderComponent.astro"
|
||||||
|
---
|
||||||
|
<Layout>
|
||||||
|
<Component>
|
||||||
|
<p slot="slot-name">Paragraph.</p>
|
||||||
|
</Component>
|
||||||
|
</Layout>
|
|
@ -1,27 +0,0 @@
|
||||||
import { expect } from 'chai';
|
|
||||||
import * as cheerio from 'cheerio';
|
|
||||||
import { loadFixture } from './test-utils.js';
|
|
||||||
|
|
||||||
describe('Head injection with markdown', () => {
|
|
||||||
/** @type {import('./test-utils').Fixture} */
|
|
||||||
let fixture;
|
|
||||||
|
|
||||||
before(async () => {
|
|
||||||
fixture = await loadFixture({
|
|
||||||
root: './fixtures/head-injection-md/',
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('build', () => {
|
|
||||||
before(async () => {
|
|
||||||
await fixture.build();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('only injects head content once', async () => {
|
|
||||||
const html = await fixture.readFile(`/index.html`);
|
|
||||||
const $ = cheerio.load(html);
|
|
||||||
|
|
||||||
expect($('link[rel=stylesheet]')).to.have.a.lengthOf(1);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
55
packages/astro/test/head-injection.test.js
Normal file
55
packages/astro/test/head-injection.test.js
Normal file
|
@ -0,0 +1,55 @@
|
||||||
|
import { expect } from 'chai';
|
||||||
|
import * as cheerio from 'cheerio';
|
||||||
|
import { loadFixture } from './test-utils.js';
|
||||||
|
|
||||||
|
describe('Head injection', () => {
|
||||||
|
/** @type {import('./test-utils').Fixture} */
|
||||||
|
let fixture;
|
||||||
|
|
||||||
|
before(async () => {
|
||||||
|
fixture = await loadFixture({
|
||||||
|
root: './fixtures/head-injection/',
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('build', () => {
|
||||||
|
before(async () => {
|
||||||
|
await fixture.build();
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('Markdown', () => {
|
||||||
|
it('only injects head content once', async () => {
|
||||||
|
const html = await fixture.readFile(`/index.html`);
|
||||||
|
const $ = cheerio.load(html);
|
||||||
|
|
||||||
|
expect($('head link[rel=stylesheet]')).to.have.a.lengthOf(1);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('Astro components', () => {
|
||||||
|
it('Using slots within slots', async () => {
|
||||||
|
const html = await fixture.readFile('/with-slot-in-slot/index.html');
|
||||||
|
const $ = cheerio.load(html);
|
||||||
|
|
||||||
|
expect($('head link[rel=stylesheet]')).to.have.a.lengthOf(1);
|
||||||
|
expect($('body link[rel=stylesheet]')).to.have.a.lengthOf(0);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Using slots with Astro.slots.render()', async () => {
|
||||||
|
const html = await fixture.readFile('/with-slot-render/index.html');
|
||||||
|
const $ = cheerio.load(html);
|
||||||
|
|
||||||
|
expect($('head link[rel=stylesheet]')).to.have.a.lengthOf(1);
|
||||||
|
expect($('body link[rel=stylesheet]')).to.have.a.lengthOf(0);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Using slots within slots using Astro.slots.render()', async () => {
|
||||||
|
const html = await fixture.readFile('/with-slot-in-render-slot/index.html');
|
||||||
|
const $ = cheerio.load(html);
|
||||||
|
|
||||||
|
expect($('head link[rel=stylesheet]')).to.have.a.lengthOf(2);
|
||||||
|
expect($('body link[rel=stylesheet]')).to.have.a.lengthOf(0);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
|
@ -3,6 +3,7 @@ import mdx from '@astrojs/mdx';
|
||||||
import { expect } from 'chai';
|
import { expect } from 'chai';
|
||||||
import { parseHTML } from 'linkedom';
|
import { parseHTML } from 'linkedom';
|
||||||
import { loadFixture } from '../../../astro/test/test-utils.js';
|
import { loadFixture } from '../../../astro/test/test-utils.js';
|
||||||
|
import * as cheerio from 'cheerio';
|
||||||
|
|
||||||
describe('Head injection w/ MDX', () => {
|
describe('Head injection w/ MDX', () => {
|
||||||
let fixture;
|
let fixture;
|
||||||
|
@ -56,5 +57,17 @@ describe('Head injection w/ MDX', () => {
|
||||||
const links = document.querySelectorAll('head 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);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('Using component but no layout', async () => {
|
||||||
|
const html = await fixture.readFile('/noLayoutWithComponent/index.html');
|
||||||
|
// Using cheerio here because linkedom doesn't support head tag injection
|
||||||
|
const $ = cheerio.load(html);
|
||||||
|
|
||||||
|
const headLinks = $('head link[rel=stylesheet]');
|
||||||
|
expect(headLinks).to.have.a.lengthOf(1);
|
||||||
|
|
||||||
|
const bodyLinks = $('body link[rel=stylesheet]');
|
||||||
|
expect(bodyLinks).to.have.a.lengthOf(0);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
22
packages/integrations/mdx/test/fixtures/css-head-mdx/src/pages/noLayoutWithComponent.mdx
vendored
Normal file
22
packages/integrations/mdx/test/fixtures/css-head-mdx/src/pages/noLayoutWithComponent.mdx
vendored
Normal file
|
@ -0,0 +1,22 @@
|
||||||
|
---
|
||||||
|
title: 'Lorem'
|
||||||
|
description: 'Lorem ipsum dolor sit amet'
|
||||||
|
pubDate: 'Jul 02 2022'
|
||||||
|
---
|
||||||
|
|
||||||
|
import MyComponent from '../components/HelloWorld.astro';
|
||||||
|
|
||||||
|
|
||||||
|
## Lorem
|
||||||
|
|
||||||
|
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.
|
||||||
|
|
||||||
|
## Lorem 2
|
||||||
|
|
||||||
|
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.
|
||||||
|
|
||||||
|
<MyComponent />
|
||||||
|
|
||||||
|
## Lorem 3
|
||||||
|
|
||||||
|
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.
|
|
@ -1849,7 +1849,7 @@ importers:
|
||||||
dependencies:
|
dependencies:
|
||||||
astro: link:../../..
|
astro: link:../../..
|
||||||
|
|
||||||
packages/astro/test/fixtures/head-injection-md:
|
packages/astro/test/fixtures/head-injection:
|
||||||
specifiers:
|
specifiers:
|
||||||
astro: workspace:*
|
astro: workspace:*
|
||||||
dependencies:
|
dependencies:
|
||||||
|
|
Loading…
Reference in a new issue