Fix double injecting of head content in md pages (#4334)
* Fix double injecting of head content * Refactor * Changeset * Break into a separate util fn * fix oops * remove unused code
This commit is contained in:
parent
655d9840f8
commit
b55f76c1ca
10 changed files with 101 additions and 7 deletions
5
.changeset/hip-bobcats-divide.md
Normal file
5
.changeset/hip-bobcats-divide.md
Normal file
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
'astro': patch
|
||||
---
|
||||
|
||||
Fix double injecting of head content in md pages
|
|
@ -56,6 +56,10 @@ export function isAstroComponent(obj: any): obj is AstroComponent {
|
|||
);
|
||||
}
|
||||
|
||||
export function isAstroComponentFactory(obj: any): obj is AstroComponentFactory {
|
||||
return obj == null ? false : !!obj.isAstroComponentFactory;
|
||||
}
|
||||
|
||||
export async function* renderAstroComponent(
|
||||
component: InstanceType<typeof AstroComponent>
|
||||
): AsyncIterable<string | RenderInstruction> {
|
||||
|
|
|
@ -6,7 +6,7 @@ import { extractDirectives, generateHydrateScript } from '../hydration.js';
|
|||
import { serializeProps } from '../serialize.js';
|
||||
import { shorthash } from '../shorthash.js';
|
||||
import { renderSlot } from './any.js';
|
||||
import { renderAstroComponent, renderTemplate, renderToIterable } from './astro.js';
|
||||
import { isAstroComponentFactory, renderAstroComponent, renderTemplate, renderToIterable } from './astro.js';
|
||||
import { Fragment, Renderer } from './common.js';
|
||||
import { componentIsHTMLElement, renderHTMLElement } from './dom.js';
|
||||
import { formatList, internalSpreadAttributes, renderElement, voidElementNames } from './util.js';
|
||||
|
@ -37,7 +37,7 @@ function getComponentType(Component: unknown): ComponentType {
|
|||
if (Component && typeof Component === 'object' && (Component as any)['astro:html']) {
|
||||
return 'html';
|
||||
}
|
||||
if (Component && (Component as any).isAstroComponentFactory) {
|
||||
if (isAstroComponentFactory(Component)) {
|
||||
return 'astro-factory';
|
||||
}
|
||||
return 'unknown';
|
||||
|
|
|
@ -2,21 +2,34 @@ import type { SSRResult } from '../../../@types/astro';
|
|||
import type { AstroComponentFactory } from './index';
|
||||
|
||||
import { createResponse } from '../response.js';
|
||||
import { isAstroComponent, renderAstroComponent } from './astro.js';
|
||||
import { isAstroComponent, isAstroComponentFactory, renderAstroComponent } from './astro.js';
|
||||
import { stringifyChunk } from './common.js';
|
||||
import { renderComponent } from './component.js';
|
||||
import { maybeRenderHead } from './head.js';
|
||||
|
||||
const encoder = new TextEncoder();
|
||||
const needsHeadRenderingSymbol = Symbol.for('astro.needsHeadRendering');
|
||||
|
||||
type NonAstroPageComponent = {
|
||||
name: string;
|
||||
[needsHeadRenderingSymbol]: boolean;
|
||||
};
|
||||
|
||||
function nonAstroPageNeedsHeadInjection(pageComponent: NonAstroPageComponent): boolean {
|
||||
return (
|
||||
(needsHeadRenderingSymbol in pageComponent) &&
|
||||
!!pageComponent[needsHeadRenderingSymbol]
|
||||
);
|
||||
}
|
||||
|
||||
export async function renderPage(
|
||||
result: SSRResult,
|
||||
componentFactory: AstroComponentFactory,
|
||||
componentFactory: AstroComponentFactory | NonAstroPageComponent,
|
||||
props: any,
|
||||
children: any,
|
||||
streaming: boolean
|
||||
): Promise<Response> {
|
||||
if (!componentFactory.isAstroComponentFactory) {
|
||||
if (!isAstroComponentFactory(componentFactory)) {
|
||||
const pageProps: Record<string, any> = { ...(props ?? {}), 'server:root': true };
|
||||
const output = await renderComponent(
|
||||
result,
|
||||
|
@ -29,8 +42,13 @@ export async function renderPage(
|
|||
if (!/<!doctype html/i.test(html)) {
|
||||
let rest = html;
|
||||
html = `<!DOCTYPE html>`;
|
||||
for await (let chunk of maybeRenderHead(result)) {
|
||||
html += chunk;
|
||||
// This symbol currently exists for md components, but is something that could
|
||||
// be added for any page-level component that's not an Astro component.
|
||||
// to signal that head rendering is needed.
|
||||
if(nonAstroPageNeedsHeadInjection(componentFactory)) {
|
||||
for await (let chunk of maybeRenderHead(result)) {
|
||||
html += chunk;
|
||||
}
|
||||
}
|
||||
html += rest;
|
||||
}
|
||||
|
|
|
@ -117,6 +117,7 @@ export default function markdown({ config, logging }: AstroPluginOptions): Plugi
|
|||
: `contentFragment`
|
||||
};
|
||||
}
|
||||
Content[Symbol.for('astro.needsHeadRendering')] = ${layout ? 'false' : 'true'};
|
||||
export default Content;
|
||||
`);
|
||||
|
||||
|
|
8
packages/astro/test/fixtures/head-injection-md/package.json
vendored
Normal file
8
packages/astro/test/fixtures/head-injection-md/package.json
vendored
Normal file
|
@ -0,0 +1,8 @@
|
|||
{
|
||||
"name": "@test/head-injection-md",
|
||||
"version": "0.0.0",
|
||||
"private": true,
|
||||
"dependencies": {
|
||||
"astro": "workspace:*"
|
||||
}
|
||||
}
|
18
packages/astro/test/fixtures/head-injection-md/src/components/Layout.astro
vendored
Normal file
18
packages/astro/test/fixtures/head-injection-md/src/components/Layout.astro
vendored
Normal file
|
@ -0,0 +1,18 @@
|
|||
---
|
||||
const title = 'My Title';
|
||||
---
|
||||
<!DOCTYPE html>
|
||||
<html lang="en" class="dark">
|
||||
<head>
|
||||
<title set:html={title}></title>
|
||||
<meta name="theme-color" content="#ffffff" />
|
||||
<style>
|
||||
body {
|
||||
font-family: Arial;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body class="flex flex-col min-h-screen font-sans bg-indigo-50 dark:bg-slate-900">
|
||||
<slot />
|
||||
</body>
|
||||
</html>
|
7
packages/astro/test/fixtures/head-injection-md/src/pages/index.md
vendored
Normal file
7
packages/astro/test/fixtures/head-injection-md/src/pages/index.md
vendored
Normal file
|
@ -0,0 +1,7 @@
|
|||
---
|
||||
layout: ../components/Layout.astro
|
||||
---
|
||||
|
||||
# Heading
|
||||
|
||||
And content here.
|
27
packages/astro/test/head-injection-md.test.js
Normal file
27
packages/astro/test/head-injection-md.test.js
Normal file
|
@ -0,0 +1,27 @@
|
|||
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);
|
||||
});
|
||||
});
|
||||
});
|
|
@ -1531,6 +1531,12 @@ importers:
|
|||
dependencies:
|
||||
astro: link:../../..
|
||||
|
||||
packages/astro/test/fixtures/head-injection-md:
|
||||
specifiers:
|
||||
astro: workspace:*
|
||||
dependencies:
|
||||
astro: link:../../..
|
||||
|
||||
packages/astro/test/fixtures/hmr-css:
|
||||
specifiers:
|
||||
astro: workspace:*
|
||||
|
|
Loading…
Reference in a new issue