theatre/packages/playground/devEnv/createEsbuildLiveReloadTools.ts

75 lines
2.4 KiB
TypeScript

import type esbuild from 'esbuild'
import type {IncomingMessage, ServerResponse} from 'http'
export function createEsbuildLiveReloadTools(): {
handleRequest(req: IncomingMessage, res: ServerResponse): boolean
hasOpenConnections(): boolean
esbuildBanner: esbuild.BuildOptions['banner']
esbuildWatch: esbuild.WatchMode
} {
const openResponses = new Set<ServerResponse>()
return {
handleRequest(req, res) {
// If special /esbuild url requested, subscribe clients to changes
if (req.url === '/esbuild') {
res.writeHead(200, {
'Content-Type': 'text/event-stream',
'Cache-Control': 'no-cache',
Connection: 'keep-alive',
})
res.write('data: open\n\n')
openResponses.add(res)
res.on('close', () => openResponses.delete(res))
return true // handled
}
return false
},
hasOpenConnections() {
return openResponses.size > 0
},
esbuildBanner: {
// Below uses function toString to insert raw source code of the function into the JS source.
// This is being used so we can at least get a few type completions, but please understand that
// you cannot reference any non-global browser values from within the function.
js: `;(${function liveReloadClientSetup() {
// from packages/playground/devEnv/createEsbuildLiveReloadTools.ts
function connect() {
try {
const es = new EventSource('/esbuild')
es.onmessage = (evt) => {
switch (evt.data) {
case 'reload':
location.reload()
break
case 'open':
console.log('%cLive reload ready', 'color: gray')
break
}
}
es.onerror = attemptConnect
} catch (err) {
attemptConnect()
}
}
function attemptConnect() {
setTimeout(() => connect(), 1000)
}
attemptConnect()
}.toString()})();`,
},
esbuildWatch: {
onRebuild(error, res) {
if (!error) {
if (openResponses.size > 0) {
console.error(`Reloading for ${openResponses.size} clients...`)
// Notify clients on rebuild
openResponses.forEach((res) => res.write('data: reload\n\n'))
openResponses.clear()
}
} else {
console.error('Rebuild had errors...')
}
},
},
}
}