From 06843231cdab16b33f9c7e3c0d20e59ddbca83bf Mon Sep 17 00:00:00 2001 From: Anthony Fu Date: Thu, 24 Mar 2022 19:56:32 +0800 Subject: [PATCH 01/11] feat(client): expose `hot.send` api for client server communication --- docs/guide/api-hmr.md | 6 ++++++ packages/vite/src/client/client.ts | 14 ++++++++++++++ packages/vite/types/importMeta.d.ts | 2 ++ 3 files changed, 22 insertions(+) diff --git a/docs/guide/api-hmr.md b/docs/guide/api-hmr.md index f4ddf59d8abcd1..87ba1bbc2aa976 100644 --- a/docs/guide/api-hmr.md +++ b/docs/guide/api-hmr.md @@ -123,3 +123,9 @@ The following HMR events are dispatched by Vite automatically: - `'vite:error'` when an error occurs (e.g. syntax error) Custom HMR events can also be sent from plugins. See [handleHotUpdate](./api-plugin#handlehotupdate) for more details. + +## `hot.send(payload)` + +Send custom data back to server. + +If called before connected, the data will be buffered and sent once the connection is established. diff --git a/packages/vite/src/client/client.ts b/packages/vite/src/client/client.ts index a9e8fb639de958..50d2e294eeba4f 100644 --- a/packages/vite/src/client/client.ts +++ b/packages/vite/src/client/client.ts @@ -30,6 +30,7 @@ const socketHost = __HMR_PORT__ const socket = new WebSocket(`${socketProtocol}://${socketHost}`, 'vite-hmr') const base = __BASE__ || '/' +const messageBuffer: string[] = [] function warnFailedFetch(err: Error, path: string | string[]) { if (!err.message.match('fetch')) { @@ -59,6 +60,7 @@ async function handleMessage(payload: HMRPayload) { switch (payload.type) { case 'connected': console.log(`[vite] connected.`) + sendMessageBuffer() // proxy(nginx, docker) hmr ws maybe caused timeout, // so send ping package let ws keep alive. setInterval(() => socket.send('ping'), __HMR_TIMEOUT__) @@ -361,6 +363,13 @@ async function fetchUpdate({ path, acceptedPath, timestamp }: Update) { } } +function sendMessageBuffer() { + if (socket.readyState === 1) { + messageBuffer.forEach((msg) => socket.send(msg)) + messageBuffer.length = 0 + } +} + interface HotModule { id: string callbacks: HotCallback[] @@ -478,6 +487,11 @@ export const createHotContext = (ownerPath: string) => { } addToMap(customListenersMap) addToMap(newListeners) + }, + + send: (payload: string) => { + messageBuffer.push(payload) + sendMessageBuffer() } } diff --git a/packages/vite/types/importMeta.d.ts b/packages/vite/types/importMeta.d.ts index 56b82125381e20..c49f4a0665c4cd 100644 --- a/packages/vite/types/importMeta.d.ts +++ b/packages/vite/types/importMeta.d.ts @@ -59,6 +59,8 @@ interface ImportMeta { cb: (data: any) => void ): void } + + send(payload: string): void } readonly env: ImportMetaEnv From 33a0608fbf4f54b90c2449aea30859c7ef87b241 Mon Sep 17 00:00:00 2001 From: Anthony Fu Date: Sat, 26 Mar 2022 15:58:10 +0800 Subject: [PATCH 02/11] feat: support message event from server --- packages/playground/hmr/__tests__/hmr.spec.ts | 7 +- packages/playground/hmr/hmr.js | 6 ++ packages/playground/hmr/index.html | 1 + packages/playground/hmr/vite.config.js | 9 ++ packages/vite/src/client/client.ts | 6 +- packages/vite/src/node/server/ws.ts | 102 ++++++++++++++++-- packages/vite/types/importMeta.d.ts | 2 +- 7 files changed, 118 insertions(+), 15 deletions(-) diff --git a/packages/playground/hmr/__tests__/hmr.spec.ts b/packages/playground/hmr/__tests__/hmr.spec.ts index 1f28763a90df94..4d0491af91a69e 100644 --- a/packages/playground/hmr/__tests__/hmr.spec.ts +++ b/packages/playground/hmr/__tests__/hmr.spec.ts @@ -123,11 +123,16 @@ if (!isBuild) { await untilUpdated(() => el.textContent(), 'edited') }) + test('plugin client-server communication', async () => { + const el = await page.$('.custom-communication') + await untilUpdated(() => el.textContent(), '3') + }) + test('full-reload encodeURI path', async () => { await page.goto( viteTestUrl + '/unicode-path/中文-にほんご-한글-🌕🌖🌗/index.html' ) - let el = await page.$('#app') + const el = await page.$('#app') expect(await el.textContent()).toBe('title') await editFile( 'unicode-path/中文-にほんご-한글-🌕🌖🌗/index.html', diff --git a/packages/playground/hmr/hmr.js b/packages/playground/hmr/hmr.js index 01dca20f5dd71c..e80b517e6449dc 100644 --- a/packages/playground/hmr/hmr.js +++ b/packages/playground/hmr/hmr.js @@ -57,6 +57,12 @@ if (import.meta.hot) { import.meta.hot.on('foo', ({ msg }) => { text('.custom', msg) }) + + // send custom event to server to calculate 1 + 2 + import.meta.hot.send('remote-add', { a: 1, b: 2 }) + import.meta.hot.on('remote-add-result', ({ result }) => { + text('.custom-communication', result) + }) } function text(el, text) { diff --git a/packages/playground/hmr/index.html b/packages/playground/hmr/index.html index 766338598e51ad..fc398c60c4cadf 100644 --- a/packages/playground/hmr/index.html +++ b/packages/playground/hmr/index.html @@ -5,5 +5,6 @@
+
diff --git a/packages/playground/hmr/vite.config.js b/packages/playground/hmr/vite.config.js index c34637844e2170..2b6c8f94b1badb 100644 --- a/packages/playground/hmr/vite.config.js +++ b/packages/playground/hmr/vite.config.js @@ -17,6 +17,15 @@ module.exports = { } }) } + }, + configureServer(server) { + server.ws.onMessage('remote-add', ({ a, b }, client) => { + client.send({ + type: 'custom', + event: 'remote-add-result', + data: { result: a + b } + }) + }) } } ] diff --git a/packages/vite/src/client/client.ts b/packages/vite/src/client/client.ts index 50d2e294eeba4f..7526992c889b80 100644 --- a/packages/vite/src/client/client.ts +++ b/packages/vite/src/client/client.ts @@ -63,7 +63,7 @@ async function handleMessage(payload: HMRPayload) { sendMessageBuffer() // proxy(nginx, docker) hmr ws maybe caused timeout, // so send ping package let ws keep alive. - setInterval(() => socket.send('ping'), __HMR_TIMEOUT__) + setInterval(() => socket.send('{"type":"ping"}'), __HMR_TIMEOUT__) break case 'update': notifyListeners('vite:beforeUpdate', payload) @@ -489,8 +489,8 @@ export const createHotContext = (ownerPath: string) => { addToMap(newListeners) }, - send: (payload: string) => { - messageBuffer.push(payload) + send: (event: string, data?: unknown) => { + messageBuffer.push(JSON.stringify({ type: 'custom', event, data })) sendMessageBuffer() } } diff --git a/packages/vite/src/node/server/ws.ts b/packages/vite/src/node/server/ws.ts index ffbfd7a56eca97..01318896d6af52 100644 --- a/packages/vite/src/node/server/ws.ts +++ b/packages/vite/src/node/server/ws.ts @@ -3,20 +3,58 @@ import type { Server } from 'http' import { STATUS_CODES } from 'http' import type { ServerOptions as HttpsServerOptions } from 'https' import { createServer as createHttpsServer } from 'https' -import type { ServerOptions } from 'ws' -import { WebSocketServer as WebSocket } from 'ws' -import type { WebSocket as WebSocketTypes } from 'types/ws' +import type { ServerOptions, WebSocket as WebSocketRaw } from 'ws' +import { WebSocketServer as WebSocketServerRaw } from 'ws' import type { ErrorPayload, HMRPayload } from 'types/hmrPayload' import type { ResolvedConfig } from '..' import { isObject } from '../utils' import type { Socket } from 'net' export const HMR_HEADER = 'vite-hmr' +export type WebSocketCustomListener = ( + data: T, + client: WebSocketClient +) => void + export interface WebSocketServer { - on: WebSocketTypes.Server['on'] - off: WebSocketTypes.Server['off'] + /** + * Get all connected clients. + */ + clients: Set + /** + * Boardcast events to all clients + */ send(payload: HMRPayload): void + /** + * Disconnect all clients and terminate the server. + */ close(): Promise + /** + * Handle custom event emitted by `import.meta.hot.send` + */ + onMessage(event: string, listener: WebSocketCustomListener): () => void + /** + * Listen to raw events from the WebSocket server. + * @advanced + */ + on: WebSocketServerRaw['on'] + /** + * Unregister listeners for raw WebSocket server events. + * @advanced + */ + off: WebSocketServerRaw['off'] +} + +export interface WebSocketClient { + /** + * Send event to the client + */ + send(payload: HMRPayload): void + /** + * The raw WebSocket instance + * @advanced + */ + socket: WebSocketRaw } export function createWebSocketServer( @@ -24,7 +62,7 @@ export function createWebSocketServer( config: ResolvedConfig, httpsOptions?: HttpsServerOptions ): WebSocketServer { - let wss: WebSocket + let wss: WebSocketServerRaw let httpsServer: Server | undefined = undefined const hmr = isObject(config.server.hmr) && config.server.hmr @@ -33,9 +71,11 @@ export function createWebSocketServer( // TODO: the main server port may not have been chosen yet as it may use the next available const portsAreCompatible = !hmrPort || hmrPort === config.server.port const wsServer = hmrServer || (portsAreCompatible && server) + const customListeners = new Map>>() + const clientsMap = new WeakMap() if (wsServer) { - wss = new WebSocket({ noServer: true }) + wss = new WebSocketServerRaw({ noServer: true }) wsServer.on('upgrade', (req, socket, head) => { if (req.headers['sec-websocket-protocol'] === HMR_HEADER) { wss.handleUpgrade(req, socket as Socket, head, (ws) => { @@ -76,10 +116,22 @@ export function createWebSocketServer( } // vite dev server in middleware mode - wss = new WebSocket(websocketServerOptions) + wss = new WebSocketServerRaw(websocketServerOptions) } wss.on('connection', (socket) => { + socket.on('message', (raw) => { + if (!customListeners.size) return + let parsed: any + try { + parsed = JSON.parse(String(raw)) + } catch {} + if (!parsed || parsed.type !== 'custom' || !parsed.event) return + const listeners = customListeners.get(parsed.event) + if (!listeners?.size) return + const client = getSocketClent(socket) + listeners.forEach((listener) => listener(parsed.data, client)) + }) socket.send(JSON.stringify({ type: 'connected' })) if (bufferedError) { socket.send(JSON.stringify(bufferedError)) @@ -96,6 +148,18 @@ export function createWebSocketServer( } }) + // Provide a wrapper to the ws client so we can send messages in JSON format + // To be consistent with server.ws.send + function getSocketClent(socket: WebSocketRaw) { + if (!clientsMap.has(socket)) { + clientsMap.set(socket, { + send: (payload) => socket.send(JSON.stringify(payload)), + socket + }) + } + return clientsMap.get(socket)! + } + // On page reloads, if a file fails to compile and returns 500, the server // sends the error payload before the client connection is established. // If we have no open clients, buffer the error and send it to the next @@ -103,8 +167,16 @@ export function createWebSocketServer( let bufferedError: ErrorPayload | null = null return { - on: wss.on.bind(wss), - off: wss.off.bind(wss), + get on() { + return wss.on.bind(wss) + }, + get off() { + return wss.off.bind(wss) + }, + get clients() { + return new Set(Array.from(wss.clients).map(getSocketClent)) + }, + send(payload: HMRPayload) { if (payload.type === 'error' && !wss.clients.size) { bufferedError = payload @@ -143,6 +215,16 @@ export function createWebSocketServer( } }) }) + }, + + onMessage(event: string, listener: WebSocketCustomListener) { + if (!customListeners.has(event)) customListeners.set(event, new Set()) + customListeners.get(event)!.add(listener) + + const off = () => { + customListeners.get(event)?.delete(listener) + } + return off } } } diff --git a/packages/vite/types/importMeta.d.ts b/packages/vite/types/importMeta.d.ts index c49f4a0665c4cd..79759868cb9bd6 100644 --- a/packages/vite/types/importMeta.d.ts +++ b/packages/vite/types/importMeta.d.ts @@ -60,7 +60,7 @@ interface ImportMeta { ): void } - send(payload: string): void + send(event: string, data?: unknown): void } readonly env: ImportMetaEnv From 6dc770f448b41423051de1c83d1060ea46083e96 Mon Sep 17 00:00:00 2001 From: Anthony Fu Date: Sat, 26 Mar 2022 16:14:46 +0800 Subject: [PATCH 03/11] chore: docs --- docs/guide/api-hmr.md | 6 ++-- docs/guide/api-plugin.md | 74 +++++++++++++++++++++++++++++++++++++++- 2 files changed, 77 insertions(+), 3 deletions(-) diff --git a/docs/guide/api-hmr.md b/docs/guide/api-hmr.md index 87ba1bbc2aa976..6b70d4d1553413 100644 --- a/docs/guide/api-hmr.md +++ b/docs/guide/api-hmr.md @@ -124,8 +124,10 @@ The following HMR events are dispatched by Vite automatically: Custom HMR events can also be sent from plugins. See [handleHotUpdate](./api-plugin#handlehotupdate) for more details. -## `hot.send(payload)` +## `hot.send(event, payload)` -Send custom data back to server. +Send custom event back to server. If called before connected, the data will be buffered and sent once the connection is established. + +See [Client-server Communication](/guide/api-plugin.html#client-server-communication) for more details. diff --git a/docs/guide/api-plugin.md b/docs/guide/api-plugin.md index b3888b0fd7009b..1eaa5a09f4ad89 100644 --- a/docs/guide/api-plugin.md +++ b/docs/guide/api-plugin.md @@ -480,7 +480,7 @@ export default defineConfig({ Check out [Vite Rollup Plugins](https://vite-rollup-plugins.patak.dev) for a list of compatible official Rollup plugins with usage instructions. -## Path normalization +## Path Normalization Vite normalizes paths while resolving ids to use POSIX separators ( / ) while preserving the volume in Windows. On the other hand, Rollup keeps resolved paths untouched by default, so resolved ids have win32 separators ( \\ ) in Windows. However, Rollup plugins use a [`normalizePath` utility function](https://github.com/rollup/plugins/tree/master/packages/pluginutils#normalizepath) from `@rollup/pluginutils` internally, which converts separators to POSIX before performing comparisons. This means that when these plugins are used in Vite, the `include` and `exclude` config pattern and other similar paths against resolved ids comparisons work correctly. @@ -492,3 +492,75 @@ import { normalizePath } from 'vite' normalizePath('foo\\bar') // 'foo/bar' normalizePath('foo/bar') // 'foo/bar' ``` + +## Client-server Communication + +As in Vite 2.9, we provides some utilities for plugins to handle the communcation with clients easier. + +### Server to Client + +On the plugin side, we could use `server.ws.send` to boardcast events to all the clients: + +```js +// vite.config.js +export default defineConfig({ + plugins: [ + { + // ... + configureServer(server) { + server.ws.send({ + type: 'custom', + event: 'greetings', + data: { msg: 'hello' } + }) + } + } + ] +}) +``` + +And on the client side, we can use [`hot.on`](/guide/api-hmr.html#hot-on-event-cb) to listen to the events: + +```ts +// client side +if (import.meta.hot) { + import.meta.hot.on('greetings', (data) => { + console.log(data.msg) // hello + }) +} +``` + +### Client to Server + +To send events from the client to the server, we can use [`hot.send`](/guide/api-hmr.html#hot-send-event-payload): + +```ts +// client side +if (import.meta.hot) { + import.meta.hot.send('from-client', { msg: 'Hey!' }) +} +``` + +On the plugin side, we can use `server.ws.onMessage` listening to the events: + +```js +// vite.config.js +export default defineConfig({ + plugins: [ + { + // ... + configureServer(server) { + server.ws.onMessage('from-client', (data, client) => { + console.log('Message from client:', data.msg) // Hey! + // reply only to the client (if needed) + client.send({ + type: 'custom', + event: 'ack', + data: { msg: 'Hi! I got your message!' } + }) + }) + } + } + ] +}) +``` From 06b851845f70d68dd4e93b9855a47102e4d4a127 Mon Sep 17 00:00:00 2001 From: Anthony Fu Date: Sat, 26 Mar 2022 16:23:11 +0800 Subject: [PATCH 04/11] chore: types --- packages/vite/src/node/index.ts | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/packages/vite/src/node/index.ts b/packages/vite/src/node/index.ts index 027a715c454a74..2b59da8737029b 100644 --- a/packages/vite/src/node/index.ts +++ b/packages/vite/src/node/index.ts @@ -73,7 +73,11 @@ export type { TransformOptions as EsbuildTransformOptions } from 'esbuild' export type { ESBuildOptions, ESBuildTransformResult } from './plugins/esbuild' export type { Manifest, ManifestChunk } from './plugins/manifest' export type { ResolveOptions, InternalResolveOptions } from './plugins/resolve' -export type { WebSocketServer } from './server/ws' +export type { + WebSocketServer, + WebSocketClient, + WebSocketCustomListener +} from './server/ws' export type { PluginContainer } from './server/pluginContainer' export type { ModuleGraph, ModuleNode, ResolvedUrl } from './server/moduleGraph' export type { SendOptions } from './server/send' From 8bb4a489b72c978f06f2e2111eba68704563c2a4 Mon Sep 17 00:00:00 2001 From: Anthony Fu Date: Sat, 26 Mar 2022 16:42:39 +0800 Subject: [PATCH 05/11] Update docs/guide/api-plugin.md --- docs/guide/api-plugin.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/guide/api-plugin.md b/docs/guide/api-plugin.md index 1eaa5a09f4ad89..a1f101c9a45280 100644 --- a/docs/guide/api-plugin.md +++ b/docs/guide/api-plugin.md @@ -495,7 +495,7 @@ normalizePath('foo/bar') // 'foo/bar' ## Client-server Communication -As in Vite 2.9, we provides some utilities for plugins to handle the communcation with clients easier. +As in Vite 2.9, we provide some utilities for plugins to handle the communication with clients easier. ### Server to Client From c26627f1b7923f69b64f4c57f9ca6017946508c1 Mon Sep 17 00:00:00 2001 From: Anthony Fu Date: Sat, 26 Mar 2022 19:53:00 +0800 Subject: [PATCH 06/11] Apply suggestions from code review Co-authored-by: patak --- docs/guide/api-hmr.md | 2 +- docs/guide/api-plugin.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/guide/api-hmr.md b/docs/guide/api-hmr.md index 6b70d4d1553413..0c0bf852f116ad 100644 --- a/docs/guide/api-hmr.md +++ b/docs/guide/api-hmr.md @@ -126,7 +126,7 @@ Custom HMR events can also be sent from plugins. See [handleHotUpdate](./api-plu ## `hot.send(event, payload)` -Send custom event back to server. +Send custom events back to Vite's dev server. If called before connected, the data will be buffered and sent once the connection is established. diff --git a/docs/guide/api-plugin.md b/docs/guide/api-plugin.md index a1f101c9a45280..97ce41021a7294 100644 --- a/docs/guide/api-plugin.md +++ b/docs/guide/api-plugin.md @@ -495,7 +495,7 @@ normalizePath('foo/bar') // 'foo/bar' ## Client-server Communication -As in Vite 2.9, we provide some utilities for plugins to handle the communication with clients easier. +Since Vite 2.9, we provide some utilities for plugins to help handle the communication with clients. ### Server to Client From 661f7d232c7b57ac5fd54ef15f1ec09a6c9975e3 Mon Sep 17 00:00:00 2001 From: Anthony Fu Date: Sat, 26 Mar 2022 20:04:14 +0800 Subject: [PATCH 07/11] chore: use object style send --- docs/guide/api-plugin.md | 5 ++++- packages/playground/hmr/hmr.js | 2 +- packages/vite/src/client/client.ts | 6 +++--- packages/vite/types/customEvent.d.ts | 6 ++++++ packages/vite/types/importMeta.d.ts | 2 +- 5 files changed, 15 insertions(+), 6 deletions(-) diff --git a/docs/guide/api-plugin.md b/docs/guide/api-plugin.md index 97ce41021a7294..359309bcec68fd 100644 --- a/docs/guide/api-plugin.md +++ b/docs/guide/api-plugin.md @@ -537,7 +537,10 @@ To send events from the client to the server, we can use [`hot.send`](/guide/api ```ts // client side if (import.meta.hot) { - import.meta.hot.send('from-client', { msg: 'Hey!' }) + import.meta.hot.send({ + event: 'from-client', + data: { msg: 'Hey!' } + }) } ``` diff --git a/packages/playground/hmr/hmr.js b/packages/playground/hmr/hmr.js index e80b517e6449dc..2c873ec8f288cf 100644 --- a/packages/playground/hmr/hmr.js +++ b/packages/playground/hmr/hmr.js @@ -59,7 +59,7 @@ if (import.meta.hot) { }) // send custom event to server to calculate 1 + 2 - import.meta.hot.send('remote-add', { a: 1, b: 2 }) + import.meta.hot.send({ event: 'remote-add', data: { a: 1, b: 2 } }) import.meta.hot.on('remote-add-result', ({ result }) => { text('.custom-communication', result) }) diff --git a/packages/vite/src/client/client.ts b/packages/vite/src/client/client.ts index 7526992c889b80..0fcd71714fece1 100644 --- a/packages/vite/src/client/client.ts +++ b/packages/vite/src/client/client.ts @@ -6,7 +6,7 @@ import type { Update, UpdatePayload } from 'types/hmrPayload' -import type { CustomEventName } from 'types/customEvent' +import type { CustomEventName, CustomEventPayload } from 'types/customEvent' import { ErrorOverlay, overlayId } from './overlay' // eslint-disable-next-line node/no-missing-import import '@vite/env' @@ -489,8 +489,8 @@ export const createHotContext = (ownerPath: string) => { addToMap(newListeners) }, - send: (event: string, data?: unknown) => { - messageBuffer.push(JSON.stringify({ type: 'custom', event, data })) + send: (payload: CustomEventPayload) => { + messageBuffer.push(JSON.stringify({ type: 'custom', ...payload })) sendMessageBuffer() } } diff --git a/packages/vite/types/customEvent.d.ts b/packages/vite/types/customEvent.d.ts index c38a4ac940ff6e..f5135c34f6760f 100644 --- a/packages/vite/types/customEvent.d.ts +++ b/packages/vite/types/customEvent.d.ts @@ -3,3 +3,9 @@ export type CustomEventName = (T extends `vite:${T}` ? never : T) & (`vite:${T}` extends T ? never : T) + +export interface CustomEventPayload { + type?: 'custom' + event: string + data?: any +} diff --git a/packages/vite/types/importMeta.d.ts b/packages/vite/types/importMeta.d.ts index 79759868cb9bd6..6d83ff40a3def8 100644 --- a/packages/vite/types/importMeta.d.ts +++ b/packages/vite/types/importMeta.d.ts @@ -60,7 +60,7 @@ interface ImportMeta { ): void } - send(event: string, data?: unknown): void + send(payload: import('./customEvent').CustomEventPayload): void } readonly env: ImportMetaEnv From bf2acd90a464ba25099cb0872eae3edcc0f0b8e5 Mon Sep 17 00:00:00 2001 From: patak Date: Sat, 26 Mar 2022 13:24:05 +0100 Subject: [PATCH 08/11] chore: docs update --- docs/guide/api-hmr.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/guide/api-hmr.md b/docs/guide/api-hmr.md index 0c0bf852f116ad..3e8ef3eb6708a2 100644 --- a/docs/guide/api-hmr.md +++ b/docs/guide/api-hmr.md @@ -124,7 +124,7 @@ The following HMR events are dispatched by Vite automatically: Custom HMR events can also be sent from plugins. See [handleHotUpdate](./api-plugin#handlehotupdate) for more details. -## `hot.send(event, payload)` +## `hot.send({ event, data })` Send custom events back to Vite's dev server. From 4c65e3f04f045bd63848e074e978ce567a3086fd Mon Sep 17 00:00:00 2001 From: Anthony Fu Date: Sat, 26 Mar 2022 21:01:58 +0800 Subject: [PATCH 09/11] refactor: `onMessage` -> `onEvent` --- docs/guide/api-plugin.md | 4 ++-- packages/playground/hmr/vite.config.js | 2 +- packages/vite/src/node/server/ws.ts | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/docs/guide/api-plugin.md b/docs/guide/api-plugin.md index 359309bcec68fd..010367d7cdb577 100644 --- a/docs/guide/api-plugin.md +++ b/docs/guide/api-plugin.md @@ -544,7 +544,7 @@ if (import.meta.hot) { } ``` -On the plugin side, we can use `server.ws.onMessage` listening to the events: +On the plugin side, we can use `server.ws.onEvent` listening to the events: ```js // vite.config.js @@ -553,7 +553,7 @@ export default defineConfig({ { // ... configureServer(server) { - server.ws.onMessage('from-client', (data, client) => { + server.ws.onEvent('from-client', (data, client) => { console.log('Message from client:', data.msg) // Hey! // reply only to the client (if needed) client.send({ diff --git a/packages/playground/hmr/vite.config.js b/packages/playground/hmr/vite.config.js index 2b6c8f94b1badb..868d62aa8d681a 100644 --- a/packages/playground/hmr/vite.config.js +++ b/packages/playground/hmr/vite.config.js @@ -19,7 +19,7 @@ module.exports = { } }, configureServer(server) { - server.ws.onMessage('remote-add', ({ a, b }, client) => { + server.ws.onEvent('remote-add', ({ a, b }, client) => { client.send({ type: 'custom', event: 'remote-add-result', diff --git a/packages/vite/src/node/server/ws.ts b/packages/vite/src/node/server/ws.ts index 01318896d6af52..fa146f7eed5ccf 100644 --- a/packages/vite/src/node/server/ws.ts +++ b/packages/vite/src/node/server/ws.ts @@ -32,7 +32,7 @@ export interface WebSocketServer { /** * Handle custom event emitted by `import.meta.hot.send` */ - onMessage(event: string, listener: WebSocketCustomListener): () => void + onEvent(event: string, listener: WebSocketCustomListener): () => void /** * Listen to raw events from the WebSocket server. * @advanced @@ -217,7 +217,7 @@ export function createWebSocketServer( }) }, - onMessage(event: string, listener: WebSocketCustomListener) { + onEvent(event: string, listener: WebSocketCustomListener) { if (!customListeners.has(event)) customListeners.set(event, new Set()) customListeners.get(event)!.add(listener) From 37c2350c2f541eff0d87bfef966a21239ba5d782 Mon Sep 17 00:00:00 2001 From: Anthony Fu Date: Sat, 26 Mar 2022 22:03:45 +0800 Subject: [PATCH 10/11] feat: simplfied the interface --- docs/guide/api-hmr.md | 2 +- docs/guide/api-plugin.md | 29 +++----- packages/playground/hmr/hmr.js | 2 +- packages/playground/hmr/vite.config.js | 16 +---- packages/vite/src/client/client.ts | 6 +- packages/vite/src/node/server/ws.ts | 94 ++++++++++++++++++-------- packages/vite/types/customEvent.d.ts | 6 -- packages/vite/types/importMeta.d.ts | 2 +- 8 files changed, 86 insertions(+), 71 deletions(-) diff --git a/docs/guide/api-hmr.md b/docs/guide/api-hmr.md index 3e8ef3eb6708a2..46eabab04e8868 100644 --- a/docs/guide/api-hmr.md +++ b/docs/guide/api-hmr.md @@ -124,7 +124,7 @@ The following HMR events are dispatched by Vite automatically: Custom HMR events can also be sent from plugins. See [handleHotUpdate](./api-plugin#handlehotupdate) for more details. -## `hot.send({ event, data })` +## `hot.send(event, data)` Send custom events back to Vite's dev server. diff --git a/docs/guide/api-plugin.md b/docs/guide/api-plugin.md index 010367d7cdb577..13767c45dd3103 100644 --- a/docs/guide/api-plugin.md +++ b/docs/guide/api-plugin.md @@ -508,23 +508,23 @@ export default defineConfig({ { // ... configureServer(server) { - server.ws.send({ - type: 'custom', - event: 'greetings', - data: { msg: 'hello' } - }) + server.ws.send('my:greetings', { msg: 'hello' }) } } ] }) ``` -And on the client side, we can use [`hot.on`](/guide/api-hmr.html#hot-on-event-cb) to listen to the events: +::: tip NOTE +We recommend **alway prefixing** your event names to avoid collisions with other plugins. +::: + +On the client side, use [`hot.on`](/guide/api-hmr.html#hot-on-event-cb) to listen to the events: ```ts // client side if (import.meta.hot) { - import.meta.hot.on('greetings', (data) => { + import.meta.hot.on('my:greetings', (data) => { console.log(data.msg) // hello }) } @@ -537,14 +537,11 @@ To send events from the client to the server, we can use [`hot.send`](/guide/api ```ts // client side if (import.meta.hot) { - import.meta.hot.send({ - event: 'from-client', - data: { msg: 'Hey!' } - }) + import.meta.hot.send('my:from-client', { msg: 'Hey!' }) } ``` -On the plugin side, we can use `server.ws.onEvent` listening to the events: +Then use `server.ws.on` and listen to the events on the server side: ```js // vite.config.js @@ -553,14 +550,10 @@ export default defineConfig({ { // ... configureServer(server) { - server.ws.onEvent('from-client', (data, client) => { + server.ws.on('my:from-client', (data, client) => { console.log('Message from client:', data.msg) // Hey! // reply only to the client (if needed) - client.send({ - type: 'custom', - event: 'ack', - data: { msg: 'Hi! I got your message!' } - }) + client.send('my:ack', { msg: 'Hi! I got your message!' }) }) } } diff --git a/packages/playground/hmr/hmr.js b/packages/playground/hmr/hmr.js index 2c873ec8f288cf..e80b517e6449dc 100644 --- a/packages/playground/hmr/hmr.js +++ b/packages/playground/hmr/hmr.js @@ -59,7 +59,7 @@ if (import.meta.hot) { }) // send custom event to server to calculate 1 + 2 - import.meta.hot.send({ event: 'remote-add', data: { a: 1, b: 2 } }) + import.meta.hot.send('remote-add', { a: 1, b: 2 }) import.meta.hot.on('remote-add-result', ({ result }) => { text('.custom-communication', result) }) diff --git a/packages/playground/hmr/vite.config.js b/packages/playground/hmr/vite.config.js index 868d62aa8d681a..4e655828ac384a 100644 --- a/packages/playground/hmr/vite.config.js +++ b/packages/playground/hmr/vite.config.js @@ -9,22 +9,12 @@ module.exports = { if (file.endsWith('customFile.js')) { const content = await read() const msg = content.match(/export const msg = '(\w+)'/)[1] - server.ws.send({ - type: 'custom', - event: 'foo', - data: { - msg - } - }) + server.ws.send('foo', { data: msg }) } }, configureServer(server) { - server.ws.onEvent('remote-add', ({ a, b }, client) => { - client.send({ - type: 'custom', - event: 'remote-add-result', - data: { result: a + b } - }) + server.ws.on('remote-add', ({ a, b }, client) => { + client.send('remote-add-result', { result: a + b }) }) } } diff --git a/packages/vite/src/client/client.ts b/packages/vite/src/client/client.ts index 0fcd71714fece1..40f0bb0418f365 100644 --- a/packages/vite/src/client/client.ts +++ b/packages/vite/src/client/client.ts @@ -6,7 +6,7 @@ import type { Update, UpdatePayload } from 'types/hmrPayload' -import type { CustomEventName, CustomEventPayload } from 'types/customEvent' +import type { CustomEventName } from 'types/customEvent' import { ErrorOverlay, overlayId } from './overlay' // eslint-disable-next-line node/no-missing-import import '@vite/env' @@ -489,8 +489,8 @@ export const createHotContext = (ownerPath: string) => { addToMap(newListeners) }, - send: (payload: CustomEventPayload) => { - messageBuffer.push(JSON.stringify({ type: 'custom', ...payload })) + send: (event: string, data?: any) => { + messageBuffer.push(JSON.stringify({ type: 'custom', event, data })) sendMessageBuffer() } } diff --git a/packages/vite/src/node/server/ws.ts b/packages/vite/src/node/server/ws.ts index fa146f7eed5ccf..3c6875e2475c64 100644 --- a/packages/vite/src/node/server/ws.ts +++ b/packages/vite/src/node/server/ws.ts @@ -5,7 +5,7 @@ import type { ServerOptions as HttpsServerOptions } from 'https' import { createServer as createHttpsServer } from 'https' import type { ServerOptions, WebSocket as WebSocketRaw } from 'ws' import { WebSocketServer as WebSocketServerRaw } from 'ws' -import type { ErrorPayload, HMRPayload } from 'types/hmrPayload' +import type { CustomPayload, ErrorPayload, HMRPayload } from 'types/hmrPayload' import type { ResolvedConfig } from '..' import { isObject } from '../utils' import type { Socket } from 'net' @@ -25,6 +25,10 @@ export interface WebSocketServer { * Boardcast events to all clients */ send(payload: HMRPayload): void + /** + * Send custom event + */ + send(event: string, payload?: CustomPayload['data']): void /** * Disconnect all clients and terminate the server. */ @@ -32,17 +36,15 @@ export interface WebSocketServer { /** * Handle custom event emitted by `import.meta.hot.send` */ - onEvent(event: string, listener: WebSocketCustomListener): () => void - /** - * Listen to raw events from the WebSocket server. - * @advanced - */ - on: WebSocketServerRaw['on'] + on: WebSocketServerRaw['on'] & { + (event: string, listener: WebSocketCustomListener): void + } /** - * Unregister listeners for raw WebSocket server events. - * @advanced + * Unregister event listener. */ - off: WebSocketServerRaw['off'] + off: WebSocketServerRaw['off'] & { + (event: string, listener: WebSocketCustomListener): void + } } export interface WebSocketClient { @@ -50,6 +52,10 @@ export interface WebSocketClient { * Send event to the client */ send(payload: HMRPayload): void + /** + * Send custom event + */ + send(event: string, payload?: CustomPayload['data']): void /** * The raw WebSocket instance * @advanced @@ -57,6 +63,14 @@ export interface WebSocketClient { socket: WebSocketRaw } +const wsServerEvents = [ + 'connection', + 'error', + 'headers', + 'listening', + 'message' +] + export function createWebSocketServer( server: Server | null, config: ResolvedConfig, @@ -153,7 +167,19 @@ export function createWebSocketServer( function getSocketClent(socket: WebSocketRaw) { if (!clientsMap.has(socket)) { clientsMap.set(socket, { - send: (payload) => socket.send(JSON.stringify(payload)), + send: (...args) => { + let payload: HMRPayload + if (typeof args[0] === 'string') { + payload = { + type: 'custom', + event: args[0], + data: args[1] + } + } else { + payload = args[0] + } + socket.send(JSON.stringify(payload)) + }, socket }) } @@ -167,17 +193,39 @@ export function createWebSocketServer( let bufferedError: ErrorPayload | null = null return { - get on() { - return wss.on.bind(wss) - }, - get off() { - return wss.off.bind(wss) - }, + on: ((event: string, fn: () => void) => { + if (wsServerEvents.includes(event)) wss.on(event, fn) + else { + if (!customListeners.has(event)) { + customListeners.set(event, new Set()) + } + customListeners.get(event)!.add(fn) + } + }) as WebSocketServer['on'], + off: ((event: string, fn: () => void) => { + if (wsServerEvents.includes(event)) { + wss.off(event, fn) + } else { + customListeners.get(event)?.delete(fn) + } + }) as WebSocketServer['off'], + get clients() { return new Set(Array.from(wss.clients).map(getSocketClent)) }, - send(payload: HMRPayload) { + send(...args: any[]) { + let payload: HMRPayload + if (typeof args[0] === 'string') { + payload = { + type: 'custom', + event: args[0], + data: args[1] + } + } else { + payload = args[0] + } + if (payload.type === 'error' && !wss.clients.size) { bufferedError = payload return @@ -215,16 +263,6 @@ export function createWebSocketServer( } }) }) - }, - - onEvent(event: string, listener: WebSocketCustomListener) { - if (!customListeners.has(event)) customListeners.set(event, new Set()) - customListeners.get(event)!.add(listener) - - const off = () => { - customListeners.get(event)?.delete(listener) - } - return off } } } diff --git a/packages/vite/types/customEvent.d.ts b/packages/vite/types/customEvent.d.ts index f5135c34f6760f..c38a4ac940ff6e 100644 --- a/packages/vite/types/customEvent.d.ts +++ b/packages/vite/types/customEvent.d.ts @@ -3,9 +3,3 @@ export type CustomEventName = (T extends `vite:${T}` ? never : T) & (`vite:${T}` extends T ? never : T) - -export interface CustomEventPayload { - type?: 'custom' - event: string - data?: any -} diff --git a/packages/vite/types/importMeta.d.ts b/packages/vite/types/importMeta.d.ts index 6d83ff40a3def8..1fd39b993d5142 100644 --- a/packages/vite/types/importMeta.d.ts +++ b/packages/vite/types/importMeta.d.ts @@ -60,7 +60,7 @@ interface ImportMeta { ): void } - send(payload: import('./customEvent').CustomEventPayload): void + send(event: string, data?: any): void } readonly env: ImportMetaEnv From d27778a6e442f0ecd6facad3159787a5ae575c67 Mon Sep 17 00:00:00 2001 From: Anthony Fu Date: Sat, 26 Mar 2022 22:10:58 +0800 Subject: [PATCH 11/11] chore: fix tests --- packages/playground/hmr/vite.config.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/playground/hmr/vite.config.js b/packages/playground/hmr/vite.config.js index 4e655828ac384a..57252c91be410b 100644 --- a/packages/playground/hmr/vite.config.js +++ b/packages/playground/hmr/vite.config.js @@ -9,7 +9,7 @@ module.exports = { if (file.endsWith('customFile.js')) { const content = await read() const msg = content.match(/export const msg = '(\w+)'/)[1] - server.ws.send('foo', { data: msg }) + server.ws.send('foo', { msg }) } }, configureServer(server) {