Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
150 changes: 131 additions & 19 deletions packages/vite/src/client/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -323,7 +323,123 @@ function hasErrorOverlay() {
return document.querySelectorAll(overlayId).length
}

async function waitForSuccessfulPing(socketUrl: string, ms = 1000) {
function waitForSuccessfulPing(socketUrl: string) {
if (typeof SharedWorker === 'undefined') {
const visibilityManager: VisibilityManager = {
currentState: document.visibilityState,
listeners: new Set(),
}
const onVisibilityChange = () => {
visibilityManager.currentState = document.visibilityState
for (const listener of visibilityManager.listeners) {
listener(visibilityManager.currentState)
}
}
document.addEventListener('visibilitychange', onVisibilityChange)
return waitForSuccessfulPingInternal(socketUrl, visibilityManager)
}

// needs to be inlined to
// - load the worker after the server is closed
// - make it work with backend integrations
const blob = new Blob(
[
'"use strict";',
`const waitForSuccessfulPingInternal = ${waitForSuccessfulPingInternal.toString()};`,
`const fn = ${pingWorkerContentMain.toString()};`,
`fn(${JSON.stringify(socketUrl)})`,
],
{ type: 'application/javascript' },
)
const objURL = URL.createObjectURL(blob)
const sharedWorker = new SharedWorker(objURL)
return new Promise<void>((resolve, reject) => {
const onVisibilityChange = () => {
sharedWorker.port.postMessage({ visibility: document.visibilityState })
}
document.addEventListener('visibilitychange', onVisibilityChange)

sharedWorker.port.addEventListener('message', (event) => {
document.removeEventListener('visibilitychange', onVisibilityChange)
sharedWorker.port.close()

const data: { type: 'success' } | { type: 'error'; error: unknown } =
event.data
if (data.type === 'error') {
reject(data.error)
return
}
resolve()
})

onVisibilityChange()
sharedWorker.port.start()
})
}

type VisibilityManager = {
currentState: DocumentVisibilityState
listeners: Set<(newVisibility: DocumentVisibilityState) => void>
}

function pingWorkerContentMain(socketUrl: string) {
self.addEventListener('connect', (_event) => {
const event = _event as MessageEvent
const port = event.ports[0]

if (!socketUrl) {
port.postMessage({
type: 'error',
error: new Error('socketUrl not found'),
})
return
}

const visibilityManager: VisibilityManager = {
currentState: 'visible',
listeners: new Set(),
}
port.addEventListener('message', (event) => {
const { visibility } = event.data
visibilityManager.currentState = visibility
console.debug('new window visibility', visibility)
for (const listener of visibilityManager.listeners) {
listener(visibility)
}
})
port.start()

console.debug('connected from window')
waitForSuccessfulPingInternal(socketUrl, visibilityManager).then(
() => {
console.debug('ping successful')
try {
port.postMessage({ type: 'success' })
} catch (error) {
port.postMessage({ type: 'error', error })
}
},
(error) => {
console.debug('error happened', error)
try {
port.postMessage({ type: 'error', error })
} catch (error) {
port.postMessage({ type: 'error', error })
}
},
)
})
}

async function waitForSuccessfulPingInternal(
socketUrl: string,
visibilityManager: VisibilityManager,
ms = 1000,
) {
function wait(ms: number) {
return new Promise((resolve) => setTimeout(resolve, ms))
}

async function ping() {
const socket = new WebSocket(socketUrl, 'vite-ping')
return new Promise<boolean>((resolve) => {
Expand All @@ -345,39 +461,35 @@ async function waitForSuccessfulPing(socketUrl: string, ms = 1000) {
})
}

function waitForWindowShow(visibilityManager: VisibilityManager) {
return new Promise<void>((resolve) => {
const onChange = (newVisibility: DocumentVisibilityState) => {
if (newVisibility === 'visible') {
resolve()
visibilityManager.listeners.delete(onChange)
}
}
visibilityManager.listeners.add(onChange)
})
}

if (await ping()) {
return
}
await wait(ms)

while (true) {
if (document.visibilityState === 'visible') {
if (visibilityManager.currentState === 'visible') {
if (await ping()) {
break
}
await wait(ms)
} else {
await waitForWindowShow()
await waitForWindowShow(visibilityManager)
}
}
}

function wait(ms: number) {
return new Promise((resolve) => setTimeout(resolve, ms))
}

function waitForWindowShow() {
return new Promise<void>((resolve) => {
const onChange = async () => {
if (document.visibilityState === 'visible') {
resolve()
document.removeEventListener('visibilitychange', onChange)
}
}
document.addEventListener('visibilitychange', onChange)
})
}

const sheetsMap = new Map<string, HTMLStyleElement>()

// collect existing style elements that may have been inserted during SSR
Expand Down
Loading