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

Fix head injection misplacement with Astro.slots.render() #6196

Merged
merged 4 commits into from
Feb 9, 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/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.