From b1e0628b6a5dd541e15166ca6666e05f9b478ab8 Mon Sep 17 00:00:00 2001 From: sapphi-red <49056869+sapphi-red@users.noreply.github.com> Date: Fri, 10 Apr 2026 12:44:03 +0900 Subject: [PATCH 1/3] fix(build): emptyOutDir should happen for watch rebuilds --- .../vite/src/node/plugins/prepareOutDir.ts | 2 +- playground/assets/__tests__/assets.spec.ts | 21 +++++++++++++++++++ playground/assets/asset/update.js | 4 ++++ playground/assets/index.html | 2 ++ playground/vitestSetup.ts | 2 -- 5 files changed, 28 insertions(+), 3 deletions(-) create mode 100644 playground/assets/asset/update.js diff --git a/packages/vite/src/node/plugins/prepareOutDir.ts b/packages/vite/src/node/plugins/prepareOutDir.ts index 9ee4b3c4b9419c..4117e63bb5e17f 100644 --- a/packages/vite/src/node/plugins/prepareOutDir.ts +++ b/packages/vite/src/node/plugins/prepareOutDir.ts @@ -11,7 +11,7 @@ export function prepareOutDirPlugin(): Plugin { const rendered = new Set() return { name: 'vite:prepare-out-dir', - options() { + buildStart() { rendered.delete(this.environment) }, renderStart: { diff --git a/playground/assets/__tests__/assets.spec.ts b/playground/assets/__tests__/assets.spec.ts index 8c8cd4ffe74f3d..89cfe2a78f4344 100644 --- a/playground/assets/__tests__/assets.spec.ts +++ b/playground/assets/__tests__/assets.spec.ts @@ -705,6 +705,27 @@ describe.runIf(isBuild)('css and assets in css in build watch', () => { expect(cssFile).not.toMatch(/undefined/) }) + test('old file is removed when the content changes', async () => { + await expect.poll(() => page.textContent('.update-content')).toBe('hello') + + const oldMainJsFiles = listAssets('foo').filter((f) => + /index-[-\w]+\.js$/.test(f), + ) + expect(oldMainJsFiles.length).toBe(1) + const oldMainJsFile = oldMainJsFiles[0] + + editFile('asset/update.js', (code) => code.replace('hello', 'world')) + await notifyRebuildComplete(watcher) + await page.reload() + await expect.poll(() => page.textContent('.update-content')).toBe('world') + + const newMainJsFiles = listAssets('foo').filter((f) => + /index-[-\w]+\.js$/.test(f), + ) + expect(newMainJsFiles).not.toContain(oldMainJsFile) + expect(newMainJsFiles.length).toBe(1) + }) + test('import module.css', async () => { expect(await getColor('#foo')).toBe('red') editFile('css/foo.module.css', (code) => code.replace('red', 'blue')) diff --git a/playground/assets/asset/update.js b/playground/assets/asset/update.js new file mode 100644 index 00000000000000..79b790f512ef45 --- /dev/null +++ b/playground/assets/asset/update.js @@ -0,0 +1,4 @@ +function text(el, text) { + document.querySelector(el).textContent = text +} +text('.update-content', 'hello') diff --git a/playground/assets/index.html b/playground/assets/index.html index 6b6eac7f2df73b..77f7ea4add1a44 100644 --- a/playground/assets/index.html +++ b/playground/assets/index.html @@ -482,7 +482,9 @@

assets in template

link style
+
+ diff --git a/playground/vitestSetup.ts b/playground/vitestSetup.ts index 17bbc40755f4ac..6bfe32282a92bd 100644 --- a/playground/vitestSetup.ts +++ b/playground/vitestSetup.ts @@ -265,8 +265,6 @@ async function loadConfig(configEnv: ConfigEnv) { // esbuild do not minify ES lib output since that would remove pure annotations and break tree-shaking // skip transpilation during tests to make it faster target: 'esnext', - // tests are flaky when `emptyOutDir` is `true` - emptyOutDir: false, }, customLogger: createInMemoryLogger(serverLogs), plugins: [throwHtmlParseError()], From 413c537ce9e3ebc20ba25bd6a76b60e1d4705bc1 Mon Sep 17 00:00:00 2001 From: sapphi-red <49056869+sapphi-red@users.noreply.github.com> Date: Fri, 10 Apr 2026 13:06:20 +0900 Subject: [PATCH 2/3] test: fix legacy playground --- playground/legacy/vite.config.js | 1 + 1 file changed, 1 insertion(+) diff --git a/playground/legacy/vite.config.js b/playground/legacy/vite.config.js index c822b5d60b066b..8b5884f7943cad 100644 --- a/playground/legacy/vite.config.js +++ b/playground/legacy/vite.config.js @@ -17,6 +17,7 @@ export default defineConfig({ manifest: true, sourcemap: true, assetsInlineLimit: 100, // keep SVG as assets URL + emptyOutDir: false, // the dist directory is shared with other configs rollupOptions: { input: { index: path.resolve(import.meta.dirname, 'index.html'), From e61d8ce83376309e031960f3821855c2bd57c239 Mon Sep 17 00:00:00 2001 From: sapphi-red <49056869+sapphi-red@users.noreply.github.com> Date: Fri, 10 Apr 2026 13:51:11 +0900 Subject: [PATCH 3/3] fix: buildStart is called for each output --- packages/vite/src/node/plugins/prepareOutDir.ts | 2 +- playground/css/vite.config.js | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/vite/src/node/plugins/prepareOutDir.ts b/packages/vite/src/node/plugins/prepareOutDir.ts index 4117e63bb5e17f..eb8fd6554c6d21 100644 --- a/packages/vite/src/node/plugins/prepareOutDir.ts +++ b/packages/vite/src/node/plugins/prepareOutDir.ts @@ -11,7 +11,7 @@ export function prepareOutDirPlugin(): Plugin { const rendered = new Set() return { name: 'vite:prepare-out-dir', - buildStart() { + watchChange() { rendered.delete(this.environment) }, renderStart: { diff --git a/playground/css/vite.config.js b/playground/css/vite.config.js index 76dc4395aeac39..937a2dd4e54dfc 100644 --- a/playground/css/vite.config.js +++ b/playground/css/vite.config.js @@ -55,6 +55,7 @@ export default defineConfig({ }, }, }, + emptyOutDir: false, // the dist directory is shared with other configs }, esbuild: { logOverride: {