Skip to content
Closed
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/fix-viewtransitions-css-server-defer.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'astro': patch
---

Fixes `viewtransitions.css` being loaded on pages that use `server:defer` without any View Transition directives. The compiler incorrectly treats `server:defer` like a transition directive, causing the CSS to be imported. The CSS import is now stripped when no `transition:name`, `transition:animate`, or `transition:persist` directives are present in the source.
17 changes: 17 additions & 0 deletions packages/astro/src/core/compile/compile.ts
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,23 @@ export async function compile({

handleCompileResultErrors(transformResult, cssTransformErrors);

// The compiler incorrectly imports viewtransitions.css when `server:defer` is used,
// even without any transition directives. Strip the import if the source doesn't
// actually use transition:name, transition:animate, or transition:persist.
// See: https://github.com/withastro/astro/issues/15574
if (
transformResult.code.includes('astro/components/viewtransitions.css') &&
!/transition:(?:name|animate|persist)/.test(source)
) {
transformResult = {
...transformResult,
code: transformResult.code.replace(
/import\s*"astro\/components\/viewtransitions\.css";\n?/,
'',
),
};
}

return {
...transformResult,
css: transformResult.css.map((code, i) => ({
Expand Down
16 changes: 16 additions & 0 deletions packages/astro/test/server-islands.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,22 @@ describe('Server islands', () => {
assert.equal(serverIslandEl.length, 0);
});

it('does not include viewtransitions.css when not using ViewTransitions', async () => {
const res = await fixture.fetch('/');
assert.equal(res.status, 200);
const html = await res.text();
assert.equal(
html.includes('astroFadeInOut'),
false,
'should not include viewtransitions.css keyframes',
);
assert.equal(
html.includes('viewtransitions.css'),
false,
'should not reference viewtransitions.css',
);
});

it('HTML escapes scripts', async () => {
const res = await fixture.fetch('/');
assert.equal(res.status, 200);
Expand Down
116 changes: 116 additions & 0 deletions packages/astro/test/units/compile/viewtransitions-css.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
import * as assert from 'node:assert/strict';
import { describe, it } from 'node:test';
import { pathToFileURL } from 'node:url';
import { resolveConfig } from 'vite';
import { compile } from '../../../dist/core/compile/index.js';

describe('astro/src/core/compile', () => {
describe('ViewTransitions CSS import', () => {
it('does not include viewtransitions.css when only server:defer is used', async () => {
const result = await compile({
astroConfig: {
root: pathToFileURL('/'),
experimental: {},
},
viteConfig: await resolveConfig({ configFile: false }, 'serve'),
filename: '/src/pages/index.astro',
source: `---
import Island from '../components/Island.astro';
---
<html>
<head><title>Test</title></head>
<body>
<Island server:defer />
</body>
</html>
`,
});

assert.equal(
result.code.includes('viewtransitions.css'),
false,
'should not import viewtransitions.css when only server:defer is used',
);
});

it('includes viewtransitions.css when transition:name is used', async () => {
const result = await compile({
astroConfig: {
root: pathToFileURL('/'),
experimental: {},
},
viteConfig: await resolveConfig({ configFile: false }, 'serve'),
filename: '/src/pages/index.astro',
source: `---
---
<html>
<head><title>Test</title></head>
<body>
<div transition:name="hero">Hello</div>
</body>
</html>
`,
});

assert.equal(
result.code.includes('viewtransitions.css'),
true,
'should import viewtransitions.css when transition:name is used',
);
});

it('includes viewtransitions.css when transition:animate is used', async () => {
const result = await compile({
astroConfig: {
root: pathToFileURL('/'),
experimental: {},
},
viteConfig: await resolveConfig({ configFile: false }, 'serve'),
filename: '/src/pages/index.astro',
source: `---
---
<html>
<head><title>Test</title></head>
<body>
<div transition:animate="slide">Hello</div>
</body>
</html>
`,
});

assert.equal(
result.code.includes('viewtransitions.css'),
true,
'should import viewtransitions.css when transition:animate is used',
);
});

it('includes viewtransitions.css when server:defer and transition:name are both used', async () => {
const result = await compile({
astroConfig: {
root: pathToFileURL('/'),
experimental: {},
},
viteConfig: await resolveConfig({ configFile: false }, 'serve'),
filename: '/src/pages/index.astro',
source: `---
import Island from '../components/Island.astro';
---
<html>
<head><title>Test</title></head>
<body>
<Island server:defer />
<div transition:name="hero">Hello</div>
</body>
</html>
`,
});

assert.equal(
result.code.includes('viewtransitions.css'),
true,
'should import viewtransitions.css when transition directives are used alongside server:defer',
);
});
});
});
Loading