Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add vite:afterUpdate event #9810

Merged
merged 3 commits into from Nov 3, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
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