From 3390cb84443a43eb997f3efeb5ca298a8477aaf0 Mon Sep 17 00:00:00 2001 From: Matthew Phillips Date: Thu, 9 Feb 2023 11:50:04 -0500 Subject: [PATCH] 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 --- .changeset/thirty-bugs-film.md | 5 ++ .../astro/src/runtime/server/render/common.ts | 20 ++++++- .../astro/src/runtime/server/render/scope.ts | 4 ++ .../astro/src/runtime/server/render/slot.ts | 2 +- .../package.json | 2 +- .../src/components/Layout.astro | 0 .../src/components/RegularSlot.astro | 8 +++ .../src/components/SlotRenderComponent.astro | 12 ++++ .../src/components/SlotRenderLayout.astro | 7 +++ .../src/components/SlotsRender.astro | 25 +++++++++ .../src/pages/index.md | 0 .../src/pages/with-slot-in-render-slot.astro | 24 ++++++++ .../src/pages/with-slot-in-slot.astro | 11 ++++ .../src/pages/with-slot-render.astro | 9 +++ packages/astro/test/head-injection-md.test.js | 27 --------- packages/astro/test/head-injection.test.js | 55 +++++++++++++++++++ .../mdx/test/css-head-mdx.test.js | 13 +++++ .../src/pages/noLayoutWithComponent.mdx | 22 ++++++++ pnpm-lock.yaml | 2 +- 19 files changed, 216 insertions(+), 32 deletions(-) create mode 100644 .changeset/thirty-bugs-film.md rename packages/astro/test/fixtures/{head-injection-md => head-injection}/package.json (72%) rename packages/astro/test/fixtures/{head-injection-md => head-injection}/src/components/Layout.astro (100%) create mode 100644 packages/astro/test/fixtures/head-injection/src/components/RegularSlot.astro create mode 100644 packages/astro/test/fixtures/head-injection/src/components/SlotRenderComponent.astro create mode 100644 packages/astro/test/fixtures/head-injection/src/components/SlotRenderLayout.astro create mode 100644 packages/astro/test/fixtures/head-injection/src/components/SlotsRender.astro rename packages/astro/test/fixtures/{head-injection-md => head-injection}/src/pages/index.md (100%) create mode 100644 packages/astro/test/fixtures/head-injection/src/pages/with-slot-in-render-slot.astro create mode 100644 packages/astro/test/fixtures/head-injection/src/pages/with-slot-in-slot.astro create mode 100644 packages/astro/test/fixtures/head-injection/src/pages/with-slot-render.astro delete mode 100644 packages/astro/test/head-injection-md.test.js create mode 100644 packages/astro/test/head-injection.test.js create mode 100644 packages/integrations/mdx/test/fixtures/css-head-mdx/src/pages/noLayoutWithComponent.mdx diff --git a/.changeset/thirty-bugs-film.md b/.changeset/thirty-bugs-film.md new file mode 100644 index 000000000000..9a8ba2cc982e --- /dev/null +++ b/.changeset/thirty-bugs-film.md @@ -0,0 +1,5 @@ +--- +'astro': patch +--- + +Fix head injection misplacement with Astro.slots.render() diff --git a/packages/astro/src/runtime/server/render/common.ts b/packages/astro/src/runtime/server/render/common.ts index 2750c886f309..484f574c6fca 100644 --- a/packages/astro/src/runtime/server/render/common.ts +++ b/packages/astro/src/runtime/server/render/common.ts @@ -9,7 +9,7 @@ import { PrescriptType, } from '../scripts.js'; import { renderAllHeadContent } from './head.js'; -import { ScopeFlags } from './scope.js'; +import { hasScopeFlag, ScopeFlags } from './scope.js'; import { isSlotString, type SlotString } from './slot.js'; export const Fragment = Symbol.for('astro:fragment'); @@ -63,7 +63,23 @@ export function stringifyChunk(result: SSRResult, chunk: string | SlotString | R 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 | ScopeFlags.JSX: case ScopeFlags.RenderSlot | ScopeFlags.Astro | ScopeFlags.JSX | ScopeFlags.HeadBuffer: { diff --git a/packages/astro/src/runtime/server/render/scope.ts b/packages/astro/src/runtime/server/render/scope.ts index 43cf80b51f16..fce96c0e79fc 100644 --- a/packages/astro/src/runtime/server/render/scope.ts +++ b/packages/astro/src/runtime/server/render/scope.ts @@ -18,6 +18,10 @@ export function removeScopeFlag(result: SSRResult, flag: ScopeFlagValues) { result.scope &= ~flag; } +export function hasScopeFlag(result: SSRResult, flag: ScopeFlagValues) { + return (result.scope & flag) === flag; +} + export function createScopedResult(result: SSRResult, flag?: ScopeFlagValues): SSRResult { const scopedResult = Object.create(result, { scope: { diff --git a/packages/astro/src/runtime/server/render/slot.ts b/packages/astro/src/runtime/server/render/slot.ts index 5e21d1e07c3d..b0e8a0fa72ce 100644 --- a/packages/astro/src/runtime/server/render/slot.ts +++ b/packages/astro/src/runtime/server/render/slot.ts @@ -4,7 +4,7 @@ import type { RenderInstruction } from './types.js'; import { HTMLString, markHTMLString } from '../escape.js'; import { renderChild } from './any.js'; -import { createScopedResult, ScopeFlags } from './scope.js'; +import { createScopedResult, hasScopeFlag, ScopeFlags } from './scope.js'; type RenderTemplateResult = ReturnType; export type ComponentSlots = Record; diff --git a/packages/astro/test/fixtures/head-injection-md/package.json b/packages/astro/test/fixtures/head-injection/package.json similarity index 72% rename from packages/astro/test/fixtures/head-injection-md/package.json rename to packages/astro/test/fixtures/head-injection/package.json index d2f2c67781c2..82455011aecd 100644 --- a/packages/astro/test/fixtures/head-injection-md/package.json +++ b/packages/astro/test/fixtures/head-injection/package.json @@ -1,5 +1,5 @@ { - "name": "@test/head-injection-md", + "name": "@test/head-injection", "version": "0.0.0", "private": true, "dependencies": { diff --git a/packages/astro/test/fixtures/head-injection-md/src/components/Layout.astro b/packages/astro/test/fixtures/head-injection/src/components/Layout.astro similarity index 100% rename from packages/astro/test/fixtures/head-injection-md/src/components/Layout.astro rename to packages/astro/test/fixtures/head-injection/src/components/Layout.astro diff --git a/packages/astro/test/fixtures/head-injection/src/components/RegularSlot.astro b/packages/astro/test/fixtures/head-injection/src/components/RegularSlot.astro new file mode 100644 index 000000000000..cec06fe2f361 --- /dev/null +++ b/packages/astro/test/fixtures/head-injection/src/components/RegularSlot.astro @@ -0,0 +1,8 @@ + +
+ +
diff --git a/packages/astro/test/fixtures/head-injection/src/components/SlotRenderComponent.astro b/packages/astro/test/fixtures/head-injection/src/components/SlotRenderComponent.astro new file mode 100644 index 000000000000..d8756fff54d7 --- /dev/null +++ b/packages/astro/test/fixtures/head-injection/src/components/SlotRenderComponent.astro @@ -0,0 +1,12 @@ +--- +const html = await Astro.slots.render('slot-name'); +--- +
+ +
+ + diff --git a/packages/astro/test/fixtures/head-injection/src/components/SlotRenderLayout.astro b/packages/astro/test/fixtures/head-injection/src/components/SlotRenderLayout.astro new file mode 100644 index 000000000000..efef491a0649 --- /dev/null +++ b/packages/astro/test/fixtures/head-injection/src/components/SlotRenderLayout.astro @@ -0,0 +1,7 @@ + + + + + + + diff --git a/packages/astro/test/fixtures/head-injection/src/components/SlotsRender.astro b/packages/astro/test/fixtures/head-injection/src/components/SlotsRender.astro new file mode 100644 index 000000000000..85a57916e497 --- /dev/null +++ b/packages/astro/test/fixtures/head-injection/src/components/SlotsRender.astro @@ -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; +--- + + +
+
+ {title &&

{title}

} + {subtitle &&

} + {content &&

} +
+
diff --git a/packages/astro/test/fixtures/head-injection-md/src/pages/index.md b/packages/astro/test/fixtures/head-injection/src/pages/index.md similarity index 100% rename from packages/astro/test/fixtures/head-injection-md/src/pages/index.md rename to packages/astro/test/fixtures/head-injection/src/pages/index.md diff --git a/packages/astro/test/fixtures/head-injection/src/pages/with-slot-in-render-slot.astro b/packages/astro/test/fixtures/head-injection/src/pages/with-slot-in-render-slot.astro new file mode 100644 index 000000000000..e3c2975e27ed --- /dev/null +++ b/packages/astro/test/fixtures/head-injection/src/pages/with-slot-in-render-slot.astro @@ -0,0 +1,24 @@ +--- +import Layout from '../components/Layout.astro'; +import SlotsRender from '../components/SlotsRender.astro'; +--- + + + + +

+ 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. +

+
+
+
diff --git a/packages/astro/test/fixtures/head-injection/src/pages/with-slot-in-slot.astro b/packages/astro/test/fixtures/head-injection/src/pages/with-slot-in-slot.astro new file mode 100644 index 000000000000..85b228c43696 --- /dev/null +++ b/packages/astro/test/fixtures/head-injection/src/pages/with-slot-in-slot.astro @@ -0,0 +1,11 @@ +--- +import Layout from "../components/SlotRenderLayout.astro"; +import RegularSlot from "../components/RegularSlot.astro" +--- + + + +

Paragraph.

+
+
+
diff --git a/packages/astro/test/fixtures/head-injection/src/pages/with-slot-render.astro b/packages/astro/test/fixtures/head-injection/src/pages/with-slot-render.astro new file mode 100644 index 000000000000..337b4a95cff8 --- /dev/null +++ b/packages/astro/test/fixtures/head-injection/src/pages/with-slot-render.astro @@ -0,0 +1,9 @@ +--- +import Layout from "../components/SlotRenderLayout.astro"; +import Component from "../components/SlotRenderComponent.astro" +--- + + +

Paragraph.

+
+
diff --git a/packages/astro/test/head-injection-md.test.js b/packages/astro/test/head-injection-md.test.js deleted file mode 100644 index 1acd5e83bd77..000000000000 --- a/packages/astro/test/head-injection-md.test.js +++ /dev/null @@ -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); - }); - }); -}); diff --git a/packages/astro/test/head-injection.test.js b/packages/astro/test/head-injection.test.js new file mode 100644 index 000000000000..6f6010773339 --- /dev/null +++ b/packages/astro/test/head-injection.test.js @@ -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); + }); + }); + }); +}); diff --git a/packages/integrations/mdx/test/css-head-mdx.test.js b/packages/integrations/mdx/test/css-head-mdx.test.js index c38f23701d34..82a86e5d23e6 100644 --- a/packages/integrations/mdx/test/css-head-mdx.test.js +++ b/packages/integrations/mdx/test/css-head-mdx.test.js @@ -3,6 +3,7 @@ import mdx from '@astrojs/mdx'; import { expect } from 'chai'; import { parseHTML } from 'linkedom'; import { loadFixture } from '../../../astro/test/test-utils.js'; +import * as cheerio from 'cheerio'; describe('Head injection w/ MDX', () => { let fixture; @@ -56,5 +57,17 @@ describe('Head injection w/ MDX', () => { const links = document.querySelectorAll('head link[rel=stylesheet]'); 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); + }); }); }); diff --git a/packages/integrations/mdx/test/fixtures/css-head-mdx/src/pages/noLayoutWithComponent.mdx b/packages/integrations/mdx/test/fixtures/css-head-mdx/src/pages/noLayoutWithComponent.mdx new file mode 100644 index 000000000000..9d799d4dba4f --- /dev/null +++ b/packages/integrations/mdx/test/fixtures/css-head-mdx/src/pages/noLayoutWithComponent.mdx @@ -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. + + + +## 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. diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 7c65389599d6..daf8f0ecf684 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -1849,7 +1849,7 @@ importers: dependencies: astro: link:../../.. - packages/astro/test/fixtures/head-injection-md: + packages/astro/test/fixtures/head-injection: specifiers: astro: workspace:* dependencies: