diff --git a/.changeset/young-pillows-shave.md b/.changeset/young-pillows-shave.md new file mode 100644 index 000000000000..e3d8c4d5dba7 --- /dev/null +++ b/.changeset/young-pillows-shave.md @@ -0,0 +1,5 @@ +--- +'astro': patch +--- + +Ensures consistent CSS chunk hashes across different environments diff --git a/packages/astro/src/core/build/css-asset-name.ts b/packages/astro/src/core/build/css-asset-name.ts index 4172a1cbba19..1876c08771f4 100644 --- a/packages/astro/src/core/build/css-asset-name.ts +++ b/packages/astro/src/core/build/css-asset-name.ts @@ -2,6 +2,8 @@ import type { GetModuleInfo, ModuleInfo } from 'rollup'; import crypto from 'node:crypto'; import npath from 'node:path'; +import { fileURLToPath } from 'node:url'; +import { normalizePath } from 'vite'; import type { AstroSettings } from '../../@types/astro.js'; import { viteID } from '../util.js'; import { getTopLevelPageModuleInfos } from './graph.js'; @@ -13,19 +15,32 @@ const confusingBaseNames = ['404', '500']; // The short name for when the hash can be included // We could get rid of this and only use the createSlugger implementation, but this creates // slightly prettier names. -export function shortHashedName(id: string, ctx: { getModuleInfo: GetModuleInfo }): string { - const parents = getTopLevelPageModuleInfos(id, ctx); - return createNameHash( - getFirstParentId(parents), - parents.map((page) => page.id) - ); +export function shortHashedName(settings: AstroSettings) { + return function (id: string, ctx: { getModuleInfo: GetModuleInfo }): string { + const parents = getTopLevelPageModuleInfos(id, ctx); + return createNameHash( + getFirstParentId(parents), + parents.map((page) => page.id), + settings + ); + }; } -export function createNameHash(baseId: string | undefined, hashIds: string[]): string { +export function createNameHash( + baseId: string | undefined, + hashIds: string[], + settings: AstroSettings +): string { const baseName = baseId ? prettifyBaseName(npath.parse(baseId).name) : 'index'; const hash = crypto.createHash('sha256'); + const root = fileURLToPath(settings.config.root); + for (const id of hashIds) { - hash.update(id, 'utf-8'); + // Strip the project directory from the paths before they are hashed, so that assets + // that import these css files have consistent hashes when built in different environments. + const relativePath = npath.relative(root, id); + // Normalize the path to fix differences between windows and other environments + hash.update(normalizePath(relativePath), 'utf-8'); } const h = hash.digest('hex').slice(0, 8); const proposedName = baseName + '.' + h; diff --git a/packages/astro/src/core/build/plugins/plugin-css.ts b/packages/astro/src/core/build/plugins/plugin-css.ts index 9ac29ebe02a6..26936484265e 100644 --- a/packages/astro/src/core/build/plugins/plugin-css.ts +++ b/packages/astro/src/core/build/plugins/plugin-css.ts @@ -70,7 +70,7 @@ function rollupPluginAstroBuildCSS(options: PluginOptions): VitePlugin[] { const assetFileNames = outputOptions.assetFileNames; const namingIncludesHash = assetFileNames?.toString().includes('[hash]'); const createNameForParentPages = namingIncludesHash - ? assetName.shortHashedName + ? assetName.shortHashedName(settings) : assetName.createSlugger(settings); extendManualChunks(outputOptions, { @@ -94,7 +94,7 @@ function rollupPluginAstroBuildCSS(options: PluginOptions): VitePlugin[] { if (hasAssetPropagationFlag(pageInfo.id)) { // Split delayed assets to separate modules // so they can be injected where needed - const chunkId = assetName.createNameHash(id, [id]); + const chunkId = assetName.createNameHash(id, [id], settings); internals.cssModuleToChunkIdMap.set(id, chunkId); return chunkId; }