Skip to content

Commit

Permalink
Fix head injection misplacement with Astro.slots.render() (#6196)
Browse files Browse the repository at this point in the history
* Fix head injection misplacement with Astro.slots.render()

* Adding a changeset

* Fix case of JSX with no layout

* missing break
  • Loading branch information
matthewp authored Feb 9, 2023
1 parent 0c3485a commit 3390cb8
Show file tree
Hide file tree
Showing 19 changed files with 216 additions and 32 deletions.
5 changes: 5 additions & 0 deletions .changeset/thirty-bugs-film.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'astro': patch
---

Fix head injection misplacement with Astro.slots.render()
20 changes: 18 additions & 2 deletions packages/astro/src/runtime/server/render/common.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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');
Expand Down Expand Up @@ -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: {
Expand Down
4 changes: 4 additions & 0 deletions packages/astro/src/runtime/server/render/scope.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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: {
Expand Down
2 changes: 1 addition & 1 deletion packages/astro/src/runtime/server/render/slot.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<typeof renderTemplate>;
export type ComponentSlots = Record<string, ComponentSlotValue>;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{
"name": "@test/head-injection-md",
"name": "@test/head-injection",
"version": "0.0.0",
"private": true,
"dependencies": {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
<style>
div {
font-weight: bolder;
}
</style>
<div>
<slot />
</div>
Original file line number Diff line number Diff line change
@@ -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>
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
<!DOCTYPE html>
<html>
<head> </head>
<body>
<slot />
</body>
</html>
Original file line number Diff line number Diff line change
@@ -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>
Original file line number Diff line number Diff line change
@@ -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>
Original file line number Diff line number Diff line change
@@ -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>
Original file line number Diff line number Diff line change
@@ -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>
27 changes: 0 additions & 27 deletions packages/astro/test/head-injection-md.test.js

This file was deleted.

55 changes: 55 additions & 0 deletions packages/astro/test/head-injection.test.js
Original file line number Diff line number Diff line change
@@ -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);
});
});
});
});
13 changes: 13 additions & 0 deletions packages/integrations/mdx/test/css-head-mdx.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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);
});
});
});
Original file line number Diff line number Diff line change
@@ -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.
2 changes: 1 addition & 1 deletion pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

0 comments on commit 3390cb8

Please sign in to comment.