diff --git a/docs/guide/api-javascript.md b/docs/guide/api-javascript.md index 55c1510898cbdb..199507bc85b29e 100644 --- a/docs/guide/api-javascript.md +++ b/docs/guide/api-javascript.md @@ -119,6 +119,11 @@ interface ViteDevServer { * Fix ssr error stacktrace. */ ssrFixStacktrace(e: Error): void + /** + * Triggers HMR for a module in the module graph. You can use the `server.moduleGraph` + * API to retrieve the module to be reloaded. If `hmr` is false, this is a no-op. + */ + reloadModule(module: ModuleNode): Promise /** * Start the server. */ diff --git a/packages/vite/src/node/server/index.ts b/packages/vite/src/node/server/index.ts index e919302e1d0412..5e8296e5ad3aad 100644 --- a/packages/vite/src/node/server/index.ts +++ b/packages/vite/src/node/server/index.ts @@ -65,6 +65,7 @@ import { serveStaticMiddleware } from './middlewares/static' import { timeMiddleware } from './middlewares/time' +import type { ModuleNode } from './moduleGraph' import { ModuleGraph } from './moduleGraph' import { errorMiddleware, prepareError } from './middlewares/error' import type { HmrOptions } from './hmr' @@ -245,6 +246,11 @@ export interface ViteDevServer { * Mutates the given SSR error by rewriting the stacktrace */ ssrFixStacktrace(e: Error): void + /** + * Triggers HMR for a module in the module graph. You can use the `server.moduleGraph` + * API to retrieve the module to be reloaded. If `hmr` is false, this is a no-op. + */ + reloadModule(module: ModuleNode): Promise /** * Start the server. */ @@ -382,6 +388,11 @@ export async function createServer( ssrRewriteStacktrace(stack: string) { return ssrRewriteStacktrace(stack, moduleGraph) }, + async reloadModule(module) { + if (serverConfig.hmr !== false && module.file) { + updateModules(module.file, [module], Date.now(), server) + } + }, async listen(port?: number, isRestart?: boolean) { await startServer(server, port, isRestart) if (httpServer) { diff --git a/playground/hmr/__tests__/hmr.spec.ts b/playground/hmr/__tests__/hmr.spec.ts index 70d5a1b9ace52e..143adea310f0d1 100644 --- a/playground/hmr/__tests__/hmr.spec.ts +++ b/playground/hmr/__tests__/hmr.spec.ts @@ -651,11 +651,23 @@ if (!isBuild) { test('handle virtual module updates', async () => { await page.goto(viteTestUrl) const el = await page.$('.virtual') - expect(await el.textContent()).toBe('[success]') + expect(await el.textContent()).toBe('[success]0') editFile('importedVirtual.js', (code) => code.replace('[success]', '[wow]')) await untilUpdated(async () => { const el = await page.$('.virtual') return await el.textContent() }, '[wow]') }) + + test('invalidate virtual module', async () => { + await page.goto(viteTestUrl) + const el = await page.$('.virtual') + expect(await el.textContent()).toBe('[wow]0') + const btn = await page.$('.virtual-update') + btn.click() + await untilUpdated(async () => { + const el = await page.$('.virtual') + return await el.textContent() + }, '[wow]1') + }) } diff --git a/playground/hmr/hmr.ts b/playground/hmr/hmr.ts index 473dff9fdbfb88..4af73ee48489f0 100644 --- a/playground/hmr/hmr.ts +++ b/playground/hmr/hmr.ts @@ -10,6 +10,13 @@ text('.dep', depFoo) text('.nested', nestedFoo) text('.virtual', virtual) +const btn = document.querySelector('.virtual-update') as HTMLButtonElement +btn.onclick = () => { + if (import.meta.hot) { + import.meta.hot.send('virtual:increment') + } +} + if (import.meta.hot) { import.meta.hot.accept(({ foo }) => { console.log('(self-accepting 1) foo is now:', foo) diff --git a/playground/hmr/index.html b/playground/hmr/index.html index b8d6065a9fd5e2..3ddd29be711a67 100644 --- a/playground/hmr/index.html +++ b/playground/hmr/index.html @@ -5,6 +5,8 @@ href="./global.css?param=required" /> + +