Skip to content

Commit

Permalink
feat: add vite:afterUpdate event (#9810)
Browse files Browse the repository at this point in the history
  • Loading branch information
rexxars committed Nov 3, 2022
1 parent 51ed059 commit 1f57f84
Show file tree
Hide file tree
Showing 6 changed files with 56 additions and 32 deletions.
1 change: 1 addition & 0 deletions docs/guide/api-hmr.md
Expand Up @@ -145,6 +145,7 @@ Listen to an HMR event.
The following HMR events are dispatched by Vite automatically:
- `'vite:beforeUpdate'` when an update is about to be applied (e.g. a module will be replaced)
- `'vite:afterUpdate'` when an update has just been applied (e.g. a module has been replaced)
- `'vite:beforeFullReload'` when a full reload is about to occur
- `'vite:beforePrune'` when modules that are no longer needed are about to be pruned
- `'vite:invalidate'` when a module is invalidated with `import.meta.hot.invalidate()`
Expand Down
49 changes: 30 additions & 19 deletions packages/vite/src/client/client.ts
Expand Up @@ -154,10 +154,12 @@ async function handleMessage(payload: HMRPayload) {
clearErrorOverlay()
isFirstUpdate = false
}
payload.updates.forEach((update) => {
if (update.type === 'js-update') {
queueUpdate(fetchUpdate(update))
} else {
await Promise.all(
payload.updates.map(async (update): Promise<void> => {
if (update.type === 'js-update') {
return queueUpdate(fetchUpdate(update))
}

// css-update
// this is only sent when a css file referenced with <link> is updated
const { path, timestamp } = update
Expand All @@ -171,27 +173,36 @@ async function handleMessage(payload: HMRPayload) {
(e) =>
!outdatedLinkTags.has(e) && cleanUrl(e.href).includes(searchUrl)
)
if (el) {
const newPath = `${base}${searchUrl.slice(1)}${
searchUrl.includes('?') ? '&' : '?'
}t=${timestamp}`

// rather than swapping the href on the existing tag, we will
// create a new link tag. Once the new stylesheet has loaded we
// will remove the existing link tag. This removes a Flash Of
// Unstyled Content that can occur when swapping out the tag href
// directly, as the new stylesheet has not yet been loaded.

if (!el) {
return
}

const newPath = `${base}${searchUrl.slice(1)}${
searchUrl.includes('?') ? '&' : '?'
}t=${timestamp}`

// rather than swapping the href on the existing tag, we will
// create a new link tag. Once the new stylesheet has loaded we
// will remove the existing link tag. This removes a Flash Of
// Unstyled Content that can occur when swapping out the tag href
// directly, as the new stylesheet has not yet been loaded.
return new Promise((resolve) => {
const newLinkTag = el.cloneNode() as HTMLLinkElement
newLinkTag.href = new URL(newPath, el.href).href
const removeOldEl = () => el.remove()
const removeOldEl = () => {
el.remove()
console.debug(`[vite] css hot updated: ${searchUrl}`)
resolve()
}
newLinkTag.addEventListener('load', removeOldEl)
newLinkTag.addEventListener('error', removeOldEl)
outdatedLinkTags.add(el)
el.after(newLinkTag)
}
console.debug(`[vite] css hot updated: ${searchUrl}`)
}
})
})
})
)
notifyListeners('vite:afterUpdate', payload)
break
case 'custom': {
notifyListeners(payload.event, payload.data)
Expand Down
1 change: 1 addition & 0 deletions packages/vite/types/customEvent.d.ts
Expand Up @@ -7,6 +7,7 @@ import type {

export interface CustomEventMap {
'vite:beforeUpdate': UpdatePayload
'vite:afterUpdate': UpdatePayload
'vite:beforePrune': PrunePayload
'vite:beforeFullReload': FullReloadPayload
'vite:error': ErrorPayload
Expand Down
22 changes: 15 additions & 7 deletions playground/hmr/__tests__/hmr.spec.ts
Expand Up @@ -34,7 +34,8 @@ if (!isBuild) {
'foo was: 1',
'(self-accepting 1) foo is now: 2',
'(self-accepting 2) foo is now: 2',
'[vite] hot updated: /hmr.ts'
'[vite] hot updated: /hmr.ts',
'>>> vite:afterUpdate -- update'
])
browserLogs.length = 0

Expand All @@ -46,7 +47,8 @@ if (!isBuild) {
'foo was: 2',
'(self-accepting 1) foo is now: 3',
'(self-accepting 2) foo is now: 3',
'[vite] hot updated: /hmr.ts'
'[vite] hot updated: /hmr.ts',
'>>> vite:afterUpdate -- update'
])
browserLogs.length = 0
})
Expand All @@ -67,7 +69,8 @@ if (!isBuild) {
'(single dep) nested foo is now: 1',
'(multi deps) foo is now: 2',
'(multi deps) nested foo is now: 1',
'[vite] hot updated: /hmrDep.js via /hmr.ts'
'[vite] hot updated: /hmrDep.js via /hmr.ts',
'>>> vite:afterUpdate -- update'
])
browserLogs.length = 0

Expand All @@ -84,7 +87,8 @@ if (!isBuild) {
'(single dep) nested foo is now: 1',
'(multi deps) foo is now: 3',
'(multi deps) nested foo is now: 1',
'[vite] hot updated: /hmrDep.js via /hmr.ts'
'[vite] hot updated: /hmrDep.js via /hmr.ts',
'>>> vite:afterUpdate -- update'
])
browserLogs.length = 0
})
Expand All @@ -106,7 +110,8 @@ if (!isBuild) {
'(single dep) nested foo is now: 2',
'(multi deps) foo is now: 3',
'(multi deps) nested foo is now: 2',
'[vite] hot updated: /hmrDep.js via /hmr.ts'
'[vite] hot updated: /hmrDep.js via /hmr.ts',
'>>> vite:afterUpdate -- update'
])
browserLogs.length = 0

Expand All @@ -123,7 +128,8 @@ if (!isBuild) {
'(single dep) nested foo is now: 3',
'(multi deps) foo is now: 3',
'(multi deps) nested foo is now: 3',
'[vite] hot updated: /hmrDep.js via /hmr.ts'
'[vite] hot updated: /hmrDep.js via /hmr.ts',
'>>> vite:afterUpdate -- update'
])
browserLogs.length = 0
})
Expand All @@ -140,9 +146,11 @@ if (!isBuild) {
'>>> vite:beforeUpdate -- update',
'>>> vite:invalidate -- /invalidation/child.js',
'[vite] hot updated: /invalidation/child.js',
'>>> vite:afterUpdate -- update',
'>>> vite:beforeUpdate -- update',
'(invalidation) parent is executing',
'[vite] hot updated: /invalidation/parent.js'
'[vite] hot updated: /invalidation/parent.js',
'>>> vite:afterUpdate -- update'
])
browserLogs.length = 0
})
Expand Down
9 changes: 6 additions & 3 deletions playground/hmr/hmr.ts
Expand Up @@ -45,6 +45,10 @@ if (import.meta.hot) {
console.log(`foo was:`, foo)
})

import.meta.hot.on('vite:afterUpdate', (event) => {
console.log(`>>> vite:afterUpdate -- ${event.type}`)
})

import.meta.hot.on('vite:beforeUpdate', (event) => {
console.log(`>>> vite:beforeUpdate -- ${event.type}`)

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

// We don't have a vite:afterUpdate event.
// We need to wait until the tag has been swapped out, which
// includes the time taken to download and parse the new stylesheet.
// Wait until the tag has been swapped out, which includes the time taken
// to download and parse the new stylesheet. Assert the swapped link.
const observer = new MutationObserver((mutations) => {
mutations.forEach((mutation) => {
mutation.addedNodes.forEach((node) => {
Expand Down
6 changes: 3 additions & 3 deletions playground/tailwind/__test__/tailwind.spec.ts
Expand Up @@ -79,10 +79,10 @@ if (!isBuild) {

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

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

browserLogs.length = 0
})
Expand Down

0 comments on commit 1f57f84

Please sign in to comment.