Created
July 15, 2024 13:32
-
-
Save GrandSchtroumpf/cf06e37f9d8d73988a1e48d187599cc4 to your computer and use it in GitHub Desktop.
Qwik web worker
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import { component$, useSignal, $ } from "@builder.io/qwik"; | |
import { useWebWorker } from "./use-web-worker"; | |
export default component$(() => { | |
const count = useSignal(0); | |
const { postMessage } = useWebWorker<string>({ | |
track: [count], | |
// This code run in a web worker | |
worker$: $(({ postMessage, onmessage }) => { | |
// Receive message from the main thread | |
onmessage((content) => { | |
console.log('Received message', content, 'with counter', count.value); | |
postMessage('Hello from worker'); // <-- Send message to the main thread | |
}); | |
// manage running task | |
const i = setInterval(() => console.log('interval', count.value), 1000); | |
return () => clearInterval(i); | |
}), | |
// React to message from worker | |
onMessage$: $((message) => { | |
console.log('message from the worker', message); | |
}), | |
}); | |
return <> | |
{/* Send message to worker */} | |
<button onClick$={() => postMessage('From main thread')}>Post Message</button> | |
<button onClick$={() => count.value++}>Counter</button> | |
</> | |
}) |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import { OnVisibleTaskOptions, QRL, Signal, useTask$, useVisibleTask$ } from "@builder.io/qwik"; | |
import { QwikWorker, webWorkerQrl } from "./web-worker"; | |
interface OnWebWorkerOptions<T> extends OnVisibleTaskOptions { | |
track?: Signal[], | |
onMessage$?: QRL<(args: T) => any>; | |
worker$: QRL<(args: QwikWorker<T>) => any>; | |
} | |
export const useWebWorker = <T = unknown>(opt: OnWebWorkerOptions<T>) => { | |
const { | |
track = [], | |
onMessage$, | |
worker$, | |
...visibleOptions | |
} = opt; | |
const worker = webWorkerQrl(worker$); | |
// Terminate worker when component is closed | |
useTask$(() => worker.terminate); | |
useVisibleTask$(async (task) => { | |
track.forEach(task.track); | |
worker.close(); | |
worker.create(); | |
if (onMessage$) return worker.onMessage$(onMessage$); | |
}, visibleOptions); | |
return worker; | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import { | |
$, | |
implicit$FirstArg, | |
type QRL, | |
_getContextElement, | |
_serializeData, | |
} from '@builder.io/qwik'; | |
//@ts-ignore | |
import workerUrl from './web-worker.worker.ts?worker&url'; | |
const qwikWorkers = new Map<string, Worker>(); | |
let workerRequests = 0; | |
const getWorkerRequest = () => ++workerRequests; | |
const getWorker = (qrl: QRL) => { | |
let worker = qwikWorkers.get(qrl.getHash()); | |
if (!worker) { | |
qwikWorkers.set( | |
qrl.getHash(), | |
(worker = new Worker(workerUrl, { | |
name: `worker$(${qrl.getSymbol()})`, | |
type: 'module', | |
})) | |
); | |
} | |
return worker; | |
}; | |
type Callback<T> = (args: T) => any | QRL<(args: T) => any>; | |
export interface QwikWorker<Message> { | |
onmessage: (cb: Callback<Message>) => any; | |
onclose: (cb: Callback<void>) => any; | |
postMessage: QRL<(data: Message) => any>; | |
} | |
export const webWorkerQrl = <T = unknown>(qrl: QRL<(this: any, props: QwikWorker<T>) => any>) => { | |
const sendMessage = $((type: 'init' | 'close' | 'message', data?: string) => { | |
const worker = getWorker(qrl); | |
const ctxElement = (_getContextElement() as HTMLElement | undefined); | |
const containerEl = ctxElement?.closest('[q\\:container]') ?? document.documentElement; | |
const qbase = containerEl.getAttribute('q:base') ?? '/'; | |
const baseURI = document.baseURI; | |
const requestId = getWorkerRequest(); | |
worker.postMessage([type, requestId, baseURI, qbase, data]); | |
}) | |
const create = $(async () => { | |
const data = await _serializeData([qrl], false); | |
sendMessage('init', data); | |
}); | |
/** Post message to web worker instance */ | |
const postMessage = $(async (...args: any[]) => { | |
const data = await _serializeData(args, false); | |
sendMessage('message', data); | |
}); | |
/** Close current process be do not use */ | |
const close = $(() => sendMessage('close')) | |
/** Register callback when worker post message to main thread */ | |
const onMessage$ = $((cb: (...args: any[]) => any) => { | |
const worker = getWorker(qrl); | |
const handler = ({ data }: MessageEvent) => { | |
if (!Array.isArray(data)) return; | |
const [type, ...args] = data; | |
if (type !== 'onmessage') return; | |
cb(...args); | |
}; | |
worker.addEventListener('message', handler); | |
return () => worker.removeEventListener('message', handler); | |
}); | |
/** Terminate the worker process */ | |
const terminate = $(() => { | |
qwikWorkers.get(qrl.getHash())?.terminate(); | |
qwikWorkers.delete(qrl.getHash()); | |
}); | |
return { | |
create, | |
onMessage$, | |
postMessage, | |
close, | |
terminate | |
}; | |
}; | |
export const webWorker$ = implicit$FirstArg(webWorkerQrl); |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import { $, _deserializeData } from '@builder.io/qwik'; | |
import { QwikWorker } from './web-worker'; | |
globalThis.document = { | |
nodeType: 9, | |
ownerDocument: null, | |
dispatchEvent() { | |
return true; | |
}, | |
createElement() { | |
return { | |
nodeType: 1, | |
} as any; | |
}, | |
} as any; | |
const msgCallbacks = new Set<(...args: any[]) => any>(); | |
const closeCallbacks = new Set<() => any>(); | |
const props: QwikWorker<unknown> = { | |
// Marking it as QRL rerender the worker for some reason | |
onmessage: (cb) => msgCallbacks.add(cb), | |
onclose: (cb) => msgCallbacks.add(cb), | |
postMessage: $((...args: any[]) => self.postMessage(['onmessage', ...args])), | |
} | |
globalThis.onmessage = async ({ data }) => { | |
const [type, requestId, baseURI, qBase, params] = data; | |
const containerEl = { | |
nodeType: 1, | |
ownerDocument: { | |
baseURI, | |
}, | |
closest() { | |
return containerEl; | |
}, | |
getAttribute(name: string) { | |
return name === 'q:base' ? qBase : undefined; | |
}, | |
}; | |
try { | |
if (type === 'init') { | |
const [qrl] = _deserializeData(params, containerEl); | |
const output = await qrl.apply(self, [props]); | |
if (typeof output === 'function') closeCallbacks.add(output); | |
self.postMessage([type, requestId, true]); | |
} | |
if (type === 'message') { | |
const args = _deserializeData(params, containerEl); | |
msgCallbacks.forEach(cb => cb(args)); | |
self.postMessage([type, requestId, true]); | |
} | |
if (type === 'close') { | |
closeCallbacks.forEach(cb => cb()); | |
msgCallbacks.clear(); | |
closeCallbacks.clear(); | |
} | |
} catch (err) { | |
console.error(err); | |
self.postMessage([requestId, false, err]); | |
return; | |
} | |
}; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment