diff --git a/packages/astro/src/core/build/static-build.ts b/packages/astro/src/core/build/static-build.ts index c0b11f9ad77e..e624c3f8cfc2 100644 --- a/packages/astro/src/core/build/static-build.ts +++ b/packages/astro/src/core/build/static-build.ts @@ -289,7 +289,16 @@ async function buildEnvironments(opts: StaticBuildOptions, internals: BuildInter } return [prefix, cleanChunkName(name), suffix].join(''); }, - assetFileNames: `${settings.config.build.assets}/[name].[hash][extname]`, + assetFileNames(assetInfo) { + // Strip the @_@ extension-masking pattern from asset names, just like chunkFileNames above. + // The @_@ pattern is an internal mechanism for virtual module IDs and should not leak into output filenames. + const name = assetInfo.names?.[0] ?? ''; + if (name.includes(ASTRO_PAGE_EXTENSION_POST_PATTERN)) { + const [sanitizedName] = name.split(ASTRO_PAGE_EXTENSION_POST_PATTERN); + return `${settings.config.build.assets}/${sanitizedName}.[hash][extname]`; + } + return `${settings.config.build.assets}/[name].[hash][extname]`; + }, ...viteConfig.build?.rollupOptions?.output, entryFileNames(chunkInfo) { if (chunkInfo.facadeModuleId?.startsWith(VIRTUAL_PAGE_RESOLVED_MODULE_ID)) { @@ -424,7 +433,16 @@ async function buildEnvironments(opts: StaticBuildOptions, internals: BuildInter chunkFileNames(chunkInfo) { return `${settings.config.build.assets}/${cleanChunkName(chunkInfo.name)}.[hash].js`; }, - assetFileNames: `${settings.config.build.assets}/[name].[hash][extname]`, + assetFileNames(assetInfo) { + // Strip the @_@ extension-masking pattern from asset names. + // The @_@ pattern is an internal mechanism for virtual module IDs and should not leak into output filenames. + const name = assetInfo.names?.[0] ?? ''; + if (name.includes(ASTRO_PAGE_EXTENSION_POST_PATTERN)) { + const [sanitizedName] = name.split(ASTRO_PAGE_EXTENSION_POST_PATTERN); + return `${settings.config.build.assets}/${sanitizedName}.[hash][extname]`; + } + return `${settings.config.build.assets}/[name].[hash][extname]`; + }, ...viteConfig.environments?.client?.build?.rollupOptions?.output, }, }, diff --git a/packages/astro/test/css-deduplication.test.ts b/packages/astro/test/css-deduplication.test.ts index e6e674e3433a..b86202d9aea6 100644 --- a/packages/astro/test/css-deduplication.test.ts +++ b/packages/astro/test/css-deduplication.test.ts @@ -19,8 +19,10 @@ describe('CSS deduplication for hydrated components', () => { it('should not duplicate CSS for hydrated components', async () => { const assets = await fixture.readdir('/_astro'); - // Generated file for Counter.css (filename format changed in main-next) - const COUNTER_CSS_PATH = '/_astro/index@_@astro.DbgLc3FE.css'; + // Generated file for Counter.css — find it dynamically since the hash may change + const counterCssFile = assets.find((f) => f.startsWith('index.') && f.endsWith('.css')); + assert.ok(counterCssFile, 'Expected a CSS file starting with "index."'); + const COUNTER_CSS_PATH = `/_astro/${counterCssFile}`; let file = await fixture.readFile(COUNTER_CSS_PATH); file = file.replace(/\s+/g, '');