Skip to content

Commit

Permalink
fix(hmr): clean importers in module graph when file is deleted
Browse files Browse the repository at this point in the history
  • Loading branch information
ArnaudBarre committed Oct 19, 2023
1 parent 5acda5e commit 27fe596
Show file tree
Hide file tree
Showing 8 changed files with 86 additions and 4 deletions.
9 changes: 9 additions & 0 deletions packages/vite/src/node/server/hmr.ts
Original file line number Diff line number Diff line change
Expand Up @@ -218,9 +218,18 @@ export function updateModules(
export async function handleFileAddUnlink(
file: string,
server: ViteDevServer,
isUnlink: boolean,
): Promise<void> {
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) {
Expand Down
8 changes: 4 additions & 4 deletions packages/vite/src/node/server/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}

Expand All @@ -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)
Expand Down
51 changes: 51 additions & 0 deletions playground/hmr/__tests__/hmr.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand Down
1 change: 1 addition & 0 deletions playground/hmr/hmr.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 2 additions & 0 deletions playground/hmr/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -33,4 +33,6 @@
<div class="importing-reloaded"></div>
<div class="file-delete-restore"></div>
<div class="optional-chaining"></div>
<button class="intermediate-file-delete-increment">1</button>
<div class="intermediate-file-delete-display"></div>
<image id="logo"></image>
1 change: 1 addition & 0 deletions playground/hmr/intermediate-file-delete/display.js
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export const displayCount = (count) => `count is ${count}`
17 changes: 17 additions & 0 deletions playground/hmr/intermediate-file-delete/index.js
Original file line number Diff line number Diff line change
@@ -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()
1 change: 1 addition & 0 deletions playground/hmr/intermediate-file-delete/re-export.js
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from './display.js'

0 comments on commit 27fe596

Please sign in to comment.