Skip to content

Commit 1f57f84

Browse files
authored
feat: add vite:afterUpdate event (#9810)
1 parent 51ed059 commit 1f57f84

File tree

6 files changed

+56
-32
lines changed

6 files changed

+56
-32
lines changed

docs/guide/api-hmr.md

+1
Original file line numberDiff line numberDiff line change
@@ -145,6 +145,7 @@ Listen to an HMR event.
145145
The following HMR events are dispatched by Vite automatically:
146146
147147
- `'vite:beforeUpdate'` when an update is about to be applied (e.g. a module will be replaced)
148+
- `'vite:afterUpdate'` when an update has just been applied (e.g. a module has been replaced)
148149
- `'vite:beforeFullReload'` when a full reload is about to occur
149150
- `'vite:beforePrune'` when modules that are no longer needed are about to be pruned
150151
- `'vite:invalidate'` when a module is invalidated with `import.meta.hot.invalidate()`

packages/vite/src/client/client.ts

+30-19
Original file line numberDiff line numberDiff line change
@@ -154,10 +154,12 @@ async function handleMessage(payload: HMRPayload) {
154154
clearErrorOverlay()
155155
isFirstUpdate = false
156156
}
157-
payload.updates.forEach((update) => {
158-
if (update.type === 'js-update') {
159-
queueUpdate(fetchUpdate(update))
160-
} else {
157+
await Promise.all(
158+
payload.updates.map(async (update): Promise<void> => {
159+
if (update.type === 'js-update') {
160+
return queueUpdate(fetchUpdate(update))
161+
}
162+
161163
// css-update
162164
// this is only sent when a css file referenced with <link> is updated
163165
const { path, timestamp } = update
@@ -171,27 +173,36 @@ async function handleMessage(payload: HMRPayload) {
171173
(e) =>
172174
!outdatedLinkTags.has(e) && cleanUrl(e.href).includes(searchUrl)
173175
)
174-
if (el) {
175-
const newPath = `${base}${searchUrl.slice(1)}${
176-
searchUrl.includes('?') ? '&' : '?'
177-
}t=${timestamp}`
178-
179-
// rather than swapping the href on the existing tag, we will
180-
// create a new link tag. Once the new stylesheet has loaded we
181-
// will remove the existing link tag. This removes a Flash Of
182-
// Unstyled Content that can occur when swapping out the tag href
183-
// directly, as the new stylesheet has not yet been loaded.
176+
177+
if (!el) {
178+
return
179+
}
180+
181+
const newPath = `${base}${searchUrl.slice(1)}${
182+
searchUrl.includes('?') ? '&' : '?'
183+
}t=${timestamp}`
184+
185+
// rather than swapping the href on the existing tag, we will
186+
// create a new link tag. Once the new stylesheet has loaded we
187+
// will remove the existing link tag. This removes a Flash Of
188+
// Unstyled Content that can occur when swapping out the tag href
189+
// directly, as the new stylesheet has not yet been loaded.
190+
return new Promise((resolve) => {
184191
const newLinkTag = el.cloneNode() as HTMLLinkElement
185192
newLinkTag.href = new URL(newPath, el.href).href
186-
const removeOldEl = () => el.remove()
193+
const removeOldEl = () => {
194+
el.remove()
195+
console.debug(`[vite] css hot updated: ${searchUrl}`)
196+
resolve()
197+
}
187198
newLinkTag.addEventListener('load', removeOldEl)
188199
newLinkTag.addEventListener('error', removeOldEl)
189200
outdatedLinkTags.add(el)
190201
el.after(newLinkTag)
191-
}
192-
console.debug(`[vite] css hot updated: ${searchUrl}`)
193-
}
194-
})
202+
})
203+
})
204+
)
205+
notifyListeners('vite:afterUpdate', payload)
195206
break
196207
case 'custom': {
197208
notifyListeners(payload.event, payload.data)

packages/vite/types/customEvent.d.ts

+1
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import type {
77

88
export interface CustomEventMap {
99
'vite:beforeUpdate': UpdatePayload
10+
'vite:afterUpdate': UpdatePayload
1011
'vite:beforePrune': PrunePayload
1112
'vite:beforeFullReload': FullReloadPayload
1213
'vite:error': ErrorPayload

playground/hmr/__tests__/hmr.spec.ts

+15-7
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,8 @@ if (!isBuild) {
3434
'foo was: 1',
3535
'(self-accepting 1) foo is now: 2',
3636
'(self-accepting 2) foo is now: 2',
37-
'[vite] hot updated: /hmr.ts'
37+
'[vite] hot updated: /hmr.ts',
38+
'>>> vite:afterUpdate -- update'
3839
])
3940
browserLogs.length = 0
4041

@@ -46,7 +47,8 @@ if (!isBuild) {
4647
'foo was: 2',
4748
'(self-accepting 1) foo is now: 3',
4849
'(self-accepting 2) foo is now: 3',
49-
'[vite] hot updated: /hmr.ts'
50+
'[vite] hot updated: /hmr.ts',
51+
'>>> vite:afterUpdate -- update'
5052
])
5153
browserLogs.length = 0
5254
})
@@ -67,7 +69,8 @@ if (!isBuild) {
6769
'(single dep) nested foo is now: 1',
6870
'(multi deps) foo is now: 2',
6971
'(multi deps) nested foo is now: 1',
70-
'[vite] hot updated: /hmrDep.js via /hmr.ts'
72+
'[vite] hot updated: /hmrDep.js via /hmr.ts',
73+
'>>> vite:afterUpdate -- update'
7174
])
7275
browserLogs.length = 0
7376

@@ -84,7 +87,8 @@ if (!isBuild) {
8487
'(single dep) nested foo is now: 1',
8588
'(multi deps) foo is now: 3',
8689
'(multi deps) nested foo is now: 1',
87-
'[vite] hot updated: /hmrDep.js via /hmr.ts'
90+
'[vite] hot updated: /hmrDep.js via /hmr.ts',
91+
'>>> vite:afterUpdate -- update'
8892
])
8993
browserLogs.length = 0
9094
})
@@ -106,7 +110,8 @@ if (!isBuild) {
106110
'(single dep) nested foo is now: 2',
107111
'(multi deps) foo is now: 3',
108112
'(multi deps) nested foo is now: 2',
109-
'[vite] hot updated: /hmrDep.js via /hmr.ts'
113+
'[vite] hot updated: /hmrDep.js via /hmr.ts',
114+
'>>> vite:afterUpdate -- update'
110115
])
111116
browserLogs.length = 0
112117

@@ -123,7 +128,8 @@ if (!isBuild) {
123128
'(single dep) nested foo is now: 3',
124129
'(multi deps) foo is now: 3',
125130
'(multi deps) nested foo is now: 3',
126-
'[vite] hot updated: /hmrDep.js via /hmr.ts'
131+
'[vite] hot updated: /hmrDep.js via /hmr.ts',
132+
'>>> vite:afterUpdate -- update'
127133
])
128134
browserLogs.length = 0
129135
})
@@ -140,9 +146,11 @@ if (!isBuild) {
140146
'>>> vite:beforeUpdate -- update',
141147
'>>> vite:invalidate -- /invalidation/child.js',
142148
'[vite] hot updated: /invalidation/child.js',
149+
'>>> vite:afterUpdate -- update',
143150
'>>> vite:beforeUpdate -- update',
144151
'(invalidation) parent is executing',
145-
'[vite] hot updated: /invalidation/parent.js'
152+
'[vite] hot updated: /invalidation/parent.js',
153+
'>>> vite:afterUpdate -- update'
146154
])
147155
browserLogs.length = 0
148156
})

playground/hmr/hmr.ts

+6-3
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,10 @@ if (import.meta.hot) {
4545
console.log(`foo was:`, foo)
4646
})
4747

48+
import.meta.hot.on('vite:afterUpdate', (event) => {
49+
console.log(`>>> vite:afterUpdate -- ${event.type}`)
50+
})
51+
4852
import.meta.hot.on('vite:beforeUpdate', (event) => {
4953
console.log(`>>> vite:beforeUpdate -- ${event.type}`)
5054

@@ -58,9 +62,8 @@ if (import.meta.hot) {
5862
(document.querySelector('.global-css') as HTMLLinkElement).href
5963
)
6064

61-
// We don't have a vite:afterUpdate event.
62-
// We need to wait until the tag has been swapped out, which
63-
// includes the time taken to download and parse the new stylesheet.
65+
// Wait until the tag has been swapped out, which includes the time taken
66+
// to download and parse the new stylesheet. Assert the swapped link.
6467
const observer = new MutationObserver((mutations) => {
6568
mutations.forEach((mutation) => {
6669
mutation.addedNodes.forEach((node) => {

playground/tailwind/__test__/tailwind.spec.ts

+3-3
Original file line numberDiff line numberDiff line change
@@ -79,10 +79,10 @@ if (!isBuild) {
7979

8080
await untilUpdated(() => getBgColor(el), 'rgb(220, 38, 38)')
8181

82-
expect(browserLogs).toMatchObject([
83-
'[vite] css hot updated: /index.css',
82+
expect(browserLogs).toContain('[vite] css hot updated: /index.css')
83+
expect(browserLogs).toContain(
8484
'[vite] hot updated: /src/components/PugTemplate.vue?vue&type=template&lang.js'
85-
])
85+
)
8686

8787
browserLogs.length = 0
8888
})

0 commit comments

Comments
 (0)