From 1d4fea950b70cd298cdcee28afea594af68934dc Mon Sep 17 00:00:00 2001 From: Espen Hovlandsdal Date: Sat, 20 Aug 2022 00:18:19 -0700 Subject: [PATCH] feat: add `vite:afterUpdate` event Closes #6379 --- docs/guide/api-hmr.md | 1 + packages/vite/src/client/client.ts | 50 ++++++++++++------- packages/vite/types/customEvent.d.ts | 1 + playground/hmr/__tests__/hmr.spec.ts | 18 ++++--- playground/hmr/hmr.ts | 9 ++-- playground/tailwind/__test__/tailwind.spec.ts | 6 +-- 6 files changed, 54 insertions(+), 31 deletions(-) diff --git a/docs/guide/api-hmr.md b/docs/guide/api-hmr.md index 3b416169f5231a..fab8a7078b5eed 100644 --- a/docs/guide/api-hmr.md +++ b/docs/guide/api-hmr.md @@ -134,6 +134,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:error'` when an error occurs (e.g. syntax error) diff --git a/packages/vite/src/client/client.ts b/packages/vite/src/client/client.ts index dad7c74ee2b8a0..82b7e31cfe64b6 100644 --- a/packages/vite/src/client/client.ts +++ b/packages/vite/src/client/client.ts @@ -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 => { + if (update.type === 'js-update') { + return queueUpdate(fetchUpdate(update)) + } + // css-update // this is only sent when a css file referenced with is updated const { path, timestamp } = update @@ -171,27 +173,37 @@ 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) { + console.log(`[vite] css hot updated: ${searchUrl}`) + 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.log(`[vite] css hot updated: ${searchUrl}`) + resolve() + } newLinkTag.addEventListener('load', removeOldEl) newLinkTag.addEventListener('error', removeOldEl) outdatedLinkTags.add(el) el.after(newLinkTag) - } - console.log(`[vite] css hot updated: ${searchUrl}`) - } - }) + }) + }) + ) + notifyListeners('vite:afterUpdate', payload) break case 'custom': { notifyListeners(payload.event, payload.data) diff --git a/packages/vite/types/customEvent.d.ts b/packages/vite/types/customEvent.d.ts index af4db5d14fbe97..783a85e7252215 100644 --- a/packages/vite/types/customEvent.d.ts +++ b/packages/vite/types/customEvent.d.ts @@ -7,6 +7,7 @@ import type { export interface CustomEventMap { 'vite:beforeUpdate': UpdatePayload + 'vite:afterUpdate': UpdatePayload 'vite:beforePrune': PrunePayload 'vite:beforeFullReload': FullReloadPayload 'vite:error': ErrorPayload diff --git a/playground/hmr/__tests__/hmr.spec.ts b/playground/hmr/__tests__/hmr.spec.ts index d0635fc04db9ee..dce86c9d41fac5 100644 --- a/playground/hmr/__tests__/hmr.spec.ts +++ b/playground/hmr/__tests__/hmr.spec.ts @@ -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 @@ -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 }) @@ -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 @@ -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 }) @@ -105,7 +109,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 @@ -122,7 +127,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 }) diff --git a/playground/hmr/hmr.ts b/playground/hmr/hmr.ts index f2d21b9bc78884..2c68d04161bfe5 100644 --- a/playground/hmr/hmr.ts +++ b/playground/hmr/hmr.ts @@ -33,6 +33,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}`) @@ -46,9 +50,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) => { diff --git a/playground/tailwind/__test__/tailwind.spec.ts b/playground/tailwind/__test__/tailwind.spec.ts index 7698ca2573387d..444110de59bd37 100644 --- a/playground/tailwind/__test__/tailwind.spec.ts +++ b/playground/tailwind/__test__/tailwind.spec.ts @@ -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 })