Skip to content

Commit dbbf10e

Browse files
astro-factory[bot]factory[bot]matthewp
authored
Preserve Vite module IDs through content asset propagation to fix CSS HMR after ClientRouter navigation (#17953)
* fix(content): preserve Vite module ID in content entry CSS propagation for HMR * test(render): fix SSRResult typing in renderUniqueStylesheet unit test The test result mock only provided { styles }, which does not satisfy SSRResult and broke tsc -b during the build. Cast the mock so the test typechecks. * test(content): cover content-propagation CSS HMR after ClientRouter navigation Adds an e2e test that navigates away and back with ClientRouter, then edits a Svelte component rendered through a content entry and verifies the change applies via HMR without a full reload. The load counter starts after the initial page settles so Vite's one-time cold-start re-optimization reload is not counted against the navigation flow. * fix(dev): include MDX files in the optimizeDeps scan Framework components imported from MDX content entries are only reachable through the virtual astro:content module, which Vite's dep scanner cannot follow, so their framework dependencies were never pre-bundled at cold start. When the browser then requested them at runtime, Vite re-optimized and sent a full-reload, reloading the page once shortly after the first load. Add mdx to optimizeDeps.entries in both the runnable and client environments, mirroring the .astro entry added in #16480. Plain .md files cannot import components, so only .mdx needs scanning. --------- Co-authored-by: factory[bot] <factory[bot]@users.noreply.github.com> Co-authored-by: Matthew Phillips <matthew@matthewphillips.info>
1 parent 2637ed1 commit dbbf10e

17 files changed

Lines changed: 170 additions & 8 deletions

File tree

‎.changeset/optimize-deps-mdx.md‎

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'astro': patch
3+
---
4+
5+
Fixes a one-time page reload shortly after the first load on cold dev-server starts when a project has framework components imported from MDX content entries. MDX files are now included in the dev dependency pre-bundling scan, so their framework dependencies are bundled up front instead of being discovered (and reloaded for) at runtime.

‎.changeset/swift-steaks-cheat.md‎

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'astro': patch
3+
---
4+
5+
Fixes CSS HMR for framework components rendered through content entries after `ClientRouter` navigation

‎packages/astro/e2e/content-collections.test.ts‎

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,4 +26,32 @@ test.describe('Content Collections', () => {
2626

2727
await expect(h1, 'should have green color').toHaveCSS('color', 'rgb(0, 128, 0)');
2828
});
29+
30+
test('framework styles keep HMR after returning to a content route', async ({ page, astro }) => {
31+
await page.goto(astro.resolveUrl('/'));
32+
// A cold dev-server start can trigger a one-time full reload while Vite
33+
// re-optimizes late-discovered dependencies. That reload is unrelated to
34+
// ClientRouter, so wait for it to settle before marking style nodes and
35+
// counting loads.
36+
await page.waitForTimeout(1000);
37+
38+
let loads = 0;
39+
page.on('load', () => loads++);
40+
const message = page.locator('.svelte-message');
41+
await expect(message).toHaveCSS('background-color', 'rgb(128, 0, 0)');
42+
const messageStyle = page.locator('style[data-vite-dev-id*="SvelteMessage.svelte"]');
43+
await expect(messageStyle).toHaveCount(1);
44+
await messageStyle.evaluate((element) => (element.dataset.hmrStyle = 'svelte-message'));
45+
await page.click('#away');
46+
await page.click('#back');
47+
await expect(message).toBeVisible();
48+
await expect(page.locator('style[data-hmr-style="svelte-message"]')).toHaveCount(1);
49+
50+
await astro.editFile('./src/components/SvelteMessage.svelte', (original) =>
51+
original.replace('background-color: maroon', 'background-color: navy'),
52+
);
53+
54+
await expect(message).toHaveCSS('background-color', 'rgb(0, 0, 128)');
55+
expect(loads).toBe(0);
56+
});
2957
});
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
11
import mdx from '@astrojs/mdx';
2+
import svelte from '@astrojs/svelte';
23
import { defineConfig } from 'astro/config';
34

45
// https://astro.build/config
56
export default defineConfig({
67
integrations: [
78
mdx(),
9+
svelte(),
810
],
911
});

‎packages/astro/e2e/fixtures/content-collections/package.json‎

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@
44
"private": true,
55
"dependencies": {
66
"@astrojs/mdx": "workspace:*",
7-
"astro": "workspace:*"
7+
"@astrojs/svelte": "workspace:*",
8+
"astro": "workspace:*",
9+
"svelte": "^5.54.0"
810
}
911
}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
<p class="svelte-message">Svelte message</p>
2+
3+
<style>
4+
.svelte-message {
5+
background-color: maroon;
6+
}
7+
</style>

‎packages/astro/e2e/fixtures/content-collections/src/content/posts/post-1.mdx‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,5 +2,7 @@
22
---
33

44
import MyComponent from '../../components/MyComponent.astro';
5+
import SvelteMessage from '../../components/SvelteMessage.svelte';
56

67
<MyComponent />
8+
<SvelteMessage client:load />
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
---
2+
import { ClientRouter } from 'astro:transitions';
3+
---
4+
5+
<html>
6+
<head>
7+
<ClientRouter />
8+
</head>
9+
<body>
10+
<a id="back" href="/">Back</a>
11+
</body>
12+
</html>
Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,17 @@
11
---
22
import { getEntry, render } from 'astro:content'
3+
import { ClientRouter } from 'astro:transitions'
34
45
const post = await getEntry('posts', 'post-1')
56
const { Content } = await render(post);
67
---
78

8-
<Content/>
9+
<html>
10+
<head>
11+
<ClientRouter />
12+
</head>
13+
<body>
14+
<a id="away" href="/away">Away</a>
15+
<Content />
16+
</body>
17+
</html>

‎packages/astro/src/content/runtime.ts‎

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -690,9 +690,12 @@ async function render({
690690
if (Array.isArray(collectedStyles)) {
691691
styles = collectedStyles
692692
.map((style: any) => {
693+
const content = typeof style === 'string' ? style : style.content;
694+
const viteDevId = typeof style === 'object' && style.id ? style.id : undefined;
693695
return renderUniqueStylesheet(result, {
694696
type: 'inline',
695-
content: style,
697+
content,
698+
viteDevId,
696699
});
697700
})
698701
.join('');

0 commit comments

Comments
 (0)