diff --git a/docs/guide/api-hmr.md b/docs/guide/api-hmr.md index 10b7b302654126..adea2454038f9b 100644 --- a/docs/guide/api-hmr.md +++ b/docs/guide/api-hmr.md @@ -1,6 +1,8 @@ # HMR API :::tip Note +This is the client HMR API. For handling HMR update in plugins, see [handleHotUpdate](./api-plugin#handlehotupdate). + The manual HMR API is primarly intended for framework and tooling authors. As an end user, HMR is likely already handled for you in the framework specific starter templates. ::: diff --git a/docs/guide/api-plugin.md b/docs/guide/api-plugin.md index 5d3b86323e77ed..ccb7e8f02e788c 100644 --- a/docs/guide/api-plugin.md +++ b/docs/guide/api-plugin.md @@ -271,17 +271,23 @@ Vite plugins can also provide hooks that serve Vite-specific purposes. These hoo ### `handleHotUpdate` -- **Type:** `(file: string, mods: Array, read: () => string | Promise, server: ViteDevServer) => Array | void | Promise | void>` +- **Type:** `(ctx: HmrContext) => Array | void | Promise | void>` - Perform custom HMR update handling. The hook receives the following arguments: - - 1. The changed file path + Perform custom HMR update handling. The hook receives a context object with the following signature: - 2. An array of modules that are affected by the changed file. It's an array because a single file may map to multiple served modules (e.g. Vue SFCs). + ```ts + interface HmrContext { + file: string + timestamp: number + modules: Array + read: () => string | Promise + server: ViteDevServer + } + ``` - 3. An async read function that returns the content of the file. This is provided because on some systems, the file change callback may fire too fast before the editor finishes updating the file and direct `fs.readFile` will return empty content. The read function passed in normalizes this behavior. + - `modules` is an array of modules that are affected by the changed file. It's an array because a single file may map to multiple served modules (e.g. Vue SFCs). - 4. The [`ViteDevServer`](./api-javascript#vitedevserver) instance. + - `read` is an async read function that returns the content of the file. This is provided because on some systems, the file change callback may fire too fast before the editor finishes updating the file and direct `fs.readFile` will return empty content. The read function passed in normalizes this behavior. The hook can choose to: @@ -290,7 +296,7 @@ Vite plugins can also provide hooks that serve Vite-specific purposes. These hoo - Return an empty array and perform complete custom HMR handling by sending custom events to the client: ```js - handleHotUpdate(file, mods, read, server) { + handleHotUpdate({ server }) { server.ws.send({ type: 'custom', event: 'special-update', @@ -304,7 +310,7 @@ Vite plugins can also provide hooks that serve Vite-specific purposes. These hoo ```js if (import.meta.hot) { - import.meta.hot.on('special-update', data => { + import.meta.hot.on('special-update', (data) => { // perform custom update }) } diff --git a/packages/playground/hmr/vite.config.js b/packages/playground/hmr/vite.config.js index 6830086fce91e5..c34637844e2170 100644 --- a/packages/playground/hmr/vite.config.js +++ b/packages/playground/hmr/vite.config.js @@ -5,7 +5,7 @@ module.exports = { plugins: [ { name: 'mock-custom', - async handleHotUpdate(file, mods, read, server) { + async handleHotUpdate({ file, read, server }) { if (file.endsWith('customFile.js')) { const content = await read() const msg = content.match(/export const msg = '(\w+)'/)[1] diff --git a/packages/plugin-vue/src/handleHotUpdate.ts b/packages/plugin-vue/src/handleHotUpdate.ts index bbd126ba4cfbfb..00455a7e0804d9 100644 --- a/packages/plugin-vue/src/handleHotUpdate.ts +++ b/packages/plugin-vue/src/handleHotUpdate.ts @@ -6,19 +6,19 @@ import { setPrevDescriptor } from './utils/descriptorCache' import { getResolvedScript, setResolvedScript } from './script' -import { ModuleNode, ViteDevServer } from 'vite' +import { ModuleNode, HmrContext } from 'vite' const debug = _debug('vite:hmr') /** * Vite-specific HMR handling */ -export async function handleHotUpdate( - file: string, - modules: ModuleNode[], - read: () => string | Promise, - server: ViteDevServer -): Promise { +export async function handleHotUpdate({ + file, + modules, + read, + server +}: HmrContext): Promise { const prevDescriptor = getDescriptor(file, false) if (!prevDescriptor) { // file hasn't been requested yet (e.g. async component) diff --git a/packages/plugin-vue/src/index.ts b/packages/plugin-vue/src/index.ts index 8c7774c16be8ee..99c3fe6d56ba83 100644 --- a/packages/plugin-vue/src/index.ts +++ b/packages/plugin-vue/src/index.ts @@ -65,11 +65,11 @@ export default function vuePlugin(rawOptions: Options = {}): Plugin { return { name: 'vite:vue', - handleHotUpdate(file, mods, read, server) { - if (!filter(file)) { + handleHotUpdate(ctx) { + if (!filter(ctx.file)) { return } - return handleHotUpdate(file, mods, read, server) + return handleHotUpdate(ctx) }, config(config) { diff --git a/packages/vite/src/node/index.ts b/packages/vite/src/node/index.ts index c3bd13f16ea770..37008fd2c4cdb2 100644 --- a/packages/vite/src/node/index.ts +++ b/packages/vite/src/node/index.ts @@ -28,7 +28,7 @@ export type { PluginContainer } from './server/pluginContainer' export type { ModuleGraph, ModuleNode } from './server/moduleGraph' export type { ProxyOptions } from './server/middlewares/proxy' export type { TransformResult } from './server/transformRequest' -export type { HmrOptions } from './server/hmr' +export type { HmrOptions, HmrContext } from './server/hmr' export type { HMRPayload, ConnectedPayload, diff --git a/packages/vite/src/node/plugin.ts b/packages/vite/src/node/plugin.ts index 63e64bf19ec54a..cd7b215ec7c571 100644 --- a/packages/vite/src/node/plugin.ts +++ b/packages/vite/src/node/plugin.ts @@ -1,9 +1,10 @@ import { UserConfig } from './config' import { Plugin as RollupPlugin } from 'rollup' -import { ServerHook, ViteDevServer } from './server' +import { ServerHook } from './server' import { IndexHtmlTransform } from './plugins/html' import { ModuleNode } from './server/moduleGraph' import { ResolvedConfig } from './' +import { HmrContext } from './server/hmr' /** * Vite plugins extends the Rollup plugin interface with a few extra @@ -79,8 +80,8 @@ export interface Plugin extends RollupPlugin { transformIndexHtml?: IndexHtmlTransform /** * Perform custom handling of HMR updates. - * The handler receives the changed filename, a list of modules affected by - * the file change, and the dev server instance. + * The handler receives a context containing changed filename, timestamp, a + * list of modules affected by the file change, and the dev server instance. * * - The hook can return a filtered list of modules to narrow down the update. * e.g. for a Vue SFC, we can narrow down the part to update by comparing @@ -93,9 +94,6 @@ export interface Plugin extends RollupPlugin { * normal. */ handleHotUpdate?: ( - file: string, - mods: Array, - read: () => string | Promise, - server: ViteDevServer + ctx: HmrContext ) => Array | void | Promise | void> } diff --git a/packages/vite/src/node/server/hmr.ts b/packages/vite/src/node/server/hmr.ts index f32f86d6485e5e..66c7154ea3af1b 100644 --- a/packages/vite/src/node/server/hmr.ts +++ b/packages/vite/src/node/server/hmr.ts @@ -22,6 +22,14 @@ export interface HmrOptions { overlay?: boolean } +export interface HmrContext { + file: string + timestamp: number + modules: Array + read: () => string | Promise + server: ViteDevServer +} + export async function handleHMRUpdate( file: string, server: ViteDevServer @@ -59,17 +67,25 @@ export async function handleHMRUpdate( const mods = moduleGraph.getModulesByFile(file) // check if any plugin wants to perform custom HMR handling - let filteredMods = mods ? [...mods] : [] - const read = () => readModifiedFile(file) + const timestamp = Date.now() + const hmrContext: HmrContext = { + file, + timestamp, + modules: mods ? [...mods] : [], + read: () => readModifiedFile(file), + server + } + for (const plugin of config.plugins) { if (plugin.handleHotUpdate) { - filteredMods = - (await plugin.handleHotUpdate(file, filteredMods, read, server)) || - filteredMods + const filteredModules = await plugin.handleHotUpdate(hmrContext) + if (filteredModules) { + hmrContext.modules = filteredModules + } } } - if (!filteredMods.length) { + if (!hmrContext.modules.length) { // html file cannot be hot updated if (file.endsWith('.html')) { config.logger.info(chalk.green(`page reload `) + chalk.dim(shortFile), { @@ -87,10 +103,9 @@ export async function handleHMRUpdate( return } - const timestamp = Date.now() const updates: Update[] = [] - for (const mod of filteredMods) { + for (const mod of hmrContext.modules) { const boundaries = new Set<{ boundary: ModuleNode acceptedVia: ModuleNode