Skip to content

Commit 9e0a90b

Browse files
committed
wip: initial client concept impl
1 parent 0452bc9 commit 9e0a90b

File tree

3 files changed

+113
-18
lines changed

3 files changed

+113
-18
lines changed

packages/vite/src/client/client.ts

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -617,5 +617,19 @@ if (isFullBundleMode && typeof DevRuntime !== 'undefined') {
617617
}
618618
}
619619

620-
;(globalThis as any).__rolldown_runtime__ ??= new ViteDevRuntime()
620+
// TODO: make this more performant
621+
const wrappedSocket = {
622+
readyState: WebSocket.OPEN,
623+
send(data: string) {
624+
const d = JSON.parse(data)
625+
transport.send({
626+
type: 'custom',
627+
event: 'vite:module-loaded',
628+
data: { modules: d.modules },
629+
})
630+
},
631+
}
632+
;(globalThis as any).__rolldown_runtime__ ??= new ViteDevRuntime(
633+
wrappedSocket,
634+
)
621635
}

packages/vite/src/node/server/environments/fullBundleEnvironment.ts

Lines changed: 96 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import { randomUUID } from 'node:crypto'
12
import type { RolldownBuild } from 'rolldown'
23
import { type DevEngine, dev } from 'rolldown/experimental'
34
import type { Update } from 'types/hmrPayload'
@@ -9,6 +10,7 @@ import type { ResolvedConfig } from '../../config'
910
import type { ViteDevServer } from '../../server'
1011
import { createDebugger } from '../../utils'
1112
import { getShortName } from '../hmr'
13+
import type { WebSocketClient } from '../ws'
1214

1315
const debug = createDebugger('vite:full-bundle-mode')
1416

@@ -58,7 +60,11 @@ export class MemoryFiles {
5860

5961
export class FullBundleDevEnvironment extends DevEnvironment {
6062
private devEngine!: DevEngine
61-
private invalidateCalledModules = new Set<string>()
63+
private clients = new Clients()
64+
private invalidateCalledModules = new Map<
65+
/* clientId */ string,
66+
Set<string>
67+
>()
6268
private debouncedFullReload = debounce(20, () => {
6369
this.hot.send({ type: 'full-reload', path: '*' })
6470
this.logger.info(colors.green(`page reload`), { timestamp: true })
@@ -80,7 +86,7 @@ export class FullBundleDevEnvironment extends DevEnvironment {
8086
super(name, config, { ...context, disableDepsOptimizer: true })
8187
}
8288

83-
override async listen(_server: ViteDevServer): Promise<void> {
89+
override async listen(server: ViteDevServer): Promise<void> {
8490
this.hot.listen()
8591

8692
debug?.('INITIAL: setup bundle options')
@@ -98,19 +104,39 @@ export class FullBundleDevEnvironment extends DevEnvironment {
98104
: rollupOptions.output
99105
)!
100106

107+
// TODO: use hot API
108+
server.ws.on(
109+
'vite:module-loaded',
110+
(payload: { modules: string[] }, client: WebSocketClient) => {
111+
const clientId = this.clients.setupIfNeeded(client, () => {
112+
this.devEngine.removeClient(clientId)
113+
})
114+
this.devEngine.registerModules(clientId, payload.modules)
115+
},
116+
)
117+
server.ws.on('vite:invalidate', (payload, client: WebSocketClient) => {
118+
this.handleInvalidateModule(client, payload)
119+
})
120+
101121
this.devEngine = await dev(rollupOptions, outputOptions, {
102122
onHmrUpdates: (updates, files) => {
103123
if (files.length === 0) {
104124
return
105125
}
106-
// TODO: how to handle errors?
107-
if (updates.every((update) => update.type === 'Noop')) {
126+
// TODO: fix the need to clone
127+
const clonedUpdates = updates.map((u) => ({
128+
clientId: u.clientId,
129+
update: { ...u.update },
130+
}))
131+
if (clonedUpdates.every((update) => update.update.type === 'Noop')) {
108132
debug?.(`ignored file change for ${files.join(', ')}`)
109133
return
110134
}
111-
this.invalidateCalledModules.clear()
112-
for (const update of updates) {
113-
this.handleHmrOutput(files, update)
135+
// TODO: how to handle errors?
136+
for (const { clientId, update } of clonedUpdates) {
137+
this.invalidateCalledModules.get(clientId)?.clear()
138+
const client = this.clients.get(clientId)!
139+
this.handleHmrOutput(client, files, update)
114140
}
115141
},
116142
watch: {
@@ -144,13 +170,24 @@ export class FullBundleDevEnvironment extends DevEnvironment {
144170
// no-op
145171
}
146172

147-
protected override invalidateModule(m: {
148-
path: string
149-
message?: string
150-
firstInvalidatedBy: string
151-
}): void {
173+
protected override invalidateModule(_m: unknown): void {
174+
// no-op, handled via `server.ws` instead
175+
}
176+
177+
private handleInvalidateModule(
178+
client: WebSocketClient,
179+
m: {
180+
path: string
181+
message?: string
182+
firstInvalidatedBy: string
183+
},
184+
): void {
152185
;(async () => {
153-
if (this.invalidateCalledModules.has(m.path)) {
186+
const clientId = this.clients.getId(client)
187+
if (!clientId) return
188+
189+
const invalidateCalledModules = this.invalidateCalledModules.get(clientId)
190+
if (invalidateCalledModules?.has(m.path)) {
154191
debug?.(
155192
`INVALIDATE: invalidate received from ${m.path}, but ignored because it was already invalidated`,
156193
)
@@ -160,13 +197,18 @@ export class FullBundleDevEnvironment extends DevEnvironment {
160197
debug?.(
161198
`INVALIDATE: invalidate received from ${m.path}, re-triggering HMR`,
162199
)
163-
this.invalidateCalledModules.add(m.path)
200+
if (!invalidateCalledModules) {
201+
this.invalidateCalledModules.set(clientId, new Set([]))
202+
}
203+
this.invalidateCalledModules.get(clientId)!.add(m.path)
164204

165205
// TODO: how to handle errors?
166-
const update = await this.devEngine.invalidate(
206+
const _update = await this.devEngine.invalidate(
167207
m.path,
168208
m.firstInvalidatedBy,
169209
)
210+
const update = _update.find((u) => u.clientId === clientId)?.update
211+
if (!update) return
170212

171213
if (update.type === 'Patch') {
172214
this.logger.info(
@@ -178,7 +220,7 @@ export class FullBundleDevEnvironment extends DevEnvironment {
178220
}
179221

180222
// TODO: need to check if this is enough
181-
this.handleHmrOutput([m.path], update, {
223+
this.handleHmrOutput(client, [m.path], update, {
182224
firstInvalidatedBy: m.firstInvalidatedBy,
183225
})
184226
})()
@@ -265,6 +307,7 @@ export class FullBundleDevEnvironment extends DevEnvironment {
265307
}
266308

267309
private handleHmrOutput(
310+
client: WebSocketClient,
268311
files: string[],
269312
hmrOutput: HmrOutput,
270313
invalidateInformation?: { firstInvalidatedBy: string },
@@ -307,7 +350,7 @@ export class FullBundleDevEnvironment extends DevEnvironment {
307350
timestamp: Date.now(),
308351
}
309352
})
310-
this.hot.send({
353+
client.send({
311354
type: 'update',
312355
updates,
313356
})
@@ -319,6 +362,42 @@ export class FullBundleDevEnvironment extends DevEnvironment {
319362
}
320363
}
321364

365+
class Clients {
366+
private clientToId = new Map<WebSocketClient, string>()
367+
private idToClient = new Map<string, WebSocketClient>()
368+
369+
setupIfNeeded(client: WebSocketClient, onClose?: () => void): string {
370+
const id = this.clientToId.get(client)
371+
if (id) return id
372+
373+
const newId = randomUUID()
374+
this.clientToId.set(client, newId)
375+
this.idToClient.set(newId, client)
376+
client.socket.once('close', () => {
377+
this.clientToId.delete(client)
378+
this.idToClient.delete(newId)
379+
onClose?.()
380+
})
381+
return newId
382+
}
383+
384+
get(id: string): WebSocketClient | undefined {
385+
return this.idToClient.get(id)
386+
}
387+
388+
getId(client: WebSocketClient): string | undefined {
389+
return this.clientToId.get(client)
390+
}
391+
392+
delete(client: WebSocketClient): void {
393+
const id = this.clientToId.get(client)
394+
if (id) {
395+
this.clientToId.delete(client)
396+
this.idToClient.delete(id)
397+
}
398+
}
399+
}
400+
322401
function debounce(time: number, cb: () => void) {
323402
let timer: ReturnType<typeof setTimeout> | null
324403
return () => {

packages/vite/types/customEvent.d.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@ export interface CustomEventMap {
1414
'vite:invalidate': InvalidatePayload
1515
'vite:ws:connect': WebSocketConnectionPayload
1616
'vite:ws:disconnect': WebSocketConnectionPayload
17+
// TODO: polish this
18+
'vite:module-loaded': { modules: string[] }
1719
}
1820

1921
export interface WebSocketConnectionPayload {

0 commit comments

Comments
 (0)