From 27fe596af8d483406cd8af5575c8e25f3aaa42fd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Arnaud=20Barr=C3=A9?= Date: Thu, 7 Sep 2023 00:37:32 +0200 Subject: [PATCH 1/2] fix(hmr): clean importers in module graph when file is deleted --- packages/vite/src/node/server/hmr.ts | 9 ++++ packages/vite/src/node/server/index.ts | 8 +-- playground/hmr/__tests__/hmr.spec.ts | 51 +++++++++++++++++++ playground/hmr/hmr.ts | 1 + playground/hmr/index.html | 2 + .../hmr/intermediate-file-delete/display.js | 1 + .../hmr/intermediate-file-delete/index.js | 17 +++++++ .../hmr/intermediate-file-delete/re-export.js | 1 + 8 files changed, 86 insertions(+), 4 deletions(-) create mode 100644 playground/hmr/intermediate-file-delete/display.js create mode 100644 playground/hmr/intermediate-file-delete/index.js create mode 100644 playground/hmr/intermediate-file-delete/re-export.js diff --git a/packages/vite/src/node/server/hmr.ts b/packages/vite/src/node/server/hmr.ts index 245685b3674ef0..eaab659b2af5c0 100644 --- a/packages/vite/src/node/server/hmr.ts +++ b/packages/vite/src/node/server/hmr.ts @@ -218,9 +218,18 @@ export function updateModules( export async function handleFileAddUnlink( file: string, server: ViteDevServer, + isUnlink: boolean, ): Promise { const modules = [...(server.moduleGraph.getModulesByFile(file) || [])] + if (isUnlink) { + for (const deletedMod of modules) { + server.moduleGraph.idToModuleMap.forEach((mod) => { + mod.importers.delete(deletedMod) + }) + } + } + modules.push(...getAffectedGlobModules(file, server)) if (modules.length > 0) { diff --git a/packages/vite/src/node/server/index.ts b/packages/vite/src/node/server/index.ts index e887ce2e6ba793..adca608e9ca0d4 100644 --- a/packages/vite/src/node/server/index.ts +++ b/packages/vite/src/node/server/index.ts @@ -577,9 +577,9 @@ export async function _createServer( } } - const onFileAddUnlink = async (file: string) => { + const onFileAddUnlink = async (file: string, isUnlink: boolean) => { file = normalizePath(file) - await handleFileAddUnlink(file, server) + await handleFileAddUnlink(file, server, isUnlink) await onHMRUpdate(file, true) } @@ -591,8 +591,8 @@ export async function _createServer( await onHMRUpdate(file, false) }) - watcher.on('add', onFileAddUnlink) - watcher.on('unlink', onFileAddUnlink) + watcher.on('add', (file) => onFileAddUnlink(file, false)) + watcher.on('unlink', (file) => onFileAddUnlink(file, true)) ws.on('vite:invalidate', async ({ path, message }: InvalidatePayload) => { const mod = moduleGraph.urlToModuleMap.get(path) diff --git a/playground/hmr/__tests__/hmr.spec.ts b/playground/hmr/__tests__/hmr.spec.ts index 880f56c7f252f2..02371a8362dca1 100644 --- a/playground/hmr/__tests__/hmr.spec.ts +++ b/playground/hmr/__tests__/hmr.spec.ts @@ -793,6 +793,57 @@ if (import.meta.hot) { ) }) + test('delete file should not break hmr', async () => { + await page.goto(viteTestUrl) + + await untilUpdated( + () => page.textContent('.intermediate-file-delete-display'), + 'count is 1', + ) + + // add state + await page.click('.intermediate-file-delete-increment') + await untilUpdated( + () => page.textContent('.intermediate-file-delete-display'), + 'count is 2', + ) + + // update import, hmr works + editFile('intermediate-file-delete/index.js', (code) => + code.replace("from './re-export.js'", "from './display.js'"), + ) + editFile('intermediate-file-delete/display.js', (code) => + code.replace('count is ${count}', 'count is ${count}!'), + ) + await untilUpdated( + () => page.textContent('.intermediate-file-delete-display'), + 'count is 2!', + ) + + // remove unused file, page reload because it's considered entry point now + removeFile('intermediate-file-delete/re-export.js') + await untilUpdated( + () => page.textContent('.intermediate-file-delete-display'), + 'count is 1!', + ) + + // re-add state + await page.click('.intermediate-file-delete-increment') + await untilUpdated( + () => page.textContent('.intermediate-file-delete-display'), + 'count is 2!', + ) + + // hmr works after file deletion + editFile('intermediate-file-delete/display.js', (code) => + code.replace('count is ${count}!', 'count is ${count}'), + ) + await untilUpdated( + () => page.textContent('.intermediate-file-delete-display'), + 'count is 2', + ) + }) + test('import.meta.hot?.accept', async () => { const el = await page.$('.optional-chaining') await untilBrowserLogAfter( diff --git a/playground/hmr/hmr.ts b/playground/hmr/hmr.ts index 6267a588be3c9c..3fd552c1e598eb 100644 --- a/playground/hmr/hmr.ts +++ b/playground/hmr/hmr.ts @@ -4,6 +4,7 @@ import './importing-updated' import './invalidation/parent' import './file-delete-restore' import './optional-chaining/parent' +import './intermediate-file-delete' import logo from './logo.svg' export const foo = 1 diff --git a/playground/hmr/index.html b/playground/hmr/index.html index 53e3390ac59168..9fac186d584d71 100644 --- a/playground/hmr/index.html +++ b/playground/hmr/index.html @@ -33,4 +33,6 @@
+ +
diff --git a/playground/hmr/intermediate-file-delete/display.js b/playground/hmr/intermediate-file-delete/display.js new file mode 100644 index 00000000000000..3ab1936b0c9009 --- /dev/null +++ b/playground/hmr/intermediate-file-delete/display.js @@ -0,0 +1 @@ +export const displayCount = (count) => `count is ${count}` diff --git a/playground/hmr/intermediate-file-delete/index.js b/playground/hmr/intermediate-file-delete/index.js new file mode 100644 index 00000000000000..4137a300f2be32 --- /dev/null +++ b/playground/hmr/intermediate-file-delete/index.js @@ -0,0 +1,17 @@ +import { displayCount } from './re-export.js' + +const button = document.querySelector('.intermediate-file-delete-increment') + +const render = () => { + document.querySelector('.intermediate-file-delete-display').textContent = + displayCount(Number(button.textContent)) +} + +render() + +button.addEventListener('click', () => { + button.textContent = `${Number(button.textContent) + 1}` + render() +}) + +if (import.meta.hot) import.meta.hot.accept() diff --git a/playground/hmr/intermediate-file-delete/re-export.js b/playground/hmr/intermediate-file-delete/re-export.js new file mode 100644 index 00000000000000..b2dade525c0675 --- /dev/null +++ b/playground/hmr/intermediate-file-delete/re-export.js @@ -0,0 +1 @@ +export * from './display.js' From 40a43824bf02ad86fe51afdb36949b0b2a11aa66 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Arnaud=20Barr=C3=A9?= Date: Thu, 19 Oct 2023 09:50:36 +0200 Subject: [PATCH 2/2] CR --- packages/vite/src/node/server/hmr.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/vite/src/node/server/hmr.ts b/packages/vite/src/node/server/hmr.ts index eaab659b2af5c0..8ebe0ab1fc78e2 100644 --- a/packages/vite/src/node/server/hmr.ts +++ b/packages/vite/src/node/server/hmr.ts @@ -224,8 +224,8 @@ export async function handleFileAddUnlink( if (isUnlink) { for (const deletedMod of modules) { - server.moduleGraph.idToModuleMap.forEach((mod) => { - mod.importers.delete(deletedMod) + deletedMod.importedModules.forEach((importedMod) => { + importedMod.importers.delete(deletedMod) }) } }