diff --git a/packages/vite/src/node/plugins/html.ts b/packages/vite/src/node/plugins/html.ts
index 02305b45387c38..823fb6af465788 100644
--- a/packages/vite/src/node/plugins/html.ts
+++ b/packages/vite/src/node/plugins/html.ts
@@ -820,42 +820,165 @@ export function buildHtmlPlugin(config: ResolvedConfig): Plugin {
},
})
- const getCssFilesForChunk = (
+ const createGetCssFilesForChunk = (): ((
chunk: OutputChunk,
- seenChunks: Set = new Set(),
- seenCss: Set = new Set(),
- ): string[] => {
- if (seenChunks.has(chunk.fileName)) {
- return []
- }
- seenChunks.add(chunk.fileName)
+ ) => string[]) => {
+ const dirtyChunks = new Map>()
+
+ const getCssFilesForUncheckedDirtyChunk = (
+ chunk: OutputChunk,
+ seenChunks: Set = new Set(),
+ seenCss: Set = new Set(),
+ ): string[] => {
+ if (seenChunks.has(chunk.fileName)) {
+ return []
+ }
+ seenChunks.add(chunk.fileName)
+
+ if (analyzedImportedCssFiles.has(chunk)) {
+ const files = analyzedImportedCssFiles.get(chunk)!
+ const additionals = files.filter((file) => !seenCss.has(file))
+ additionals.forEach((file) => seenCss.add(file))
+ return additionals
+ }
+
+ const additionalFiles: string[] = []
+ chunk.imports.forEach((file) => {
+ const importee = bundle[file]
+ if (importee?.type === 'chunk') {
+ additionalFiles.push(
+ ...getCssFilesForUncheckedDirtyChunk(
+ importee,
+ seenChunks,
+ seenCss,
+ ),
+ )
+ }
+ })
+ analyzedImportedCssFiles.set(chunk, additionalFiles)
- if (analyzedImportedCssFiles.has(chunk)) {
- const files = analyzedImportedCssFiles.get(chunk)!
- const additionals = files.filter((file) => !seenCss.has(file))
- additionals.forEach((file) => seenCss.add(file))
- return additionals
+ chunk.viteMetadata!.importedCss.forEach((file) => {
+ if (!seenCss.has(file)) {
+ seenCss.add(file)
+ additionalFiles.push(file)
+ } else {
+ let dirtyChunkUnverifiedCss = dirtyChunks.get(chunk)
+ if (!dirtyChunkUnverifiedCss) {
+ dirtyChunks.set(chunk, (dirtyChunkUnverifiedCss = new Set()))
+ }
+ dirtyChunkUnverifiedCss.add(file)
+ }
+ })
+
+ return additionalFiles
}
- const files: string[] = []
- chunk.imports.forEach((file) => {
- const importee = bundle[file]
- if (importee?.type === 'chunk') {
- files.push(...getCssFilesForChunk(importee, seenChunks, seenCss))
+ const isImportChainHasDirtyChunk = (
+ chunk: OutputChunk,
+ seenCleanChunks: Set,
+ seenChunks = new Set(),
+ ): boolean => {
+ if (seenChunks.has(chunk) || seenCleanChunks.has(chunk)) {
+ return false
}
- })
- analyzedImportedCssFiles.set(chunk, files)
+ const importChainHasDirtyChunk = chunk.imports.some((file) => {
+ const importee = bundle[file]
+ if (importee?.type === 'chunk') {
+ return isImportChainHasDirtyChunk(
+ importee,
+ seenCleanChunks,
+ seenChunks,
+ )
+ }
+ return false
+ })
+ if (!importChainHasDirtyChunk) {
+ seenCleanChunks.add(chunk)
+ }
+ return importChainHasDirtyChunk
+ }
- chunk.viteMetadata!.importedCss.forEach((file) => {
- if (!seenCss.has(file)) {
- seenCss.add(file)
- files.push(file)
+ const getCssFilesForCheckedDirtyChunk = (
+ chunk: OutputChunk,
+ seenChunks: Set = new Set(),
+ seenCss: Set = new Set(),
+ seenCleanChunks = new Set(),
+ ): string[] => {
+ if (seenChunks.has(chunk.fileName)) {
+ return []
}
- })
+ seenChunks.add(chunk.fileName)
+ let isNeedToRebuildCache = false
+ let additionalFiles: string[] = []
+ if (analyzedImportedCssFiles.has(chunk)) {
+ isNeedToRebuildCache = isImportChainHasDirtyChunk(
+ chunk,
+ seenCleanChunks,
+ )
+ if (!isNeedToRebuildCache) {
+ const files = analyzedImportedCssFiles.get(chunk)!
+ const additionals = files.filter((file) => !seenCss.has(file))
+ additionals.forEach((file) => seenCss.add(file))
+ if (!dirtyChunks.has(chunk)) {
+ return additionals
+ } else {
+ additionalFiles = additionals
+ }
+ }
+ } else {
+ isNeedToRebuildCache = true
+ }
+ if (isNeedToRebuildCache) {
+ chunk.imports.forEach((file) => {
+ const importee = bundle[file]
+ if (importee?.type === 'chunk') {
+ additionalFiles.push(
+ ...getCssFilesForCheckedDirtyChunk(
+ importee,
+ seenChunks,
+ seenCss,
+ seenCleanChunks,
+ ),
+ )
+ }
+ })
+ }
+ analyzedImportedCssFiles.set(chunk, additionalFiles)
+
+ const chunkUnverifiedCss = dirtyChunks.get(chunk) ?? new Set()
+ chunk.viteMetadata!.importedCss.forEach((file) => {
+ if (!seenCss.has(file)) {
+ seenCss.add(file)
+ additionalFiles.push(file)
+ chunkUnverifiedCss.delete(file)
+ } else {
+ let dirtyChunkUnverifiedCss = dirtyChunks.get(chunk)
+ if (!dirtyChunkUnverifiedCss) {
+ dirtyChunks.set(
+ chunk,
+ (dirtyChunkUnverifiedCss = chunkUnverifiedCss),
+ )
+ }
+ dirtyChunkUnverifiedCss.add(file)
+ }
+ })
+ if (chunkUnverifiedCss.size === 0 && dirtyChunks.has(chunk)) {
+ dirtyChunks.delete(chunk)
+ }
+ return additionalFiles
+ }
- return files
+ const getCssFilesForChunk = (chunk: OutputChunk): string[] => {
+ if (!dirtyChunks.size) {
+ return getCssFilesForUncheckedDirtyChunk(chunk)
+ }
+ return getCssFilesForCheckedDirtyChunk(chunk)
+ }
+ return getCssFilesForChunk
}
+ const getCssFilesForChunk = createGetCssFilesForChunk()
+
const getCssTagsForChunk = (
chunk: OutputChunk,
toOutputPath: (filename: string) => string,