diff --git a/.changeset/four-doors-trade.md b/.changeset/four-doors-trade.md new file mode 100644 index 000000000000..592c3fea9013 --- /dev/null +++ b/.changeset/four-doors-trade.md @@ -0,0 +1,5 @@ +--- +'astro': patch +--- + +Fix CSS traversal boundaries so pages with `export const partial = true` still contribute styles when imported as components by other pages. diff --git a/packages/astro/src/core/build/plugins/plugin-css.ts b/packages/astro/src/core/build/plugins/plugin-css.ts index 6f050eb349db..2a833c1209f4 100644 --- a/packages/astro/src/core/build/plugins/plugin-css.ts +++ b/packages/astro/src/core/build/plugins/plugin-css.ts @@ -3,6 +3,7 @@ import type { BuildOptions, ResolvedConfig, Plugin as VitePlugin } from 'vite'; import { isCSSRequest } from 'vite'; import { ASTRO_VITE_ENVIRONMENT_NAMES } from '../../constants.js'; import { isPropagatedAssetBoundary } from '../../head-propagation/boundary.js'; +import { VIRTUAL_PAGE_RESOLVED_MODULE_ID } from '../../../vite-plugin-pages/const.js'; import { getParentExtendedModuleInfos, getParentModuleInfos, @@ -33,7 +34,14 @@ interface PluginOptions { function isBuildCssBoundary(id: string, ctx: { getModuleInfo: GetModuleInfo }): boolean { if (isPropagatedAssetBoundary(id)) return true; const info = ctx.getModuleInfo(id); - return info ? moduleIsTopLevelPage(info) : false; + if (!info || !moduleIsTopLevelPage(info)) return false; + const allImporters = info.importers.concat(info.dynamicImporters); + const hasNonVirtualPageImporter = allImporters.some( + (importer) => !importer.includes(VIRTUAL_PAGE_RESOLVED_MODULE_ID), + ); + // Pages imported by non-virtual modules (e.g. partials imported by other pages) + // should propagate CSS transitively instead of acting as hard page boundaries. + return !hasNonVirtualPageImporter; } function rollupPluginAstroBuildCSS(options: PluginOptions): VitePlugin[] { diff --git a/packages/astro/test/fixtures/partials-css-boundary/astro.config.mjs b/packages/astro/test/fixtures/partials-css-boundary/astro.config.mjs new file mode 100644 index 000000000000..86dbfb924824 --- /dev/null +++ b/packages/astro/test/fixtures/partials-css-boundary/astro.config.mjs @@ -0,0 +1,3 @@ +import { defineConfig } from 'astro/config'; + +export default defineConfig({}); diff --git a/packages/astro/test/fixtures/partials-css-boundary/package.json b/packages/astro/test/fixtures/partials-css-boundary/package.json new file mode 100644 index 000000000000..5dac595aa12c --- /dev/null +++ b/packages/astro/test/fixtures/partials-css-boundary/package.json @@ -0,0 +1,8 @@ +{ + "name": "@test/partials-css-boundary", + "version": "0.0.0", + "private": true, + "dependencies": { + "astro": "workspace:*" + } +} diff --git a/packages/astro/test/fixtures/partials-css-boundary/src/components/ResultsTable.astro b/packages/astro/test/fixtures/partials-css-boundary/src/components/ResultsTable.astro new file mode 100644 index 000000000000..e1196fe74463 --- /dev/null +++ b/packages/astro/test/fixtures/partials-css-boundary/src/components/ResultsTable.astro @@ -0,0 +1,13 @@ + + + + + + +
Alpha
+ + diff --git a/packages/astro/test/fixtures/partials-css-boundary/src/pages/index.astro b/packages/astro/test/fixtures/partials-css-boundary/src/pages/index.astro new file mode 100644 index 000000000000..2d6f59353b15 --- /dev/null +++ b/packages/astro/test/fixtures/partials-css-boundary/src/pages/index.astro @@ -0,0 +1,8 @@ +--- +import SearchResults from './partials/search-results/index.astro'; +--- + +
+

Home

+ +
diff --git a/packages/astro/test/fixtures/partials-css-boundary/src/pages/partials/search-results/index.astro b/packages/astro/test/fixtures/partials-css-boundary/src/pages/partials/search-results/index.astro new file mode 100644 index 000000000000..0756cefb18ef --- /dev/null +++ b/packages/astro/test/fixtures/partials-css-boundary/src/pages/partials/search-results/index.astro @@ -0,0 +1,7 @@ +--- +import ResultsTable from '../../../components/ResultsTable.astro'; + +export const partial = true; +--- + + diff --git a/packages/astro/test/partials-css-boundary.test.js b/packages/astro/test/partials-css-boundary.test.js new file mode 100644 index 000000000000..3a76f470b479 --- /dev/null +++ b/packages/astro/test/partials-css-boundary.test.js @@ -0,0 +1,38 @@ +import assert from 'node:assert/strict'; +import { before, describe, it } from 'node:test'; +import * as cheerio from 'cheerio'; +import { loadFixture } from './test-utils.js'; + +describe('Partials CSS propagation', () => { + /** @type {import('./test-utils.js').Fixture} */ + let fixture; + + before(async () => { + fixture = await loadFixture({ + root: './fixtures/partials-css-boundary/', + outDir: './dist/partials-css-boundary/', + }); + await fixture.build(); + }); + + it('includes transitive CSS from partials imported as components in build output', async () => { + const html = await fixture.readFile('/index.html'); + const $ = cheerio.load(html); + let styles = $('style') + .toArray() + .map((el) => $(el).text()) + .join('\n'); + + const stylesheetHrefs = $('link[rel="stylesheet"]') + .toArray() + .map((el) => $(el).attr('href')) + .filter((href) => href && href.startsWith('/_astro/')); + + for (const href of stylesheetHrefs) { + styles += `\n${await fixture.readFile(href)}`; + } + + assert.match(styles, /\.results-table\[data-astro-cid-/); + assert.match(html, /results-table/); + }); +}); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 0f2b301179e0..22b81d2824f9 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -3577,6 +3577,12 @@ importers: specifier: workspace:* version: link:../../.. + packages/astro/test/fixtures/partials-css-boundary: + dependencies: + astro: + specifier: workspace:* + version: link:../../.. + packages/astro/test/fixtures/passthrough-image-service: dependencies: astro: