Skip to content

Commit 5c97c22

Browse files
authored
feat(client): ping from SharedWorker (vitejs#19057)
1 parent 744582d commit 5c97c22

File tree

1 file changed

+131
-19
lines changed

1 file changed

+131
-19
lines changed

packages/vite/src/client/client.ts

Lines changed: 131 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -323,7 +323,123 @@ function hasErrorOverlay() {
323323
return document.querySelectorAll(overlayId).length
324324
}
325325

326-
async function waitForSuccessfulPing(socketUrl: string, ms = 1000) {
326+
function waitForSuccessfulPing(socketUrl: string) {
327+
if (typeof SharedWorker === 'undefined') {
328+
const visibilityManager: VisibilityManager = {
329+
currentState: document.visibilityState,
330+
listeners: new Set(),
331+
}
332+
const onVisibilityChange = () => {
333+
visibilityManager.currentState = document.visibilityState
334+
for (const listener of visibilityManager.listeners) {
335+
listener(visibilityManager.currentState)
336+
}
337+
}
338+
document.addEventListener('visibilitychange', onVisibilityChange)
339+
return waitForSuccessfulPingInternal(socketUrl, visibilityManager)
340+
}
341+
342+
// needs to be inlined to
343+
// - load the worker after the server is closed
344+
// - make it work with backend integrations
345+
const blob = new Blob(
346+
[
347+
'"use strict";',
348+
`const waitForSuccessfulPingInternal = ${waitForSuccessfulPingInternal.toString()};`,
349+
`const fn = ${pingWorkerContentMain.toString()};`,
350+
`fn(${JSON.stringify(socketUrl)})`,
351+
],
352+
{ type: 'application/javascript' },
353+
)
354+
const objURL = URL.createObjectURL(blob)
355+
const sharedWorker = new SharedWorker(objURL)
356+
return new Promise<void>((resolve, reject) => {
357+
const onVisibilityChange = () => {
358+
sharedWorker.port.postMessage({ visibility: document.visibilityState })
359+
}
360+
document.addEventListener('visibilitychange', onVisibilityChange)
361+
362+
sharedWorker.port.addEventListener('message', (event) => {
363+
document.removeEventListener('visibilitychange', onVisibilityChange)
364+
sharedWorker.port.close()
365+
366+
const data: { type: 'success' } | { type: 'error'; error: unknown } =
367+
event.data
368+
if (data.type === 'error') {
369+
reject(data.error)
370+
return
371+
}
372+
resolve()
373+
})
374+
375+
onVisibilityChange()
376+
sharedWorker.port.start()
377+
})
378+
}
379+
380+
type VisibilityManager = {
381+
currentState: DocumentVisibilityState
382+
listeners: Set<(newVisibility: DocumentVisibilityState) => void>
383+
}
384+
385+
function pingWorkerContentMain(socketUrl: string) {
386+
self.addEventListener('connect', (_event) => {
387+
const event = _event as MessageEvent
388+
const port = event.ports[0]
389+
390+
if (!socketUrl) {
391+
port.postMessage({
392+
type: 'error',
393+
error: new Error('socketUrl not found'),
394+
})
395+
return
396+
}
397+
398+
const visibilityManager: VisibilityManager = {
399+
currentState: 'visible',
400+
listeners: new Set(),
401+
}
402+
port.addEventListener('message', (event) => {
403+
const { visibility } = event.data
404+
visibilityManager.currentState = visibility
405+
console.debug('new window visibility', visibility)
406+
for (const listener of visibilityManager.listeners) {
407+
listener(visibility)
408+
}
409+
})
410+
port.start()
411+
412+
console.debug('connected from window')
413+
waitForSuccessfulPingInternal(socketUrl, visibilityManager).then(
414+
() => {
415+
console.debug('ping successful')
416+
try {
417+
port.postMessage({ type: 'success' })
418+
} catch (error) {
419+
port.postMessage({ type: 'error', error })
420+
}
421+
},
422+
(error) => {
423+
console.debug('error happened', error)
424+
try {
425+
port.postMessage({ type: 'error', error })
426+
} catch (error) {
427+
port.postMessage({ type: 'error', error })
428+
}
429+
},
430+
)
431+
})
432+
}
433+
434+
async function waitForSuccessfulPingInternal(
435+
socketUrl: string,
436+
visibilityManager: VisibilityManager,
437+
ms = 1000,
438+
) {
439+
function wait(ms: number) {
440+
return new Promise((resolve) => setTimeout(resolve, ms))
441+
}
442+
327443
async function ping() {
328444
const socket = new WebSocket(socketUrl, 'vite-ping')
329445
return new Promise<boolean>((resolve) => {
@@ -345,39 +461,35 @@ async function waitForSuccessfulPing(socketUrl: string, ms = 1000) {
345461
})
346462
}
347463

464+
function waitForWindowShow(visibilityManager: VisibilityManager) {
465+
return new Promise<void>((resolve) => {
466+
const onChange = (newVisibility: DocumentVisibilityState) => {
467+
if (newVisibility === 'visible') {
468+
resolve()
469+
visibilityManager.listeners.delete(onChange)
470+
}
471+
}
472+
visibilityManager.listeners.add(onChange)
473+
})
474+
}
475+
348476
if (await ping()) {
349477
return
350478
}
351479
await wait(ms)
352480

353481
while (true) {
354-
if (document.visibilityState === 'visible') {
482+
if (visibilityManager.currentState === 'visible') {
355483
if (await ping()) {
356484
break
357485
}
358486
await wait(ms)
359487
} else {
360-
await waitForWindowShow()
488+
await waitForWindowShow(visibilityManager)
361489
}
362490
}
363491
}
364492

365-
function wait(ms: number) {
366-
return new Promise((resolve) => setTimeout(resolve, ms))
367-
}
368-
369-
function waitForWindowShow() {
370-
return new Promise<void>((resolve) => {
371-
const onChange = async () => {
372-
if (document.visibilityState === 'visible') {
373-
resolve()
374-
document.removeEventListener('visibilitychange', onChange)
375-
}
376-
}
377-
document.addEventListener('visibilitychange', onChange)
378-
})
379-
}
380-
381493
const sheetsMap = new Map<string, HTMLStyleElement>()
382494

383495
// collect existing style elements that may have been inserted during SSR

0 commit comments

Comments
 (0)