Skip to content

Commit

Permalink
Fix CSS chunking between multiple framework components (#6582)
Browse files Browse the repository at this point in the history
* Fix CSS chunking between multiple framework components

* Better CSS dedupe handling

* Add more tests

* Update docs
  • Loading branch information
bluwy committed Apr 12, 2023
1 parent 76dd53e commit 7653cf9
Show file tree
Hide file tree
Showing 16 changed files with 115 additions and 16 deletions.
5 changes: 5 additions & 0 deletions .changeset/violet-islands-buy.md
@@ -0,0 +1,5 @@
---
'astro': patch
---

Fix CSS chunking and deduping between multiple Astro files and framework components
24 changes: 13 additions & 11 deletions packages/astro/src/core/build/plugins/plugin-css.ts
Expand Up @@ -55,7 +55,20 @@ export function rollupPluginAstroBuildCSS(options: PluginOptions): VitePlugin[]
{
name: 'astro:rollup-plugin-build-css',

transform(_, id) {
// In the SSR build, styles that are bundled are tracked in `internals.cssChunkModuleIds`.
// In the client build, if we're also bundling the same style, return an empty string to
// deduplicate the final CSS output.
if (options.target === 'client' && internals.cssChunkModuleIds.has(id)) {
return '';
}
},

outputOptions(outputOptions) {
// Skip in client builds as its module graph doesn't have reference to Astro pages
// to be able to chunk based on it's related top-level pages.
if (options.target === 'client') return;

This comment has been minimized.

Copy link
@vampaz

vampaz Apr 14, 2023

@bluwy
This ln is breaking Vue component styles
When client:only="vue" the styles for the child components will fail to be included, but not for all, still trying to figure out the exact conditions.

This comment has been minimized.

Copy link
@vampaz

vampaz Apr 14, 2023

Sorry, not this one, seem to be 62

This comment has been minimized.

Copy link
@bluwy

bluwy Apr 14, 2023

Author Member

@vampaz I think I have a clue how this breaks for your case, but it would be great if you can open a new issue with a repro too so we can test this. Thanks!

This comment has been minimized.

Copy link
@bluwy

bluwy Jun 8, 2023

Author Member

@vampaz FYI this should be fixed in #7218 (astro v2.5.6)


const assetFileNames = outputOptions.assetFileNames;
const namingIncludesHash = assetFileNames?.toString().includes('[hash]');
const createNameForParentPages = namingIncludesHash
Expand Down Expand Up @@ -133,17 +146,6 @@ export function rollupPluginAstroBuildCSS(options: PluginOptions): VitePlugin[]
internals.cssChunkModuleIds.add(id);
}
}
// In the client build, we bail if the chunk is a duplicated CSS chunk tracked from
// above. We remove all the importedCss to prevent emitting the CSS asset.
if (options.target === 'client') {
if (Object.keys(c.modules).every((id) => internals.cssChunkModuleIds.has(id))) {
for (const importedCssImport of meta.importedCss) {
delete bundle[importedCssImport];
meta.importedCss.delete(importedCssImport);
}
return;
}
}

// For the client build, client:only styles need to be mapped
// over to their page. For this chunk, determine if it's a child of a
Expand Down
16 changes: 12 additions & 4 deletions packages/astro/test/astro-client-only.test.js
Expand Up @@ -28,8 +28,12 @@ describe('Client only components', () => {
const html = await fixture.readFile('/index.html');
const $ = cheerioLoad(html);

const href = $('link[rel=stylesheet]').attr('href');
const css = await fixture.readFile(href);
const stylesheets = await Promise.all(
$('link[rel=stylesheet]').map((_, el) => {
return fixture.readFile(el.attribs.href);
})
);
const css = stylesheets.join('');

expect(css).to.match(/yellowgreen/, 'Svelte styles are added');
expect(css).to.match(/Courier New/, 'Global styles are added');
Expand Down Expand Up @@ -87,8 +91,12 @@ describe('Client only components subpath', () => {
const html = await fixture.readFile('/index.html');
const $ = cheerioLoad(html);

const href = $('link[rel=stylesheet]').attr('href');
const css = await fixture.readFile(href.replace(/\/blog/, ''));
const stylesheets = await Promise.all(
$('link[rel=stylesheet]').map((_, el) => {
return fixture.readFile(el.attribs.href.replace(/\/blog/, ''));
})
);
const css = stylesheets.join('');

expect(css).to.match(/yellowgreen/, 'Svelte styles are added');
expect(css).to.match(/Courier New/, 'Global styles are added');
Expand Down
19 changes: 19 additions & 0 deletions packages/astro/test/css-order-import.test.js
Expand Up @@ -106,6 +106,25 @@ describe('CSS ordering - import order', () => {
expect(idx1).to.be.greaterThan(idx2);
expect(idx2).to.be.greaterThan(idx3);
});

it('correctly chunks css import from framework components', async () => {
let html = await fixture.readFile('/index.html');

const content = await Promise.all(getLinks(html).map((href) => getLinkContent(href)));
const [, { css }] = content;
expect(css).to.not.include(
'.client-1{background:red!important}',
'CSS from Client2.jsx leaked into index.astro when chunking'
);
});

it('dedupe css between astro and framework components', async () => {
let html = await fixture.readFile('/dedupe/index.html');

const content = await Promise.all(getLinks(html).map((href) => getLinkContent(href)));
const css = content.map((c) => c.css).join('');
expect(css.match(/\.astro-jsx/)?.length).to.eq(1, '.astro-jsx class is duplicated');
});
});

describe('Dynamic import', () => {
Expand Down
@@ -0,0 +1,7 @@
import { defineConfig } from 'astro/config';
import react from '@astrojs/react'

// https://astro.build/config
export default defineConfig({
integrations: [react()]
});
5 changes: 4 additions & 1 deletion packages/astro/test/fixtures/css-order-import/package.json
@@ -1,6 +1,9 @@
{
"name": "@test/css-order-import",
"dependencies": {
"astro": "workspace:*"
"@astrojs/react": "workspace:*",
"astro": "workspace:*",
"react": "^18.1.0",
"react-dom": "^18.1.0"
}
}
@@ -0,0 +1,5 @@
import "../styles/Client1.css"

export default function Client() {
return <div className="client-1">Client 1</div>;
}
@@ -0,0 +1,5 @@
import "../styles/Client2.css"

export default function Client() {
return <div className="client-2">Client 2</div>;
}
@@ -0,0 +1,5 @@
import '../styles/AstroJsx.css';

export default function Dedupe() {
return <div className="astro-jsx">Dedupe Astro JSX</div>;
}
@@ -1,6 +1,7 @@
---
import One from '../components/One.astro';
import Two from '../components/Two.astro';
import Client2 from '../components/Client2.jsx';
---
<html>
<head>
Expand All @@ -15,5 +16,6 @@ import Two from '../components/Two.astro';
</head>
<body>
<h1>Test</h1>
<Client2 client:only="react" />
</body>
</html>
@@ -0,0 +1,13 @@
---
import '../styles/AstroJsx.css';
import Dedupe from '../components/Dedupe.jsx';
---
<html>
<head>
<title>Test</title>
</head>
<body>
<h1>Test</h1>
<Dedupe client:only="react" />
</body>
</html>
@@ -1,5 +1,6 @@
---
import '../styles/base.css';
import Client1 from '../components/Client1.jsx';
---
<html>
<head>
Expand All @@ -12,5 +13,6 @@ import '../styles/base.css';
</head>
<body>
<h1>Test</h1>
<Client1 client:only="react" />
</body>
</html>
@@ -0,0 +1,4 @@
/* this css is imported in both .astro and .jsx component, make sure CSS is deduped */
.astro-jsx {
color: red;
}
@@ -0,0 +1,3 @@
.client-1 {
background: green;
}
@@ -0,0 +1,10 @@
/*
This cheeky css tries to affect index.astro, the good thing is that the Client2.jsx component is
only loaded in component.astro. So index.astro's Client1.jsx component should not be affected by this.
*/
.client-1 {
background: red !important;
}
.client-2 {
background: green;
}
6 changes: 6 additions & 0 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

0 comments on commit 7653cf9

Please sign in to comment.