Skip to content

Commit 8d26785

Browse files
authored
fix(hmr): use monotonicDateNow for timestamp (#20158)
1 parent 66309e8 commit 8d26785

File tree

9 files changed

+42
-19
lines changed

9 files changed

+42
-19
lines changed

docs/guide/api-environment-instances.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -185,7 +185,7 @@ export class EnvironmentModuleGraph {
185185
invalidateModule(
186186
mod: EnvironmentModuleNode,
187187
seen: Set<EnvironmentModuleNode> = new Set(),
188-
timestamp: number = Date.now(),
188+
timestamp: number = monotonicDateNow(),
189189
isHmr: boolean = false,
190190
): void
191191

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import type {
77
ResolvedConfig,
88
ResolvedEnvironmentOptions,
99
} from '../config'
10-
import { mergeConfig } from '../utils'
10+
import { mergeConfig, monotonicDateNow } from '../utils'
1111
import { fetchModule } from '../ssr/fetchModule'
1212
import type { DepsOptimizer } from '../optimizer'
1313
import { isDepOptimizationDisabled } from '../optimizer'
@@ -202,7 +202,7 @@ export class DevEnvironment extends BaseEnvironment {
202202

203203
async reloadModule(module: EnvironmentModuleNode): Promise<void> {
204204
if (this.config.server.hmr !== false && module.file) {
205-
updateModules(this, module.file, [module], Date.now())
205+
updateModules(this, module.file, [module], monotonicDateNow())
206206
}
207207
}
208208

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

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,12 @@ import type {
1010
InvokeSendData,
1111
} from '../../shared/invokeMethods'
1212
import { CLIENT_DIR } from '../constants'
13-
import { createDebugger, isCSSRequest, normalizePath } from '../utils'
13+
import {
14+
createDebugger,
15+
isCSSRequest,
16+
monotonicDateNow,
17+
normalizePath,
18+
} from '../utils'
1419
import type { InferCustomEventPayload, ViteDevServer } from '..'
1520
import { getHookHandler } from '../plugins'
1621
import { isExplicitImportRequired } from '../plugins/importAnalysis'
@@ -418,7 +423,7 @@ export async function handleHMRUpdate(
418423
return
419424
}
420425

421-
const timestamp = Date.now()
426+
const timestamp = monotonicDateNow()
422427
const contextMeta = {
423428
type,
424429
file,
@@ -930,7 +935,7 @@ export function handlePrunedModules(
930935
// update the disposed modules' hmr timestamp
931936
// since if it's re-imported, it should re-apply side effects
932937
// and without the timestamp the browser will not re-import it!
933-
const t = Date.now()
938+
const t = monotonicDateNow()
934939
mods.forEach((mod) => {
935940
mod.lastHMRTimestamp = t
936941
mod.lastHMRInvalidationReceived = false

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

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ import {
3232
isParentDirectory,
3333
mergeConfig,
3434
mergeWithDefaults,
35+
monotonicDateNow,
3536
normalizePath,
3637
resolveHostname,
3738
resolveServerUrls,
@@ -650,7 +651,7 @@ export async function _createServer(
650651
environments[environmentModule.environment]!,
651652
module.file,
652653
[environmentModule],
653-
Date.now(),
654+
monotonicDateNow(),
654655
)
655656
}
656657
},

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

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import type { ModuleInfo } from 'rollup'
2+
import { monotonicDateNow } from '../utils'
23
import type { TransformResult } from './transformRequest'
34
import type {
45
EnvironmentModuleGraph,
@@ -387,7 +388,7 @@ export class ModuleGraph {
387388
invalidateModule(
388389
mod: ModuleNode,
389390
seen = new Set<ModuleNode>(),
390-
timestamp: number = Date.now(),
391+
timestamp: number = monotonicDateNow(),
391392
isHmr: boolean = false,
392393
/** @internal */
393394
softInvalidate = false,

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

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import { extname } from 'node:path'
22
import type { ModuleInfo, PartialResolvedId } from 'rollup'
33
import { isDirectCSSRequest } from '../plugins/css'
44
import {
5+
monotonicDateNow,
56
normalizePath,
67
removeImportQuery,
78
removeTimestampQuery,
@@ -165,7 +166,7 @@ export class EnvironmentModuleGraph {
165166
invalidateModule(
166167
mod: EnvironmentModuleNode,
167168
seen = new Set<EnvironmentModuleNode>(),
168-
timestamp: number = Date.now(),
169+
timestamp: number = monotonicDateNow(),
169170
isHmr: boolean = false,
170171
/** @internal */
171172
softInvalidate = false,
@@ -233,7 +234,7 @@ export class EnvironmentModuleGraph {
233234
}
234235

235236
invalidateAll(): void {
236-
const timestamp = Date.now()
237+
const timestamp = monotonicDateNow()
237238
const seen = new Set<EnvironmentModuleNode>()
238239
this.idToModuleMap.forEach((mod) => {
239240
this.invalidateModule(mod, seen, timestamp)

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

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import {
1212
ensureWatchedFile,
1313
injectQuery,
1414
isObject,
15+
monotonicDateNow,
1516
prettifyUrl,
1617
removeImportQuery,
1718
removeTimestampQuery,
@@ -105,7 +106,7 @@ export function transformRequest(
105106
//
106107
// We save the timestamp when we start processing and compare it with the
107108
// last time this module is invalidated
108-
const timestamp = Date.now()
109+
const timestamp = monotonicDateNow()
109110

110111
const pending = environment._pendingRequests.get(cacheKey)
111112
if (pending) {

packages/vite/src/node/utils.ts

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1648,3 +1648,23 @@ export function getServerUrlByHost(
16481648
}
16491649
return resolvedUrls?.local[0] ?? resolvedUrls?.network[0]
16501650
}
1651+
1652+
let lastDateNow = 0
1653+
/**
1654+
* Similar to `Date.now()`, but strictly monotonically increasing.
1655+
*
1656+
* This function will never return the same value.
1657+
* Thus, the value may differ from the actual time.
1658+
*
1659+
* related: https://github.com/vitejs/vite/issues/19804
1660+
*/
1661+
export function monotonicDateNow(): number {
1662+
const now = Date.now()
1663+
if (now > lastDateNow) {
1664+
lastDateNow = now
1665+
return lastDateNow
1666+
}
1667+
1668+
lastDateNow++
1669+
return lastDateNow
1670+
}

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

Lines changed: 2 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -864,8 +864,7 @@ if (!isBuild) {
864864
await untilUpdated(() => hmr('.optional-chaining')?.toString(), '2')
865865
})
866866

867-
// TODO: this is flaky due to https://github.com/vitejs/vite/issues/19804
868-
test.skip('hmr works for self-accepted module within circular imported files', async () => {
867+
test('hmr works for self-accepted module within circular imported files', async () => {
869868
await setupModuleRunner('/self-accept-within-circular/index')
870869
const el = () => hmr('.self-accept-within-circular')
871870
expect(el()).toBe('c')
@@ -882,12 +881,7 @@ if (!isBuild) {
882881
await untilUpdated(() => el(), 'cc')
883882
})
884883

885-
test('hmr should not reload if no accepted within circular imported files', async (ctx) => {
886-
// TODO: Investigate race condition that causes an inconsistent behaviour for the last `untilUpdated`
887-
// assertion where it'll sometimes receive "mod-a -> mod-b (edited) -> mod-c -> mod-a (expected no error)"
888-
// This is probably related to https://github.com/vitejs/vite/issues/19804
889-
ctx.skip()
890-
884+
test('hmr should not reload if no accepted within circular imported files', async () => {
891885
await setupModuleRunner('/circular/index')
892886
const el = () => hmr('.circular')
893887
expect(el()).toBe(

0 commit comments

Comments
 (0)