Skip to content

Commit 7acb016

Browse files
authored
fix(hmr): clean importers in module graph when file is deleted (#14315)
1 parent 9d09dfe commit 7acb016

File tree

8 files changed

+86
-4
lines changed

8 files changed

+86
-4
lines changed

packages/vite/src/node/server/hmr.ts

+9
Original file line numberDiff line numberDiff line change
@@ -218,9 +218,18 @@ export function updateModules(
218218
export async function handleFileAddUnlink(
219219
file: string,
220220
server: ViteDevServer,
221+
isUnlink: boolean,
221222
): Promise<void> {
222223
const modules = [...(server.moduleGraph.getModulesByFile(file) || [])]
223224

225+
if (isUnlink) {
226+
for (const deletedMod of modules) {
227+
deletedMod.importedModules.forEach((importedMod) => {
228+
importedMod.importers.delete(deletedMod)
229+
})
230+
}
231+
}
232+
224233
modules.push(...getAffectedGlobModules(file, server))
225234

226235
if (modules.length > 0) {

packages/vite/src/node/server/index.ts

+4-4
Original file line numberDiff line numberDiff line change
@@ -577,9 +577,9 @@ export async function _createServer(
577577
}
578578
}
579579

580-
const onFileAddUnlink = async (file: string) => {
580+
const onFileAddUnlink = async (file: string, isUnlink: boolean) => {
581581
file = normalizePath(file)
582-
await handleFileAddUnlink(file, server)
582+
await handleFileAddUnlink(file, server, isUnlink)
583583
await onHMRUpdate(file, true)
584584
}
585585

@@ -591,8 +591,8 @@ export async function _createServer(
591591
await onHMRUpdate(file, false)
592592
})
593593

594-
watcher.on('add', onFileAddUnlink)
595-
watcher.on('unlink', onFileAddUnlink)
594+
watcher.on('add', (file) => onFileAddUnlink(file, false))
595+
watcher.on('unlink', (file) => onFileAddUnlink(file, true))
596596

597597
ws.on('vite:invalidate', async ({ path, message }: InvalidatePayload) => {
598598
const mod = moduleGraph.urlToModuleMap.get(path)

playground/hmr/__tests__/hmr.spec.ts

+51
Original file line numberDiff line numberDiff line change
@@ -793,6 +793,57 @@ if (import.meta.hot) {
793793
)
794794
})
795795

796+
test('delete file should not break hmr', async () => {
797+
await page.goto(viteTestUrl)
798+
799+
await untilUpdated(
800+
() => page.textContent('.intermediate-file-delete-display'),
801+
'count is 1',
802+
)
803+
804+
// add state
805+
await page.click('.intermediate-file-delete-increment')
806+
await untilUpdated(
807+
() => page.textContent('.intermediate-file-delete-display'),
808+
'count is 2',
809+
)
810+
811+
// update import, hmr works
812+
editFile('intermediate-file-delete/index.js', (code) =>
813+
code.replace("from './re-export.js'", "from './display.js'"),
814+
)
815+
editFile('intermediate-file-delete/display.js', (code) =>
816+
code.replace('count is ${count}', 'count is ${count}!'),
817+
)
818+
await untilUpdated(
819+
() => page.textContent('.intermediate-file-delete-display'),
820+
'count is 2!',
821+
)
822+
823+
// remove unused file, page reload because it's considered entry point now
824+
removeFile('intermediate-file-delete/re-export.js')
825+
await untilUpdated(
826+
() => page.textContent('.intermediate-file-delete-display'),
827+
'count is 1!',
828+
)
829+
830+
// re-add state
831+
await page.click('.intermediate-file-delete-increment')
832+
await untilUpdated(
833+
() => page.textContent('.intermediate-file-delete-display'),
834+
'count is 2!',
835+
)
836+
837+
// hmr works after file deletion
838+
editFile('intermediate-file-delete/display.js', (code) =>
839+
code.replace('count is ${count}!', 'count is ${count}'),
840+
)
841+
await untilUpdated(
842+
() => page.textContent('.intermediate-file-delete-display'),
843+
'count is 2',
844+
)
845+
})
846+
796847
test('import.meta.hot?.accept', async () => {
797848
const el = await page.$('.optional-chaining')
798849
await untilBrowserLogAfter(

playground/hmr/hmr.ts

+1
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import './importing-updated'
44
import './invalidation/parent'
55
import './file-delete-restore'
66
import './optional-chaining/parent'
7+
import './intermediate-file-delete'
78
import logo from './logo.svg'
89

910
export const foo = 1

playground/hmr/index.html

+2
Original file line numberDiff line numberDiff line change
@@ -33,4 +33,6 @@
3333
<div class="importing-reloaded"></div>
3434
<div class="file-delete-restore"></div>
3535
<div class="optional-chaining"></div>
36+
<button class="intermediate-file-delete-increment">1</button>
37+
<div class="intermediate-file-delete-display"></div>
3638
<image id="logo"></image>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export const displayCount = (count) => `count is ${count}`
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
import { displayCount } from './re-export.js'
2+
3+
const button = document.querySelector('.intermediate-file-delete-increment')
4+
5+
const render = () => {
6+
document.querySelector('.intermediate-file-delete-display').textContent =
7+
displayCount(Number(button.textContent))
8+
}
9+
10+
render()
11+
12+
button.addEventListener('click', () => {
13+
button.textContent = `${Number(button.textContent) + 1}`
14+
render()
15+
})
16+
17+
if (import.meta.hot) import.meta.hot.accept()
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export * from './display.js'

0 commit comments

Comments
 (0)