Skip to content

Commit

Permalink
feat(vite-node): vite hmr support (#1516)
Browse files Browse the repository at this point in the history
* chore: update

* feat: vite-node hmr

* chore: lock.yaml

* chore: update

* chore: update

* chore: update

* feat: hmr module

* chore: package.json

* perf: create ctx only had `__vite_ssr_import_meta__.hot`

* chore: update

* chore: update

* chore: update

* chore: update

* perf: only install hmr plugin on watch mode

* chore: update lock

Co-authored-by: Anthony Fu <anthonyfu117@hotmail.com>
  • Loading branch information
poyoho and antfu committed Jul 2, 2022
1 parent 7ad8e3f commit 4a95177
Show file tree
Hide file tree
Showing 13 changed files with 452 additions and 49 deletions.
5 changes: 5 additions & 0 deletions packages/vite-node/package.json
Expand Up @@ -34,6 +34,11 @@
"types": "./dist/utils.d.ts",
"require": "./dist/utils.cjs",
"import": "./dist/utils.mjs"
},
"./hmr": {
"types": "./dist/hmr.d.ts",
"require": "./dist/hmr.cjs",
"import": "./dist/hmr.mjs"
}
},
"main": "./dist/index.mjs",
Expand Down
2 changes: 2 additions & 0 deletions packages/vite-node/rollup.config.js
Expand Up @@ -14,6 +14,7 @@ const entries = {
client: 'src/client.ts',
utils: 'src/utils.ts',
cli: 'src/cli.ts',
hmr: 'src/hmr/index.ts',
}

const external = [
Expand All @@ -23,6 +24,7 @@ const external = [
'birpc',
'vite',
'url',
'events',
]

const plugins = [
Expand Down
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: [
options.watch && 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
18 changes: 15 additions & 3 deletions packages/vite-node/src/client.ts
Expand Up @@ -5,7 +5,7 @@ import { dirname, extname, isAbsolute, resolve } from 'pathe'
import { isNodeBuiltin } from 'mlly'
import createDebug from 'debug'
import { isPrimitive, mergeSlashes, normalizeModuleId, normalizeRequestId, slash, toFilePath } from './utils'
import type { ModuleCache, ViteNodeRunnerOptions } from './types'
import type { HotContext, ModuleCache, ViteNodeRunnerOptions } from './types'

const debugExecute = createDebug('vite-node:client:execute')
const debugNative = createDebug('vite-node:client:native')
Expand Down Expand Up @@ -161,6 +161,7 @@ export class ViteNodeRunner {

// disambiguate the `<UNIT>:/` on windows: see nodejs/node#31710
const url = pathToFileURL(fsPath).href
const meta = { url }
const exports: any = Object.create(null)
exports[Symbol.toStringTag] = 'Module'

Expand All @@ -177,6 +178,18 @@ export class ViteNodeRunner {
},
}

// Vite hot context
let hotContext: HotContext | undefined
if (this.options.createHotContext) {
Object.defineProperty(meta, 'hot', {
enumerable: true,
get: () => {
hotContext ||= this.options.createHotContext?.(this, `/@fs/${fsPath}`)
return hotContext
},
})
}

// Be careful when changing this
// changing context will change amount of code added on line :114 (vm.runInThisContext)
// this messes up sourcemaps for coverage
Expand All @@ -187,8 +200,7 @@ 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__: meta,
__vitest_resolve_id__: resolveId,

// cjs compact
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)
}
},
}
}

0 comments on commit 4a95177

Please sign in to comment.