diff --git a/docs/guide/api-hmr.md b/docs/guide/api-hmr.md index d5d716fb13df79..13f76239892331 100644 --- a/docs/guide/api-hmr.md +++ b/docs/guide/api-hmr.md @@ -30,7 +30,7 @@ interface ViteHotContext { dispose(cb: (data: any) => void): void decline(): void - invalidate(): void + invalidate(message?: string): void // `InferCustomEventPayload` provides types for built-in Vite events on( @@ -123,9 +123,9 @@ The `import.meta.hot.data` object is persisted across different instances of the Calling `import.meta.hot.decline()` indicates this module is not hot-updatable, and the browser should perform a full reload if this module is encountered while propagating HMR updates. -## `hot.invalidate()` +## `hot.invalidate(message?: string)` -A self-accepting module may realize during runtime that it can't handle a HMR update, and so the update needs to be forcefully propagated to importers. By calling `import.meta.hot.invalidate()`, the HMR server will invalidate the importers of the caller, as if the caller wasn't self-accepting. +A self-accepting module may realize during runtime that it can't handle a HMR update, and so the update needs to be forcefully propagated to importers. By calling `import.meta.hot.invalidate()`, the HMR server will invalidate the importers of the caller, as if the caller wasn't self-accepting. This will log a message both in the browser console and in the terminal. You can pass a message to give some context on why the invalidation happened. Note that you should always call `import.meta.hot.accept` even if you plan to call `invalidate` immediately afterwards, or else the HMR client won't listen for future changes to the self-accepting module. To communicate your intent clearly, we recommend calling `invalidate` within the `accept` callback like so: diff --git a/packages/vite/src/client/client.ts b/packages/vite/src/client/client.ts index 4f19efea33d295..12e2a95b383ff1 100644 --- a/packages/vite/src/client/client.ts +++ b/packages/vite/src/client/client.ts @@ -558,9 +558,12 @@ export function createHotContext(ownerPath: string): ViteHotContext { decline() {}, // tell the server to re-perform hmr propagation from this module as root - invalidate() { - notifyListeners('vite:invalidate', { path: ownerPath }) - this.send('vite:invalidate', { path: ownerPath }) + invalidate(message) { + notifyListeners('vite:invalidate', { path: ownerPath, message }) + this.send('vite:invalidate', { path: ownerPath, message }) + console.debug( + `[vite] invalidate ${ownerPath}${message ? `: ${message}` : ''}` + ) }, // custom events diff --git a/packages/vite/src/node/server/hmr.ts b/packages/vite/src/node/server/hmr.ts index 64c031223a2df9..7046f7b44954c6 100644 --- a/packages/vite/src/node/server/hmr.ts +++ b/packages/vite/src/node/server/hmr.ts @@ -128,7 +128,8 @@ export function updateModules( file: string, modules: ModuleNode[], timestamp: number, - { config, ws }: ViteDevServer + { config, ws }: ViteDevServer, + afterInvalidation?: boolean ): void { const updates: Update[] = [] const invalidatedModules = new Set() @@ -166,7 +167,7 @@ export function updateModules( if (needFullReload) { config.logger.info(colors.green(`page reload `) + colors.dim(file), { - clear: true, + clear: !afterInvalidation, timestamp: true }) ws.send({ @@ -183,7 +184,7 @@ export function updateModules( config.logger.info( colors.green(`hmr update `) + colors.dim([...new Set(updates.map((u) => u.path))].join(', ')), - { clear: true, timestamp: true } + { clear: !afterInvalidation, timestamp: true } ) ws.send({ type: 'update', diff --git a/packages/vite/src/node/server/index.ts b/packages/vite/src/node/server/index.ts index f84637b30b3b0e..df748f2fd15844 100644 --- a/packages/vite/src/node/server/index.ts +++ b/packages/vite/src/node/server/index.ts @@ -505,11 +505,23 @@ export async function createServer( handleFileAddUnlink(normalizePath(file), server) }) - ws.on('vite:invalidate', async ({ path }: InvalidatePayload) => { + ws.on('vite:invalidate', async ({ path, message }: InvalidatePayload) => { const mod = moduleGraph.urlToModuleMap.get(path) if (mod && mod.isSelfAccepting && mod.lastHMRTimestamp > 0) { + config.logger.info( + colors.yellow(`hmr invalidate `) + + colors.dim(path) + + (message ? ` ${message}` : ''), + { timestamp: true } + ) const file = getShortName(mod.file!, config.root) - updateModules(file, [...mod.importers], mod.lastHMRTimestamp, server) + updateModules( + file, + [...mod.importers], + mod.lastHMRTimestamp, + server, + true + ) } }) diff --git a/packages/vite/types/customEvent.d.ts b/packages/vite/types/customEvent.d.ts index 64d0c19d1e4aa1..86d0807e0b9256 100644 --- a/packages/vite/types/customEvent.d.ts +++ b/packages/vite/types/customEvent.d.ts @@ -16,6 +16,7 @@ export interface CustomEventMap { export interface InvalidatePayload { path: string + message: string | undefined } export type InferCustomEventPayload = diff --git a/packages/vite/types/hot.d.ts b/packages/vite/types/hot.d.ts index 985f3a47197c43..d51bb6e6156e1c 100644 --- a/packages/vite/types/hot.d.ts +++ b/packages/vite/types/hot.d.ts @@ -23,7 +23,7 @@ export interface ViteHotContext { dispose(cb: (data: any) => void): void decline(): void - invalidate(): void + invalidate(message?: string): void on( event: T, diff --git a/playground/hmr/__tests__/hmr.spec.ts b/playground/hmr/__tests__/hmr.spec.ts index 30d0b4da05b177..7cd70a3d8a4777 100644 --- a/playground/hmr/__tests__/hmr.spec.ts +++ b/playground/hmr/__tests__/hmr.spec.ts @@ -145,6 +145,7 @@ if (!isBuild) { expect(browserLogs).toMatchObject([ '>>> vite:beforeUpdate -- update', '>>> vite:invalidate -- /invalidation/child.js', + '[vite] invalidate /invalidation/child.js', '[vite] hot updated: /invalidation/child.js', '>>> vite:afterUpdate -- update', '>>> vite:beforeUpdate -- update', diff --git a/playground/react/__tests__/react.spec.ts b/playground/react/__tests__/react.spec.ts index 654a45a668e894..6cd9db4f6d8440 100644 --- a/playground/react/__tests__/react.spec.ts +++ b/playground/react/__tests__/react.spec.ts @@ -54,6 +54,7 @@ if (!isBuild) { ) await untilUpdated(() => page.textContent('#parent'), 'Updated') expect(browserLogs).toMatchObject([ + '[vite] invalidate /hmr/no-exported-comp.jsx', '[vite] hot updated: /hmr/no-exported-comp.jsx', '[vite] hot updated: /hmr/parent.jsx', 'Parent rendered' @@ -79,6 +80,7 @@ if (!isBuild) { 'context provider updated' ) expect(browserLogs).toMatchObject([ + '[vite] invalidate /context/CountProvider.jsx', '[vite] hot updated: /context/CountProvider.jsx', '[vite] hot updated: /App.jsx', '[vite] hot updated: /context/ContextButton.jsx',