Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(vite-node): vite hmr support #1516

Merged
merged 18 commits into from Jul 2, 2022
23 changes: 10 additions & 13 deletions packages/vite-node/src/cli.ts
@@ -1,11 +1,12 @@
import cac from 'cac'
import { cyan, dim, red } from 'kolorist'
import { red } from 'kolorist'
import { createServer } from 'vite'
import { version } from '../package.json'
import { ViteNodeServer } from './server'
import { ViteNodeRunner } from './client'
import type { ViteNodeServerOptions } from './types'
import { toArray } from './utils'
import { createHotContext, handleMessage, viteNodeHmrPlugin } from './hmr'

const cli = cac('vite-node')

Expand Down Expand Up @@ -49,6 +50,9 @@ async function run(files: string[], options: CliOptions = {}) {
logLevel: 'error',
configFile: options.config,
root: options.root,
plugins: [
viteNodeHmrPlugin(),
],
})
await server.pluginContainer.buildStart({})

Expand All @@ -63,6 +67,9 @@ async function run(files: string[], options: CliOptions = {}) {
resolveId(id, importer) {
return node.resolveId(id, importer)
},
createHotContext(runner, url) {
return createHotContext(runner, server.emitter, files, url)
},
})

// provide the vite define variable in this context
Expand All @@ -74,18 +81,8 @@ async function run(files: string[], options: CliOptions = {}) {
if (!options.watch)
await server.close()

server.watcher.on('change', async (path) => {
console.log(`${cyan('[vite-node]')} File change detected. ${dim(path)}`)

// invalidate module cache but not node_modules
Array.from(runner.moduleCache.keys())
.forEach((i) => {
if (!i.includes('node_modules'))
runner.moduleCache.delete(i)
})

for (const file of files)
await runner.executeFile(file)
server.emitter.on('message', (payload) => {
handleMessage(runner, server.emitter, files, payload)
})
}

Expand Down
5 changes: 4 additions & 1 deletion packages/vite-node/src/client.ts
Expand Up @@ -187,7 +187,10 @@ export class ViteNodeRunner {
__vite_ssr_dynamic_import__: request,
__vite_ssr_exports__: exports,
__vite_ssr_exportAll__: (obj: any) => exportAll(exports, obj),
__vite_ssr_import_meta__: { url },
__vite_ssr_import_meta__: {
url,
hot: this.options.createHotContext?.(this, `/@fs/${fsPath}`),
antfu marked this conversation as resolved.
Show resolved Hide resolved
},

__vitest_resolve_id__: resolveId,

Expand Down
42 changes: 42 additions & 0 deletions packages/vite-node/src/hmr/emitter.ts
@@ -0,0 +1,42 @@
import { EventEmitter } from 'events'
import type { HMRPayload, Plugin } from 'vite'

export type EventType = string | symbol
export type Handler<T = unknown> = (event: T) => void
export interface Emitter<Events extends Record<EventType, unknown>> {
on<Key extends keyof Events>(type: Key, handler: Handler<Events[Key]>): void
off<Key extends keyof Events>(type: Key, handler?: Handler<Events[Key]>): void
emit<Key extends keyof Events>(type: Key, event: Events[Key]): void
emit<Key extends keyof Events>(type: undefined extends Events[Key] ? Key : never): void
}

export type HMREmitter = Emitter<{
'message': HMRPayload
}> & EventEmitter

declare module 'vite' {
interface ViteDevServer {
emitter: HMREmitter
}
}

export function createHmrEmitter(): HMREmitter {
const emitter = new EventEmitter()
return emitter
}

export function viteNodeHmrPlugin(): Plugin {
const emitter = createHmrEmitter()
return {
name: 'vite-node:hmr',

configureServer(server) {
const _send = server.ws.send
server.emitter = emitter
server.ws.send = function (payload: HMRPayload) {
_send(payload)
emitter.emit('message', payload)
}
},
}
}