Skip to content

Commit 21cc10b

Browse files
authored
fix(hmr): multiple updates happened when invalidate is called while multiple tabs open (#16307)
1 parent 01af308 commit 21cc10b

File tree

4 files changed

+50
-1
lines changed

4 files changed

+50
-1
lines changed

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -533,6 +533,7 @@ export function handlePrunedModules(
533533
const t = Date.now()
534534
mods.forEach((mod) => {
535535
mod.lastHMRTimestamp = t
536+
mod.lastHMRInvalidationReceived = false
536537
debugHmr?.(`[dispose] ${colors.dim(mod.file)}`)
537538
})
538539
hot.send({

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

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -787,7 +787,13 @@ export async function _createServer(
787787

788788
hot.on('vite:invalidate', async ({ path, message }) => {
789789
const mod = moduleGraph.urlToModuleMap.get(path)
790-
if (mod && mod.isSelfAccepting && mod.lastHMRTimestamp > 0) {
790+
if (
791+
mod &&
792+
mod.isSelfAccepting &&
793+
mod.lastHMRTimestamp > 0 &&
794+
!mod.lastHMRInvalidationReceived
795+
) {
796+
mod.lastHMRInvalidationReceived = true
791797
config.logger.info(
792798
colors.yellow(`hmr invalidate `) +
793799
colors.dim(path) +

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

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,13 @@ export class ModuleNode {
3535
ssrModule: Record<string, any> | null = null
3636
ssrError: Error | null = null
3737
lastHMRTimestamp = 0
38+
/**
39+
* `import.meta.hot.invalidate` is called by the client.
40+
* If there's multiple clients, multiple `invalidate` request is received.
41+
* This property is used to dedupe those request to avoid multiple updates happening.
42+
* @internal
43+
*/
44+
lastHMRInvalidationReceived = false
3845
lastInvalidationTimestamp = 0
3946
/**
4047
* If the module only needs to update its imports timestamp (e.g. within an HMR chain),
@@ -199,6 +206,7 @@ export class ModuleGraph {
199206

200207
if (isHmr) {
201208
mod.lastHMRTimestamp = timestamp
209+
mod.lastHMRInvalidationReceived = false
202210
} else {
203211
// Save the timestamp for this invalidation, so we can avoid caching the result of possible already started
204212
// processing being done for this module

playground/hmr/__tests__/hmr.spec.ts

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
import { beforeAll, describe, expect, it, test } from 'vitest'
2+
import type { Page } from 'playwright-chromium'
23
import { hasWindowsUnicodeFsBug } from '../../hasWindowsUnicodeFsBug'
34
import {
45
addFile,
6+
browser,
57
browserLogs,
68
editFile,
79
getBg,
@@ -175,6 +177,38 @@ if (!isBuild) {
175177
await untilUpdated(() => el.textContent(), 'child updated')
176178
})
177179

180+
test('invalidate works with multiple tabs', async () => {
181+
let page2: Page
182+
try {
183+
page2 = await browser.newPage()
184+
await page2.goto(viteTestUrl)
185+
186+
const el = await page.$('.invalidation')
187+
await untilBrowserLogAfter(
188+
() =>
189+
editFile('invalidation/child.js', (code) =>
190+
code.replace('child', 'child updated'),
191+
),
192+
[
193+
'>>> vite:beforeUpdate -- update',
194+
'>>> vite:invalidate -- /invalidation/child.js',
195+
'[vite] invalidate /invalidation/child.js',
196+
'[vite] hot updated: /invalidation/child.js',
197+
'>>> vite:afterUpdate -- update',
198+
// if invalidate dedupe doesn't work correctly, this beforeUpdate will be called twice
199+
'>>> vite:beforeUpdate -- update',
200+
'(invalidation) parent is executing',
201+
'[vite] hot updated: /invalidation/parent.js',
202+
'>>> vite:afterUpdate -- update',
203+
],
204+
true,
205+
)
206+
await untilUpdated(() => el.textContent(), 'child updated')
207+
} finally {
208+
await page2.close()
209+
}
210+
})
211+
178212
test('soft invalidate', async () => {
179213
const el = await page.$('.soft-invalidation')
180214
expect(await el.textContent()).toBe(

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy