Skip to content
Open
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/big-forks-lead.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'astro': patch
---

Fixes an issue where CSS from unused components, when using content collections, could be incorrectly included between page navigations in development mode.
1 change: 1 addition & 0 deletions packages/astro/src/content/consts.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
export const PROPAGATED_ASSET_FLAG = 'astroPropagatedAssets';
export const PROPAGATED_ASSET_QUERY_PARAM = `?${PROPAGATED_ASSET_FLAG}`;
export const CONTENT_RENDER_FLAG = 'astroRenderContent';
export const CONTENT_FLAG = 'astroContentCollectionEntry';
export const DATA_FLAG = 'astroDataCollectionEntry';
Expand Down
3 changes: 2 additions & 1 deletion packages/astro/src/vite-plugin-astro-server/vite.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { type EnvironmentModuleNode, isCSSRequest, type RunnableDevEnvironment }
import { SUPPORTED_MARKDOWN_FILE_EXTENSIONS } from '../core/constants.js';
import { unwrapId } from '../core/util.js';
import { hasSpecialQueries } from '../vite-plugin-utils/index.js';
import { PROPAGATED_ASSET_QUERY_PARAM } from '../content/consts.js';

/**
* List of file extensions signalling we can (and should) SSR ahead-of-time
Expand Down Expand Up @@ -76,7 +77,7 @@ export async function* crawlGraph(
const isFileTypeNeedingSSR = fileExtensionsToSSR.has(npath.extname(importedModulePathname));
// A propagation stopping point is a module with the ?astroPropagatedAssets flag.
// When we encounter one of these modules we don't want to continue traversing.
const isPropagationStoppingPoint = importedModule.id.includes('?astroPropagatedAssets');
const isPropagationStoppingPoint = importedModule.id.includes(PROPAGATED_ASSET_QUERY_PARAM);
if (
isFileTypeNeedingSSR &&
// Should not SSR a module with ?astroPropagatedAssets
Expand Down
11 changes: 11 additions & 0 deletions packages/astro/src/vite-plugin-css/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import { inlineRE, isBuildableCSSRequest, rawRE } from '../vite-plugin-astro-ser
import { getVirtualModulePageNameForComponent } from '../vite-plugin-pages/util.js';
import { getDevCSSModuleName } from './util.js';
import { CSS_LANGS_RE } from '../core/viteUtils.js';
import { PROPAGATED_ASSET_QUERY_PARAM } from '../content/consts.js';

interface AstroVitePluginOptions {
routesList: RoutesList;
Expand Down Expand Up @@ -44,6 +45,16 @@ function* collectCSSWithOrder(
): Generator<ImportedDevStyle & { id: string; idKey: string }, void, unknown> {
seen.add(id);

// Stop traversing if we reach an asset propagation stopping point to ensure we only collect CSS
// relevant to a content collection entry, if any. Not doing so could cause CSS from other
// entries to potentially be collected and bleed into the CSS included on the page, causing
// unexpected styles, for example when a module shared between 2 pages would import
// `astro:content` and thus potentially adding multiple content collection entry assets to the
// module graph.
if (id.includes(PROPAGATED_ASSET_QUERY_PARAM)) {
return;
}

// Keep all of the imported modules into an array so we can go through them one at a time
const imported = Array.from(mod.importedModules);

Expand Down
20 changes: 20 additions & 0 deletions packages/astro/test/content-collections-render.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -218,5 +218,25 @@ describe('Content Collections - render()', () => {
assert.equal(h2.length, 1);
assert.equal(h2.attr('data-components-export-applied'), 'true');
});

it('Stops collecting CSS when reaching a propagation stopping point', async () => {
let response = await fixture.fetch('/blog/5-big-news', { method: 'GET' });
assert.equal(response.status, 200);

let html = await response.text();
let $ = cheerio.load(html);

// Includes the red button styles used in the MDX blog post
assert.ok($('head > style').text().includes('background-color:red;'));

response = await fixture.fetch('/blog/about', { method: 'GET' });
assert.equal(response.status, 200);

html = await response.text();
$ = cheerio.load(html);

// Does not include the red button styles not used in this page
assert.equal($('head > style').text().includes('background-color:red;'), false);
});
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
---
import BlogLayout from './BlogLayout.astro';
import { doSomething } from '../libs/routes';

// Calling a random function from a file importing `astro:content`.
doSomething()
---

<BlogLayout title="About Page"><slot /></BlogLayout>
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
---
interface Props {
title: string;
}

const { title } = Astro.props;
---

<html lang="en">
<head>
<meta charset="utf-8" />
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
<meta name="viewport" content="width=device-width" />
<meta name="generator" content={Astro.generator} />
<title>Astro</title>
</head>
<body>
<main>
<h1>{title}</h1>
<slot />
</main>
</body>
</html>
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
<button>Do something…</button>

<style>
button {
background-color: red;
color: white;
}
</style>
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
---
title: Big News
description: Just some big news.
---

This is the content of a blog post with big news.

import RedButton from "../../components/RedButton.astro";

<RedButton />
7 changes: 7 additions & 0 deletions packages/astro/test/fixtures/content/src/libs/routes.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import { getCollection } from "astro:content";

export const routes = await getCollection("blog");

export function doSomething() {
// …
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
---
import { getEntry, render } from "astro:content";
import BlogLayout from "../../components/BlogLayout.astro";
import { routes } from "../../libs/routes";

export function getStaticPaths() {
return [routes.map((route) => ({ params: { slug: route.id } }))].flat();
}

const { slug } = Astro.params;

const entry = await getEntry("blog", slug);

if (!entry) {
throw new Error(`Entry not found for slug: ${slug}`);
}

const { Content } = await render(entry)
---

<BlogLayout title="Article Page">
<Content />
</BlogLayout>
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
import AboutLayout from "../../components/AboutLayout.astro";
---

<AboutLayout>
<p>This is the content of the about page.</p>
</AboutLayout>