Skip to content

Commit 57628dc

Browse files
authored
fix(hmr): call dispose before prune (#15782)
1 parent 6d6ae10 commit 57628dc

File tree

6 files changed

+108
-37
lines changed

6 files changed

+108
-37
lines changed

packages/vite/src/client/client.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -268,7 +268,7 @@ async function handleMessage(payload: HMRPayload) {
268268
break
269269
case 'prune':
270270
notifyListeners('vite:beforePrune', payload)
271-
hmrClient.prunePaths(payload.paths)
271+
await hmrClient.prunePaths(payload.paths)
272272
break
273273
case 'error': {
274274
notifyListeners('vite:error', payload)

packages/vite/src/runtime/hmrHandler.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,7 @@ export async function handleHMRPayload(
6767
}
6868
case 'prune':
6969
await hmrClient.notifyListeners('vite:beforePrune', payload)
70-
hmrClient.prunePaths(payload.paths)
70+
await hmrClient.prunePaths(payload.paths)
7171
break
7272
case 'error': {
7373
await hmrClient.notifyListeners('vite:error', payload)

packages/vite/src/shared/hmr.ts

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -232,8 +232,13 @@ export class HMRClient {
232232
// After an HMR update, some modules are no longer imported on the page
233233
// but they may have left behind side effects that need to be cleaned up
234234
// (.e.g style injections)
235-
// TODO Trigger their dispose callbacks.
236-
public prunePaths(paths: string[]): void {
235+
public async prunePaths(paths: string[]): Promise<void> {
236+
await Promise.all(
237+
paths.map((path) => {
238+
const disposer = this.disposeMap.get(path)
239+
if (disposer) return disposer(this.dataMap.get(path))
240+
}),
241+
)
237242
paths.forEach((path) => {
238243
const fn = this.pruneMap.get(path)
239244
if (fn) {

playground/hmr-ssr/__tests__/hmr.spec.ts renamed to playground/hmr-ssr/__tests__/hmr-ssr.spec.ts

Lines changed: 51 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,14 @@ import type { InlineConfig, Logger, ViteDevServer } from 'vite'
77
import { createServer, createViteRuntime } from 'vite'
88
import type { ViteRuntime } from 'vite/runtime'
99
import type { RollupError } from 'rollup'
10-
import { page, promiseWithResolvers, slash, untilUpdated } from '~utils'
10+
import {
11+
addFile,
12+
page,
13+
promiseWithResolvers,
14+
readFile,
15+
slash,
16+
untilUpdated,
17+
} from '~utils'
1118

1219
let server: ViteDevServer
1320
const clientLogs: string[] = []
@@ -737,31 +744,19 @@ test.todo('should hmr when file is deleted and restored', async () => {
737744
)
738745
await untilUpdated(() => hmr('.file-delete-restore'), 'parent:child1')
739746

747+
// delete the file
740748
editFile(parentFile, (code) =>
741749
code.replace(
742750
"export { value as childValue } from './child'",
743751
"export const childValue = 'not-child'",
744752
),
745753
)
754+
const originalChildFileCode = readFile(childFile)
746755
removeFile(childFile)
747756
await untilUpdated(() => hmr('.file-delete-restore'), 'parent:not-child')
748757

749-
createFile(
750-
childFile,
751-
`
752-
import { rerender } from './runtime'
753-
754-
export const value = 'child'
755-
756-
if (import.meta.hot) {
757-
import.meta.hot.accept((newMod) => {
758-
if (!newMod) return
759-
760-
rerender({ child: newMod.value })
761-
})
762-
}
763-
`,
764-
)
758+
// restore the file
759+
createFile(childFile, originalChildFileCode)
765760
editFile(parentFile, (code) =>
766761
code.replace(
767762
"export const childValue = 'not-child'",
@@ -822,6 +817,45 @@ test.todo('delete file should not break hmr', async () => {
822817
)
823818
})
824819

820+
test.todo(
821+
'deleted file should trigger dispose and prune callbacks',
822+
async () => {
823+
await setupViteRuntime('/hmr.ts')
824+
825+
const parentFile = 'file-delete-restore/parent.js'
826+
const childFile = 'file-delete-restore/child.js'
827+
828+
// delete the file
829+
editFile(parentFile, (code) =>
830+
code.replace(
831+
"export { value as childValue } from './child'",
832+
"export const childValue = 'not-child'",
833+
),
834+
)
835+
const originalChildFileCode = readFile(childFile)
836+
removeFile(childFile)
837+
await untilUpdated(
838+
() => page.textContent('.file-delete-restore'),
839+
'parent:not-child',
840+
)
841+
expect(clientLogs).to.include('file-delete-restore/child.js is disposed')
842+
expect(clientLogs).to.include('file-delete-restore/child.js is pruned')
843+
844+
// restore the file
845+
addFile(childFile, originalChildFileCode)
846+
editFile(parentFile, (code) =>
847+
code.replace(
848+
"export const childValue = 'not-child'",
849+
"export { value as childValue } from './child'",
850+
),
851+
)
852+
await untilUpdated(
853+
() => page.textContent('.file-delete-restore'),
854+
'parent:child',
855+
)
856+
},
857+
)
858+
825859
test('import.meta.hot?.accept', async () => {
826860
await setupViteRuntime('/hmr.ts')
827861
await untilConsoleLogAfter(

playground/hmr/__tests__/hmr.spec.ts

Lines changed: 40 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import {
88
getColor,
99
isBuild,
1010
page,
11+
readFile,
1112
removeFile,
1213
serverLogs,
1314
untilBrowserLogAfter,
@@ -784,34 +785,21 @@ if (!isBuild) {
784785
'parent:child1',
785786
)
786787

788+
// delete the file
787789
editFile(parentFile, (code) =>
788790
code.replace(
789791
"export { value as childValue } from './child'",
790792
"export const childValue = 'not-child'",
791793
),
792794
)
795+
const originalChildFileCode = readFile(childFile)
793796
removeFile(childFile)
794797
await untilUpdated(
795798
() => page.textContent('.file-delete-restore'),
796799
'parent:not-child',
797800
)
798801

799-
addFile(
800-
childFile,
801-
`
802-
import { rerender } from './runtime'
803-
804-
export const value = 'child'
805-
806-
if (import.meta.hot) {
807-
import.meta.hot.accept((newMod) => {
808-
if (!newMod) return
809-
810-
rerender({ child: newMod.value })
811-
})
812-
}
813-
`,
814-
)
802+
addFile(childFile, originalChildFileCode)
815803
editFile(parentFile, (code) =>
816804
code.replace(
817805
"export const childValue = 'not-child'",
@@ -875,6 +863,42 @@ if (import.meta.hot) {
875863
)
876864
})
877865

866+
test('deleted file should trigger dispose and prune callbacks', async () => {
867+
await page.goto(viteTestUrl)
868+
869+
const parentFile = 'file-delete-restore/parent.js'
870+
const childFile = 'file-delete-restore/child.js'
871+
872+
// delete the file
873+
editFile(parentFile, (code) =>
874+
code.replace(
875+
"export { value as childValue } from './child'",
876+
"export const childValue = 'not-child'",
877+
),
878+
)
879+
const originalChildFileCode = readFile(childFile)
880+
removeFile(childFile)
881+
await untilUpdated(
882+
() => page.textContent('.file-delete-restore'),
883+
'parent:not-child',
884+
)
885+
expect(browserLogs).to.include('file-delete-restore/child.js is disposed')
886+
expect(browserLogs).to.include('file-delete-restore/child.js is pruned')
887+
888+
// restore the file
889+
addFile(childFile, originalChildFileCode)
890+
editFile(parentFile, (code) =>
891+
code.replace(
892+
"export const childValue = 'not-child'",
893+
"export { value as childValue } from './child'",
894+
),
895+
)
896+
await untilUpdated(
897+
() => page.textContent('.file-delete-restore'),
898+
'parent:child',
899+
)
900+
})
901+
878902
test('import.meta.hot?.accept', async () => {
879903
const el = await page.$('.optional-chaining')
880904
await untilBrowserLogAfter(

playground/hmr/file-delete-restore/child.js

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,4 +8,12 @@ if (import.meta.hot) {
88

99
rerender({ child: newMod.value })
1010
})
11+
12+
import.meta.hot.dispose(() => {
13+
console.log('file-delete-restore/child.js is disposed')
14+
})
15+
16+
import.meta.hot.prune(() => {
17+
console.log('file-delete-restore/child.js is pruned')
18+
})
1119
}

0 commit comments

Comments
 (0)