diff --git a/.changeset/chatty-eels-lose.md b/.changeset/chatty-eels-lose.md new file mode 100644 index 000000000000..d744998c695c --- /dev/null +++ b/.changeset/chatty-eels-lose.md @@ -0,0 +1,5 @@ +--- +'astro': patch +--- + +Fixes concurrent static builds failing to generate i18n rewrite fallbacks for dynamic routes diff --git a/.changeset/cool-lamps-cover.md b/.changeset/cool-lamps-cover.md new file mode 100644 index 000000000000..307605f40673 --- /dev/null +++ b/.changeset/cool-lamps-cover.md @@ -0,0 +1,5 @@ +--- +'astro': patch +--- + +Fixes stale dev CSS after editing component style blocks and CSS files in dev diff --git a/.changeset/khaki-peaches-appear.md b/.changeset/khaki-peaches-appear.md deleted file mode 100644 index abb8f9a020eb..000000000000 --- a/.changeset/khaki-peaches-appear.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'astro': patch ---- - -Fixes a bug where an error while finalizing a request could prevent a response from being sent diff --git a/.changeset/logger-runtime-loading.md b/.changeset/logger-runtime-loading.md deleted file mode 100644 index 2a1221a8ed7c..000000000000 --- a/.changeset/logger-runtime-loading.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'astro': patch ---- - -Fixes a case where a custom `logger.entrypoint` failed to load at runtime in a built server bundle. diff --git a/.changeset/metal-lions-smell.md b/.changeset/metal-lions-smell.md deleted file mode 100644 index 06adf71c65cd..000000000000 --- a/.changeset/metal-lions-smell.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'astro': patch ---- - -Fixes action path resolution so that properties of a resolved action function are not treated as routable path segments diff --git a/.changeset/soft-things-deny.md b/.changeset/soft-things-deny.md deleted file mode 100644 index f9b1b397eaa4..000000000000 --- a/.changeset/soft-things-deny.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'astro': patch ---- - -Fixes a bug where the custom 404 (or 500) page was not rendered when a middleware rewrite targeted a route that returned an empty 404/500 response, and a blank page was returned instead diff --git a/.changeset/strict-socks-decide.md b/.changeset/strict-socks-decide.md deleted file mode 100644 index 8b990b8d3f0e..000000000000 --- a/.changeset/strict-socks-decide.md +++ /dev/null @@ -1,7 +0,0 @@ ---- -'astro': patch -'@astrojs/internal-helpers': patch -'astro-vscode': patch ---- - -Updates dependency `js-yaml` to v4.3.0 diff --git a/examples/advanced-routing/package.json b/examples/advanced-routing/package.json index 1ded7002196f..bfbd480ca15f 100644 --- a/examples/advanced-routing/package.json +++ b/examples/advanced-routing/package.json @@ -13,8 +13,8 @@ "astro": "astro" }, "dependencies": { - "@astrojs/node": "^11.0.2", - "astro": "^7.1.4", + "@astrojs/node": "^11.0.3", + "astro": "^7.1.5", "hono": "^4.12.14" } } diff --git a/examples/basics/package.json b/examples/basics/package.json index 03b4ba4cd7e3..c00383776974 100644 --- a/examples/basics/package.json +++ b/examples/basics/package.json @@ -13,6 +13,6 @@ "astro": "astro" }, "dependencies": { - "astro": "^7.1.4" + "astro": "^7.1.5" } } diff --git a/examples/blog/package.json b/examples/blog/package.json index 7ee48e2479b4..168b28aef0ca 100644 --- a/examples/blog/package.json +++ b/examples/blog/package.json @@ -13,10 +13,10 @@ "astro": "astro" }, "dependencies": { - "@astrojs/mdx": "^7.0.4", + "@astrojs/mdx": "^7.0.5", "@astrojs/rss": "^4.0.19", "@astrojs/sitemap": "^3.7.3", - "astro": "^7.1.4", + "astro": "^7.1.5", "sharp": "^0.35.0" } } diff --git a/examples/component/package.json b/examples/component/package.json index e20c18d72d47..d0c5c234124a 100644 --- a/examples/component/package.json +++ b/examples/component/package.json @@ -18,7 +18,7 @@ ], "scripts": {}, "devDependencies": { - "astro": "^7.1.4" + "astro": "^7.1.5" }, "peerDependencies": { "astro": "^5.0.0 || ^6.0.0" diff --git a/examples/container-with-vitest/package.json b/examples/container-with-vitest/package.json index f7cb30d18624..cf1811a0aaf2 100644 --- a/examples/container-with-vitest/package.json +++ b/examples/container-with-vitest/package.json @@ -14,8 +14,8 @@ "test": "vitest run" }, "dependencies": { - "@astrojs/react": "^6.0.1", - "astro": "^7.1.4", + "@astrojs/react": "^6.0.2", + "astro": "^7.1.5", "react": "^18.3.1", "react-dom": "^18.3.1", "vitest": "^4.1.0" diff --git a/examples/framework-alpine/package.json b/examples/framework-alpine/package.json index 871ce66fa808..309a6c27e6d0 100644 --- a/examples/framework-alpine/package.json +++ b/examples/framework-alpine/package.json @@ -16,6 +16,6 @@ "@astrojs/alpinejs": "^1.0.0", "@types/alpinejs": "^3.13.11", "alpinejs": "^3.15.8", - "astro": "^7.1.4" + "astro": "^7.1.5" } } diff --git a/examples/framework-multiple/package.json b/examples/framework-multiple/package.json index 466cdb2e6fa6..644286a6ea00 100644 --- a/examples/framework-multiple/package.json +++ b/examples/framework-multiple/package.json @@ -13,14 +13,14 @@ "astro": "astro" }, "dependencies": { - "@astrojs/preact": "^6.0.1", - "@astrojs/react": "^6.0.1", + "@astrojs/preact": "^6.0.2", + "@astrojs/react": "^6.0.2", "@astrojs/solid-js": "^7.0.1", "@astrojs/svelte": "^9.0.1", "@astrojs/vue": "^7.0.1", "@types/react": "^18.3.28", "@types/react-dom": "^18.3.7", - "astro": "^7.1.4", + "astro": "^7.1.5", "preact": "^10.28.4", "react": "^18.3.1", "react-dom": "^18.3.1", diff --git a/examples/framework-preact/package.json b/examples/framework-preact/package.json index 9e56c8484f22..a95972b48c77 100644 --- a/examples/framework-preact/package.json +++ b/examples/framework-preact/package.json @@ -13,9 +13,9 @@ "astro": "astro" }, "dependencies": { - "@astrojs/preact": "^6.0.1", + "@astrojs/preact": "^6.0.2", "@preact/signals": "^2.8.1", - "astro": "^7.1.4", + "astro": "^7.1.5", "preact": "^10.28.4" } } diff --git a/examples/framework-react/package.json b/examples/framework-react/package.json index b06c4f8bdaae..33c481fa0510 100644 --- a/examples/framework-react/package.json +++ b/examples/framework-react/package.json @@ -13,10 +13,10 @@ "astro": "astro" }, "dependencies": { - "@astrojs/react": "^6.0.1", + "@astrojs/react": "^6.0.2", "@types/react": "^18.3.28", "@types/react-dom": "^18.3.7", - "astro": "^7.1.4", + "astro": "^7.1.5", "react": "^18.3.1", "react-dom": "^18.3.1" } diff --git a/examples/framework-solid/package.json b/examples/framework-solid/package.json index bafecde9f7c4..c2aaf112e3c7 100644 --- a/examples/framework-solid/package.json +++ b/examples/framework-solid/package.json @@ -14,7 +14,7 @@ }, "dependencies": { "@astrojs/solid-js": "^7.0.1", - "astro": "^7.1.4", + "astro": "^7.1.5", "solid-js": "^1.9.11" } } diff --git a/examples/framework-svelte/package.json b/examples/framework-svelte/package.json index 73767bd62ad9..1163ce6d29e0 100644 --- a/examples/framework-svelte/package.json +++ b/examples/framework-svelte/package.json @@ -14,7 +14,7 @@ }, "dependencies": { "@astrojs/svelte": "^9.0.1", - "astro": "^7.1.4", + "astro": "^7.1.5", "svelte": "^5.53.5" } } diff --git a/examples/framework-vue/package.json b/examples/framework-vue/package.json index c8a8c9ba54b2..d61ee41175c1 100644 --- a/examples/framework-vue/package.json +++ b/examples/framework-vue/package.json @@ -14,7 +14,7 @@ }, "dependencies": { "@astrojs/vue": "^7.0.1", - "astro": "^7.1.4", + "astro": "^7.1.5", "vue": "^3.5.29" } } diff --git a/examples/hackernews/package.json b/examples/hackernews/package.json index e20b0cb739eb..a78107cc8ac0 100644 --- a/examples/hackernews/package.json +++ b/examples/hackernews/package.json @@ -13,7 +13,7 @@ "astro": "astro" }, "dependencies": { - "@astrojs/node": "^11.0.2", - "astro": "^7.1.4" + "@astrojs/node": "^11.0.3", + "astro": "^7.1.5" } } diff --git a/examples/integration/package.json b/examples/integration/package.json index ab7f01102989..a4362f38e23f 100644 --- a/examples/integration/package.json +++ b/examples/integration/package.json @@ -18,7 +18,7 @@ ], "scripts": {}, "devDependencies": { - "astro": "^7.1.4" + "astro": "^7.1.5" }, "peerDependencies": { "astro": "^4.0.0" diff --git a/examples/minimal/package.json b/examples/minimal/package.json index 2ddf8c765fad..bc417f83943b 100644 --- a/examples/minimal/package.json +++ b/examples/minimal/package.json @@ -13,6 +13,6 @@ "astro": "astro" }, "dependencies": { - "astro": "^7.1.4" + "astro": "^7.1.5" } } diff --git a/examples/portfolio/package.json b/examples/portfolio/package.json index a0bdcd703dcf..42a6fe397b5b 100644 --- a/examples/portfolio/package.json +++ b/examples/portfolio/package.json @@ -13,6 +13,6 @@ "astro": "astro" }, "dependencies": { - "astro": "^7.1.4" + "astro": "^7.1.5" } } diff --git a/examples/ssr/package.json b/examples/ssr/package.json index f57cb540083a..e76e33b61b55 100644 --- a/examples/ssr/package.json +++ b/examples/ssr/package.json @@ -14,9 +14,9 @@ "server": "node dist/server/entry.mjs" }, "dependencies": { - "@astrojs/node": "^11.0.2", + "@astrojs/node": "^11.0.3", "@astrojs/svelte": "^9.0.1", - "astro": "^7.1.4", + "astro": "^7.1.5", "svelte": "^5.53.5" } } diff --git a/examples/starlog/package.json b/examples/starlog/package.json index 0f5c0424189c..17fb972fa0ae 100644 --- a/examples/starlog/package.json +++ b/examples/starlog/package.json @@ -9,7 +9,7 @@ "astro": "astro" }, "dependencies": { - "astro": "^7.1.4", + "astro": "^7.1.5", "sass": "^1.97.3", "sharp": "^0.35.0" }, diff --git a/examples/toolbar-app/package.json b/examples/toolbar-app/package.json index 7267b00b2916..3fef2cdec13d 100644 --- a/examples/toolbar-app/package.json +++ b/examples/toolbar-app/package.json @@ -16,7 +16,7 @@ }, "devDependencies": { "@types/node": "^22.10.6", - "astro": "^7.1.4" + "astro": "^7.1.5" }, "engines": { "node": ">=22.12.0" diff --git a/examples/with-markdoc/package.json b/examples/with-markdoc/package.json index 0ade31b72e36..a856a1f59bd6 100644 --- a/examples/with-markdoc/package.json +++ b/examples/with-markdoc/package.json @@ -13,7 +13,7 @@ "astro": "astro" }, "dependencies": { - "@astrojs/markdoc": "^2.0.5", - "astro": "^7.1.4" + "@astrojs/markdoc": "^2.0.6", + "astro": "^7.1.5" } } diff --git a/examples/with-mdx/package.json b/examples/with-mdx/package.json index cdc4ffc757dd..eb1a9abd91cb 100644 --- a/examples/with-mdx/package.json +++ b/examples/with-mdx/package.json @@ -13,9 +13,9 @@ "astro": "astro" }, "dependencies": { - "@astrojs/mdx": "^7.0.4", - "@astrojs/preact": "^6.0.1", - "astro": "^7.1.4", + "@astrojs/mdx": "^7.0.5", + "@astrojs/preact": "^6.0.2", + "astro": "^7.1.5", "preact": "^10.28.4" } } diff --git a/examples/with-nanostores/package.json b/examples/with-nanostores/package.json index d875b802f5c7..b1cabb9feb7d 100644 --- a/examples/with-nanostores/package.json +++ b/examples/with-nanostores/package.json @@ -13,9 +13,9 @@ "astro": "astro" }, "dependencies": { - "@astrojs/preact": "^6.0.1", + "@astrojs/preact": "^6.0.2", "@nanostores/preact": "^1.0.0", - "astro": "^7.1.4", + "astro": "^7.1.5", "nanostores": "^1.1.1", "preact": "^10.28.4" } diff --git a/examples/with-tailwindcss/package.json b/examples/with-tailwindcss/package.json index 087e91010b3e..b73c9c76c51a 100644 --- a/examples/with-tailwindcss/package.json +++ b/examples/with-tailwindcss/package.json @@ -13,10 +13,10 @@ "astro": "astro" }, "dependencies": { - "@astrojs/mdx": "^7.0.4", + "@astrojs/mdx": "^7.0.5", "@tailwindcss/vite": "^4.2.1", "@types/canvas-confetti": "^1.9.0", - "astro": "^7.1.4", + "astro": "^7.1.5", "canvas-confetti": "^1.9.4", "tailwindcss": "^4.2.1", "vite": "^8.0.13" diff --git a/examples/with-vitest/package.json b/examples/with-vitest/package.json index 75ad9fc9c6f5..fbf70e954b0b 100644 --- a/examples/with-vitest/package.json +++ b/examples/with-vitest/package.json @@ -14,7 +14,7 @@ "test": "vitest" }, "dependencies": { - "astro": "^7.1.4", + "astro": "^7.1.5", "vitest": "^5.0.0-beta.2" } } diff --git a/packages/astro/CHANGELOG.md b/packages/astro/CHANGELOG.md index 60e107d1a1cf..87f22338533d 100644 --- a/packages/astro/CHANGELOG.md +++ b/packages/astro/CHANGELOG.md @@ -1,5 +1,24 @@ # astro +## 7.1.5 + +### Patch Changes + +- [#17524](https://github.com/withastro/astro/pull/17524) [`7613030`](https://github.com/withastro/astro/commit/761303051021764c5b8bc43ae4e32629c15b61a8) Thanks [@matthewp](https://github.com/matthewp)! - Fixes a bug where an error while finalizing a request could prevent a response from being sent + +- [#17480](https://github.com/withastro/astro/pull/17480) [`f61ba9c`](https://github.com/withastro/astro/commit/f61ba9cfb028d9f7448eda3fea2726e179d66391) Thanks [@florian-lefebvre](https://github.com/florian-lefebvre)! - Fixes a case where a custom `logger.entrypoint` failed to load at runtime in a built server bundle. + +- [#17525](https://github.com/withastro/astro/pull/17525) [`e614b7b`](https://github.com/withastro/astro/commit/e614b7bd8a0dae403b1f9c219250847d326cbff2) Thanks [@matthewp](https://github.com/matthewp)! - Fixes action path resolution so that properties of a resolved action function are not treated as routable path segments + +- [#17284](https://github.com/withastro/astro/pull/17284) [`c775c1f`](https://github.com/withastro/astro/commit/c775c1f984d1c176bf26a0f9c435bf6c0d585443) Thanks [@matthewp](https://github.com/matthewp)! - Fixes a bug where the custom 404 (or 500) page was not rendered when a middleware rewrite targeted a route that returned an empty 404/500 response, and a blank page was returned instead + +- [#17474](https://github.com/withastro/astro/pull/17474) [`c895b12`](https://github.com/withastro/astro/commit/c895b12b99a73f5a9f98d6699452d12c138f8a18) Thanks [@nicksnyder](https://github.com/nicksnyder)! - Updates dependency `js-yaml` to v4.3.0 + +- Updated dependencies [[`c895b12`](https://github.com/withastro/astro/commit/c895b12b99a73f5a9f98d6699452d12c138f8a18)]: + - @astrojs/internal-helpers@0.10.2 + - @astrojs/markdown-remark@7.2.2 + - @astrojs/markdown-satteri@0.3.5 + ## 7.1.4 ### Patch Changes diff --git a/packages/astro/package.json b/packages/astro/package.json index e56b5f0bc5ad..805df83e5fd8 100644 --- a/packages/astro/package.json +++ b/packages/astro/package.json @@ -1,6 +1,6 @@ { "name": "astro", - "version": "7.1.4", + "version": "7.1.5", "description": "Astro is a modern site builder with web best practices, performance, and DX front-of-mind.", "type": "module", "author": "withastro", diff --git a/packages/astro/src/core/build/generate.ts b/packages/astro/src/core/build/generate.ts index 94597735d541..349334069e70 100644 --- a/packages/astro/src/core/build/generate.ts +++ b/packages/astro/src/core/build/generate.ts @@ -103,7 +103,10 @@ export async function generatePages( // Filter paths for conflicts (same path from multiple routes) const { config } = options.settings; const builtPaths = new Set(); - const filteredPaths = pathsWithRoutes.filter(({ pathname, route }) => { + const filteredPaths: typeof pathsWithRoutes = []; + const fallbackPaths: typeof pathsWithRoutes = []; + for (const pathWithRoute of pathsWithRoutes) { + const { pathname, route } = pathWithRoute; // i18n domains won't work with prerendered routes if (hasI18nDomains && route.prerender) { throw new AstroError({ @@ -117,42 +120,41 @@ export async function generatePages( // Path hasn't been built yet, include it if (!builtPaths.has(normalized)) { builtPaths.add(normalized); - return true; - } - - // Path was already built. Check if this route has higher priority. - const matchedRoute = matchRoute(decodeURI(pathname), options.routesList); - if (!matchedRoute) { - return false; - } - - if (matchedRoute === route) { - // Current route is higher-priority. Include it for building. - return true; - } + } else { + // Path was already built. Check if this route has higher priority. + const matchedRoute = matchRoute(decodeURI(pathname), options.routesList); + if (!matchedRoute) { + continue; + } - // Current route is lower-priority. Warn or error based on config. - if (config.prerenderConflictBehavior === 'error') { - throw new AstroError({ - ...AstroErrorData.PrerenderRouteConflict, - message: AstroErrorData.PrerenderRouteConflict.message( - matchedRoute.route, - route.route, - normalized, - ), - hint: AstroErrorData.PrerenderRouteConflict.hint(matchedRoute.route, route.route), - }); - } else if (config.prerenderConflictBehavior === 'warn') { - const msg = AstroErrorData.PrerenderRouteConflict.message( - matchedRoute.route, - route.route, - normalized, - ); - logger.warn('build', msg); + if (matchedRoute !== route) { + // Current route is lower-priority. Warn or error based on config. + if (config.prerenderConflictBehavior === 'error') { + throw new AstroError({ + ...AstroErrorData.PrerenderRouteConflict, + message: AstroErrorData.PrerenderRouteConflict.message( + matchedRoute.route, + route.route, + normalized, + ), + hint: AstroErrorData.PrerenderRouteConflict.hint(matchedRoute.route, route.route), + }); + } else if (config.prerenderConflictBehavior === 'warn') { + const msg = AstroErrorData.PrerenderRouteConflict.message( + matchedRoute.route, + route.route, + normalized, + ); + logger.warn('build', msg); + } + continue; + } } - return false; - }); + const paths = route.type === 'fallback' ? fallbackPaths : filteredPaths; + paths.push(pathWithRoute); + } + const generationPhases = [filteredPaths, fallbackPaths]; // Generate each path if (config.build.concurrency > 1) { @@ -161,35 +163,37 @@ export async function generatePages( // // NOTE: ideally we could consider an iterator to avoid the batching limitation const BATCH_SIZE = 100_000; - for (let i = 0; i < filteredPaths.length; i += BATCH_SIZE) { - const batch = filteredPaths.slice(i, i + BATCH_SIZE); - const promises: Promise[] = []; - for (const { pathname, route } of batch) { - promises.push( - limit(() => - generatePathWithPrerenderer( - prerenderer, - pathname, - route, - options, - routeToHeaders, - logger, + for (const paths of generationPhases) { + for (let i = 0; i < paths.length; i += BATCH_SIZE) { + const promises = paths + .slice(i, i + BATCH_SIZE) + .map(({ pathname, route }) => + limit(() => + generatePathWithPrerenderer( + prerenderer, + pathname, + route, + options, + routeToHeaders, + logger, + ), ), - ), - ); + ); + await Promise.all(promises); } - await Promise.all(promises); } } else { - for (const { pathname, route } of filteredPaths) { - await generatePathWithPrerenderer( - prerenderer, - pathname, - route, - options, - routeToHeaders, - logger, - ); + for (const paths of generationPhases) { + for (const { pathname, route } of paths) { + await generatePathWithPrerenderer( + prerenderer, + pathname, + route, + options, + routeToHeaders, + logger, + ); + } } } @@ -197,15 +201,17 @@ export async function generatePages( // back to the original routes in allPages. The prerenderer operates on deserialized route // objects (reconstructed from the serialized manifest), so distURL mutations during generation // don't affect the original route objects that are later passed to the astro:build:done hook. - for (const { route: generatedRoute } of filteredPaths) { - if (generatedRoute.distURL && generatedRoute.distURL.length > 0) { - for (const pageData of Object.values(options.allPages)) { - if ( - pageData.route.route === generatedRoute.route && - pageData.route.component === generatedRoute.component - ) { - pageData.route.distURL = generatedRoute.distURL; - break; + for (const paths of generationPhases) { + for (const { route: generatedRoute } of paths) { + if (generatedRoute.distURL && generatedRoute.distURL.length > 0) { + for (const pageData of Object.values(options.allPages)) { + if ( + pageData.route.route === generatedRoute.route && + pageData.route.component === generatedRoute.component + ) { + pageData.route.distURL = generatedRoute.distURL; + break; + } } } } diff --git a/packages/astro/src/vite-plugin-hmr-reload/index.ts b/packages/astro/src/vite-plugin-hmr-reload/index.ts index 5f55d6cf891f..290b0f034978 100644 --- a/packages/astro/src/vite-plugin-hmr-reload/index.ts +++ b/packages/astro/src/vite-plugin-hmr-reload/index.ts @@ -4,7 +4,8 @@ import { RESOLVED_MODULE_DEV_CSS_PREFIX } from '../vite-plugin-css/const.js'; import { getDevCssModuleNameFromPageVirtualModuleName } from '../vite-plugin-css/util.js'; import { isAstroServerEnvironment } from '../environments.js'; -const STYLE_EXT_REGEX = /\.(?:css|scss|sass|less|styl|pcss)$/i; +const STYLE_EXT_REGEX = /\.(?:css|less|sass|scss|styl|stylus|pcss|postcss|sss)$/i; +const STYLE_COMPONENT_EXT_REGEX = /\.(astro|svelte|vue)$/i; const RAW_QUERY_REGEX = /(?:\?|&)raw(?:&|$)/; function hasStyleExtension(id: string): boolean { @@ -12,13 +13,47 @@ function hasStyleExtension(id: string): boolean { return STYLE_EXT_REGEX.test(id.split('?')[0]); } -function isStyleModule(mod: EnvironmentModuleNode): boolean { +function isComponentStyleModule(id: string): boolean { + const [filename, rawQuery] = id.split('?', 2); + const extensionMatch = STYLE_COMPONENT_EXT_REGEX.exec(filename); + if (!rawQuery || !extensionMatch) return false; + const query = new URLSearchParams(rawQuery); + return query.get('type') === 'style' && query.has(extensionMatch[1].toLowerCase()); +} + +// Whether a style module comes from a file (e.g. global.css, styles.scss) +// or from a component request (e.g. *.astro?astro&type=style, *.svelte?svelte&type=style). +type StyleModuleType = 'file' | 'component'; + +function getStyleModuleType(mod: EnvironmentModuleNode): StyleModuleType | undefined { // CSS imported with ?raw is a JS string export, so SSR importers need to be invalidated // instead of relying on Vite's client-side CSS HMR handling. - if (mod.id && RAW_QUERY_REGEX.test(mod.id) && hasStyleExtension(mod.id)) return false; - if (mod.file && hasStyleExtension(mod.file)) return true; + if (mod.id && RAW_QUERY_REGEX.test(mod.id) && hasStyleExtension(mod.id)) return; + if (mod.id && isComponentStyleModule(mod.id)) return 'component'; + if (mod.file && hasStyleExtension(mod.file)) return 'file'; // CSS modules and other style files may have query params in their id (e.g. ?used, ?direct) - return mod.id ? hasStyleExtension(mod.id) : false; + return mod.id && hasStyleExtension(mod.id) ? 'file' : undefined; +} + +function hasClientStyleModuleByFile( + moduleGraph: { getModulesByFile?(file: string): Set | undefined }, + mod: EnvironmentModuleNode, + styleType: StyleModuleType, +): boolean { + if (mod.file == null || moduleGraph.getModulesByFile == null) return false; + + const fileModules = moduleGraph.getModulesByFile(mod.file); + if (fileModules == null) return false; + + for (const fileMod of fileModules) { + if (fileMod.id == null) continue; + if (styleType === 'component') { + if (isComponentStyleModule(fileMod.id)) return true; + } else if (!RAW_QUERY_REGEX.test(fileMod.id) && hasStyleExtension(fileMod.id)) { + return true; + } + } + return false; } /** @@ -36,23 +71,54 @@ export default function hmrReload(): Plugin { if (!isAstroServerEnvironment(this.environment)) return; let hasSsrOnlyModules = false; + let hasStyleModules = false; let hasSkippedStyleModules = false; const invalidatedModules = new Set(); for (const mod of modules) { if (mod.id == null) continue; - if (isStyleModule(mod)) { + const styleType = getStyleModuleType(mod); + const clientModule = server.environments.client.moduleGraph.getModuleById(mod.id); + if (styleType) { + hasStyleModules = true; + // No client module means nothing will apply the CSS update client-side, so force a reload. + if ( + clientModule == null && + !hasClientStyleModuleByFile(server.environments.client.moduleGraph, mod, styleType) + ) { + this.environment.moduleGraph.invalidateModule( + mod, + invalidatedModules, + timestamp, + true, + ); + hasSsrOnlyModules = true; + continue; + } hasSkippedStyleModules = true; continue; } - const clientModule = server.environments.client.moduleGraph.getModuleById(mod.id); if (clientModule != null) continue; this.environment.moduleGraph.invalidateModule(mod, invalidatedModules, timestamp, true); hasSsrOnlyModules = true; } + const invalidateDevCssModules = () => { + for (const [id, mod] of this.environment.moduleGraph.idToModuleMap) { + if (id.startsWith(RESOLVED_MODULE_DEV_CSS_PREFIX)) { + this.environment.moduleGraph.invalidateModule(mod, undefined, timestamp, true); + if (isRunnableDevEnvironment(this.environment)) { + const runnerMod = this.environment.runner.evaluatedModules.getModuleById(id); + if (runnerMod) { + this.environment.runner.evaluatedModules.invalidateModule(runnerMod); + } + } + } + } + }; + // If any invalidated modules are virtual modules for pages, also invalidate their // associated dev CSS modules, if any. for (const invalidatedModule of invalidatedModules) { @@ -66,6 +132,9 @@ export default function hmrReload(): Plugin { } if (hasSsrOnlyModules) { + if (hasStyleModules) { + invalidateDevCssModules(); + } // Invalidate all recursively-invalidated modules (importers) in the // runner cache, not just the directly changed files. Without this, // barrel files like index.ts stay cached and dynamic import() calls @@ -106,17 +175,7 @@ export default function hmrReload(): Plugin { // Invalidate all per-route dev CSS virtual modules so the next SSR request // re-collects CSS with updated content. Without this, the inline