diff --git a/.changeset/soft-bags-flash.md b/.changeset/soft-bags-flash.md
new file mode 100644
index 000000000000..8fa979cc0e93
--- /dev/null
+++ b/.changeset/soft-bags-flash.md
@@ -0,0 +1,5 @@
+---
+"astro": patch
+---
+
+Fixes HMR for MDX dependencies in Content Collections
diff --git a/packages/astro/e2e/content-collections.test.js b/packages/astro/e2e/content-collections.test.js
new file mode 100644
index 000000000000..344a27e99769
--- /dev/null
+++ b/packages/astro/e2e/content-collections.test.js
@@ -0,0 +1,32 @@
+import { expect } from '@playwright/test';
+import { testFactory } from './test-utils.js';
+
+const test = testFactory({ root: './fixtures/content-collections/' });
+
+let devServer;
+
+test.beforeAll(async ({ astro }) => {
+ devServer = await astro.startDevServer();
+});
+
+test.afterAll(async ({ astro }) => {
+ await devServer.stop();
+ astro.resetAllFiles();
+});
+
+test.describe('Content Collections', () => {
+ test('HMR', async ({ page, astro }) => {
+ await page.goto(astro.resolveUrl('/'));
+
+ await astro.editFile('./src/components/MyComponent.astro', (original) =>
+ original.replace('red', 'green')
+ );
+
+ const h1 = page.locator('#my-heading');
+
+ await expect(h1, 'should have green color').toHaveCSS(
+ 'color',
+ 'rgb(0, 128, 0)'
+ );
+ });
+});
diff --git a/packages/astro/e2e/fixtures/content-collections/astro.config.mjs b/packages/astro/e2e/fixtures/content-collections/astro.config.mjs
new file mode 100644
index 000000000000..c14d5d0128a4
--- /dev/null
+++ b/packages/astro/e2e/fixtures/content-collections/astro.config.mjs
@@ -0,0 +1,9 @@
+import { defineConfig } from 'astro/config';
+import mdx from '@astrojs/mdx';
+
+// https://astro.build/config
+export default defineConfig({
+ integrations: [
+ mdx(),
+ ],
+});
diff --git a/packages/astro/e2e/fixtures/content-collections/package.json b/packages/astro/e2e/fixtures/content-collections/package.json
new file mode 100644
index 000000000000..169f7590b312
--- /dev/null
+++ b/packages/astro/e2e/fixtures/content-collections/package.json
@@ -0,0 +1,9 @@
+{
+ "name": "@e2e/content-collections",
+ "version": "0.0.0",
+ "private": true,
+ "dependencies": {
+ "@astrojs/mdx": "workspace:*",
+ "astro": "workspace:*"
+ }
+}
diff --git a/packages/astro/e2e/fixtures/content-collections/src/components/MyComponent.astro b/packages/astro/e2e/fixtures/content-collections/src/components/MyComponent.astro
new file mode 100644
index 000000000000..f9f831bb2e00
--- /dev/null
+++ b/packages/astro/e2e/fixtures/content-collections/src/components/MyComponent.astro
@@ -0,0 +1,10 @@
+
+ Some text here
+
+
+
+
diff --git a/packages/astro/e2e/fixtures/content-collections/src/content/posts/post-1.mdx b/packages/astro/e2e/fixtures/content-collections/src/content/posts/post-1.mdx
new file mode 100644
index 000000000000..d03b9b996c62
--- /dev/null
+++ b/packages/astro/e2e/fixtures/content-collections/src/content/posts/post-1.mdx
@@ -0,0 +1,6 @@
+---
+---
+
+import MyComponent from '../../components/MyComponent.astro';
+
+
diff --git a/packages/astro/e2e/fixtures/content-collections/src/pages/index.astro b/packages/astro/e2e/fixtures/content-collections/src/pages/index.astro
new file mode 100644
index 000000000000..4d6f17501943
--- /dev/null
+++ b/packages/astro/e2e/fixtures/content-collections/src/pages/index.astro
@@ -0,0 +1,8 @@
+---
+import { getEntryBySlug } from 'astro:content'
+
+const post = await getEntryBySlug('posts', 'post-1')
+const { Content } = await post.render();
+---
+
+
diff --git a/packages/astro/src/content/vite-plugin-content-assets.ts b/packages/astro/src/content/vite-plugin-content-assets.ts
index 6eeef1c40640..b985eb9a73fb 100644
--- a/packages/astro/src/content/vite-plugin-content-assets.ts
+++ b/packages/astro/src/content/vite-plugin-content-assets.ts
@@ -64,13 +64,29 @@ export function astroContentAssetPropagationPlugin({
if (!devModuleLoader.getModuleById(basePath)?.ssrModule) {
await devModuleLoader.import(basePath);
}
- const { styles, urls } = await getStylesForURL(pathToFileURL(basePath), devModuleLoader);
+ const {
+ styles,
+ urls,
+ crawledFiles: styleCrawledFiles,
+ } = await getStylesForURL(pathToFileURL(basePath), devModuleLoader);
- const hoistedScripts = await getScriptsForURL(
- pathToFileURL(basePath),
- settings.config.root,
- devModuleLoader
- );
+ const { scripts: hoistedScripts, crawledFiles: scriptCrawledFiles } =
+ await getScriptsForURL(pathToFileURL(basePath), settings.config.root, devModuleLoader);
+
+ // Register files we crawled to be able to retrieve the rendered styles and scripts,
+ // as when they get updated, we need to re-transform ourselves.
+ // We also only watch files within the user source code, as changes in node_modules
+ // are usually also ignored by Vite.
+ for (const file of styleCrawledFiles) {
+ if (!file.includes('node_modules')) {
+ this.addWatchFile(file);
+ }
+ }
+ for (const file of scriptCrawledFiles) {
+ if (!file.includes('node_modules')) {
+ this.addWatchFile(file);
+ }
+ }
stringifiedLinks = JSON.stringify([...urls]);
stringifiedStyles = JSON.stringify(styles.map((s) => s.content));
diff --git a/packages/astro/src/core/module-loader/loader.ts b/packages/astro/src/core/module-loader/loader.ts
index 4d987271729a..976354448acc 100644
--- a/packages/astro/src/core/module-loader/loader.ts
+++ b/packages/astro/src/core/module-loader/loader.ts
@@ -41,6 +41,7 @@ export interface ModuleLoader {
export interface ModuleNode {
id: string | null;
url: string;
+ file: string | null;
ssrModule: Record | null;
ssrTransformResult: {
deps?: string[];
diff --git a/packages/astro/src/vite-plugin-astro-server/css.ts b/packages/astro/src/vite-plugin-astro-server/css.ts
index 0f0002907a88..cda2ef002bc8 100644
--- a/packages/astro/src/vite-plugin-astro-server/css.ts
+++ b/packages/astro/src/vite-plugin-astro-server/css.ts
@@ -13,12 +13,16 @@ interface ImportedStyle {
export async function getStylesForURL(
filePath: URL,
loader: ModuleLoader
-): Promise<{ urls: Set; styles: ImportedStyle[] }> {
+): Promise<{ urls: Set; styles: ImportedStyle[]; crawledFiles: Set }> {
const importedCssUrls = new Set();
// Map of url to injected style object. Use a `url` key to deduplicate styles
const importedStylesMap = new Map();
+ const crawledFiles = new Set();
for await (const importedModule of crawlGraph(loader, viteID(filePath), true)) {
+ if (importedModule.file) {
+ crawledFiles.add(importedModule.file);
+ }
if (isBuildableCSSRequest(importedModule.url)) {
// In dev, we inline all styles if possible
let css = '';
@@ -60,5 +64,6 @@ export async function getStylesForURL(
return {
urls: importedCssUrls,
styles: [...importedStylesMap.values()],
+ crawledFiles,
};
}
diff --git a/packages/astro/src/vite-plugin-astro-server/route.ts b/packages/astro/src/vite-plugin-astro-server/route.ts
index 9f9951b7e313..f04e236418fd 100644
--- a/packages/astro/src/vite-plugin-astro-server/route.ts
+++ b/packages/astro/src/vite-plugin-astro-server/route.ts
@@ -393,7 +393,7 @@ async function getScriptsAndStyles({ pipeline, filePath }: GetScriptsAndStylesPa
const settings = pipeline.getSettings();
const mode = pipeline.getEnvironment().mode;
// Add hoisted script tags
- const scripts = await getScriptsForURL(filePath, settings.config.root, moduleLoader);
+ const { scripts } = await getScriptsForURL(filePath, settings.config.root, moduleLoader);
// Inject HMR scripts
if (isPage(filePath, settings) && mode === 'development') {
diff --git a/packages/astro/src/vite-plugin-astro-server/scripts.ts b/packages/astro/src/vite-plugin-astro-server/scripts.ts
index 00bc4054b029..902909753224 100644
--- a/packages/astro/src/vite-plugin-astro-server/scripts.ts
+++ b/packages/astro/src/vite-plugin-astro-server/scripts.ts
@@ -9,12 +9,16 @@ export async function getScriptsForURL(
filePath: URL,
root: URL,
loader: ModuleLoader
-): Promise> {
+): Promise<{ scripts: Set; crawledFiles: Set }> {
const elements = new Set();
+ const crawledFiles = new Set();
const rootID = viteID(filePath);
const modInfo = loader.getModuleInfo(rootID);
addHoistedScripts(elements, modInfo, root);
for await (const moduleNode of crawlGraph(loader, rootID, true)) {
+ if (moduleNode.file) {
+ crawledFiles.add(moduleNode.file);
+ }
const id = moduleNode.id;
if (id) {
const info = loader.getModuleInfo(id);
@@ -22,7 +26,7 @@ export async function getScriptsForURL(
}
}
- return elements;
+ return { scripts: elements, crawledFiles };
}
function addHoistedScripts(set: Set, info: ModuleInfo | null, root: URL) {
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 193bb1ae735c..d0d0cb211eb2 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -935,6 +935,15 @@ importers:
specifier: workspace:*
version: link:../../..
+ packages/astro/e2e/fixtures/content-collections:
+ dependencies:
+ '@astrojs/mdx':
+ specifier: workspace:*
+ version: link:../../../../integrations/mdx
+ astro:
+ specifier: workspace:*
+ version: link:../../..
+
packages/astro/e2e/fixtures/css:
dependencies:
astro: