Skip to content

Commit

Permalink
refactor(hmr): pass context object to handleHotUpdate plugin hook
Browse files Browse the repository at this point in the history
instead of multiple args

BREAKING CHANGE: `handleHotUpdate` plugin hook now receives a single
`HmrContext` argument instead of multiple args.
  • Loading branch information
yyx990803 committed Jan 2, 2021
1 parent b039fa5 commit b314771
Show file tree
Hide file tree
Showing 8 changed files with 57 additions and 36 deletions.
2 changes: 2 additions & 0 deletions docs/guide/api-hmr.md
Original file line number Diff line number Diff line change
@@ -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.
:::

Expand Down
24 changes: 15 additions & 9 deletions docs/guide/api-plugin.md
Original file line number Diff line number Diff line change
Expand Up @@ -271,17 +271,23 @@ Vite plugins can also provide hooks that serve Vite-specific purposes. These hoo

### `handleHotUpdate`

- **Type:** `(file: string, mods: Array<ModuleNode>, read: () => string | Promise<string>, server: ViteDevServer) => Array<ModuleNode> | void | Promise<Array<ModuleNode> | void>`
- **Type:** `(ctx: HmrContext) => Array<ModuleNode> | void | Promise<Array<ModuleNode> | 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<ModuleNode>
read: () => string | Promise<string>
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:

Expand All @@ -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',
Expand All @@ -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
})
}
Expand Down
2 changes: 1 addition & 1 deletion packages/playground/hmr/vite.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -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]
Expand Down
14 changes: 7 additions & 7 deletions packages/plugin-vue/src/handleHotUpdate.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<string>,
server: ViteDevServer
): Promise<ModuleNode[] | void> {
export async function handleHotUpdate({
file,
modules,
read,
server
}: HmrContext): Promise<ModuleNode[] | void> {
const prevDescriptor = getDescriptor(file, false)
if (!prevDescriptor) {
// file hasn't been requested yet (e.g. async component)
Expand Down
6 changes: 3 additions & 3 deletions packages/plugin-vue/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down
2 changes: 1 addition & 1 deletion packages/vite/src/node/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
12 changes: 5 additions & 7 deletions packages/vite/src/node/plugin.ts
Original file line number Diff line number Diff line change
@@ -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
Expand Down Expand Up @@ -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
Expand All @@ -93,9 +94,6 @@ export interface Plugin extends RollupPlugin {
* normal.
*/
handleHotUpdate?: (
file: string,
mods: Array<ModuleNode>,
read: () => string | Promise<string>,
server: ViteDevServer
ctx: HmrContext
) => Array<ModuleNode> | void | Promise<Array<ModuleNode> | void>
}
31 changes: 23 additions & 8 deletions packages/vite/src/node/server/hmr.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,14 @@ export interface HmrOptions {
overlay?: boolean
}

export interface HmrContext {
file: string
timestamp: number
modules: Array<ModuleNode>
read: () => string | Promise<string>
server: ViteDevServer
}

export async function handleHMRUpdate(
file: string,
server: ViteDevServer
Expand Down Expand Up @@ -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), {
Expand All @@ -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
Expand Down

0 comments on commit b314771

Please sign in to comment.