Ensure head content rendered once with lazy layouts (#4892)
* Ensure head content rendered once with lazy layouts * Add changeset
This commit is contained in:
parent
649e9080cb
commit
ff7ba0ee0f
9 changed files with 59 additions and 3 deletions
5
.changeset/popular-horses-lie.md
Normal file
5
.changeset/popular-horses-lie.md
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
---
|
||||||
|
'astro': patch
|
||||||
|
---
|
||||||
|
|
||||||
|
Prevent multiple rendering of head content
|
|
@ -1212,6 +1212,7 @@ export interface SSRMetadata {
|
||||||
pathname: string;
|
pathname: string;
|
||||||
hasHydrationScript: boolean;
|
hasHydrationScript: boolean;
|
||||||
hasDirectives: Set<string>;
|
hasDirectives: Set<string>;
|
||||||
|
hasRenderedHead: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface SSRResult {
|
export interface SSRResult {
|
||||||
|
|
|
@ -270,6 +270,7 @@ const canonicalURL = new URL(Astro.url.pathname, Astro.site);
|
||||||
renderers,
|
renderers,
|
||||||
pathname,
|
pathname,
|
||||||
hasHydrationScript: false,
|
hasHydrationScript: false,
|
||||||
|
hasRenderedHead: false,
|
||||||
hasDirectives: new Set(),
|
hasDirectives: new Set(),
|
||||||
},
|
},
|
||||||
response,
|
response,
|
||||||
|
|
|
@ -12,9 +12,8 @@ const uniqueElements = (item: any, index: number, all: any[]) => {
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const alreadyHeadRenderedResults = new WeakSet<SSRResult>();
|
|
||||||
export function renderHead(result: SSRResult): Promise<string> {
|
export function renderHead(result: SSRResult): Promise<string> {
|
||||||
alreadyHeadRenderedResults.add(result);
|
result._metadata.hasRenderedHead = true;
|
||||||
const styles = Array.from(result.styles)
|
const styles = Array.from(result.styles)
|
||||||
.filter(uniqueElements)
|
.filter(uniqueElements)
|
||||||
.map((style) => renderElement('style', style));
|
.map((style) => renderElement('style', style));
|
||||||
|
@ -36,7 +35,7 @@ export function renderHead(result: SSRResult): Promise<string> {
|
||||||
// is called before a component's first non-head HTML element. If the head was
|
// is called before a component's first non-head HTML element. If the head was
|
||||||
// already injected it is a noop.
|
// already injected it is a noop.
|
||||||
export async function* maybeRenderHead(result: SSRResult): AsyncIterable<string> {
|
export async function* maybeRenderHead(result: SSRResult): AsyncIterable<string> {
|
||||||
if (alreadyHeadRenderedResults.has(result)) {
|
if (result._metadata.hasRenderedHead) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
yield renderHead(result);
|
yield renderHead(result);
|
||||||
|
|
6
packages/astro/test/fixtures/lazy-layout/package.json
vendored
Normal file
6
packages/astro/test/fixtures/lazy-layout/package.json
vendored
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
{
|
||||||
|
"name": "@test/lazy-layout",
|
||||||
|
"dependencies": {
|
||||||
|
"astro": "workspace:*"
|
||||||
|
}
|
||||||
|
}
|
13
packages/astro/test/fixtures/lazy-layout/src/layouts/Main.astro
vendored
Normal file
13
packages/astro/test/fixtures/lazy-layout/src/layouts/Main.astro
vendored
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<title>Testing</title>
|
||||||
|
<style>
|
||||||
|
body {
|
||||||
|
background: green;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<slot />
|
||||||
|
</body>
|
||||||
|
</html>
|
6
packages/astro/test/fixtures/lazy-layout/src/pages/index.astro
vendored
Normal file
6
packages/astro/test/fixtures/lazy-layout/src/pages/index.astro
vendored
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
---
|
||||||
|
const Layout = (await import('../layouts/Main.astro')).default;
|
||||||
|
---
|
||||||
|
<Layout>
|
||||||
|
<div>Stuff here</div>
|
||||||
|
</Layout>
|
19
packages/astro/test/lazy-layout.test.js
Normal file
19
packages/astro/test/lazy-layout.test.js
Normal file
|
@ -0,0 +1,19 @@
|
||||||
|
import { expect } from 'chai';
|
||||||
|
import * as cheerio from 'cheerio';
|
||||||
|
import { loadFixture } from './test-utils.js';
|
||||||
|
|
||||||
|
describe('Lazily imported layouts', () => {
|
||||||
|
/** @type {import('./test-utils').Fixture} */
|
||||||
|
let fixture;
|
||||||
|
|
||||||
|
before(async () => {
|
||||||
|
fixture = await loadFixture({ root: './fixtures/lazy-layout/' });
|
||||||
|
await fixture.build();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Renders styles only once', async () => {
|
||||||
|
const html = await fixture.readFile('/index.html');
|
||||||
|
const $ = cheerio.load(html);
|
||||||
|
expect($('link')).to.have.a.lengthOf(1);
|
||||||
|
});
|
||||||
|
});
|
|
@ -1664,6 +1664,12 @@ importers:
|
||||||
'@astrojs/solid-js': link:../../../../integrations/solid
|
'@astrojs/solid-js': link:../../../../integrations/solid
|
||||||
astro: link:../../..
|
astro: link:../../..
|
||||||
|
|
||||||
|
packages/astro/test/fixtures/lazy-layout:
|
||||||
|
specifiers:
|
||||||
|
astro: workspace:*
|
||||||
|
dependencies:
|
||||||
|
astro: link:../../..
|
||||||
|
|
||||||
packages/astro/test/fixtures/legacy-astro-flavored-markdown:
|
packages/astro/test/fixtures/legacy-astro-flavored-markdown:
|
||||||
specifiers:
|
specifiers:
|
||||||
'@astrojs/preact': workspace:*
|
'@astrojs/preact': workspace:*
|
||||||
|
|
Loading…
Reference in a new issue