UNPKG

@mui/internal-docs-infra

Version:

MUI Infra - internal documentation creation tools.

125 lines (124 loc) 4.12 kB
/** * Worker entry point for off-main-thread syntax highlighting. * * Lifecycle: * 1. Main thread posts `{ type: 'init', grammars }` once. The worker calls * `createStarryNight(grammars)` and primes the singleton used by * `parseSource`. Posts `{ type: 'init-ack' }` when ready. * 2. For each parse request, main thread posts * `{ type: 'parse', id, source, fileName, language? }`. The worker runs * the same sync `parseSource` body that the main thread uses (so the HAST * output is byte-identical) and posts `{ type: 'parse', id, ok, hast? , error? }`. * * Parse requests that arrive before init completes are queued and drained on * `init-ack`. The worker does not import `./grammars` statically — the heavy * grammar JSON payload travels across the boundary exactly once via the init * message so that the (lazy) main-thread grammar chunk is the single shared * source of truth. */ import { createStarryNight } from '@wooorm/starry-night'; import { parseSource } from "../pipeline/parseSource/parseSource.mjs"; const STARRY_NIGHT_KEY = '__docs_infra_starry_night_instance__'; // `self` in a dedicated worker exposes `postMessage`/`addEventListener` on the // global scope, but the default `tsconfig` `lib` here doesn't include the // `webworker` lib so we cast once via a typed alias instead of per-call. const workerScope = self; const queue = []; let initialized = false; let initInFlight = null; async function init(grammars) { if (initInFlight) { return initInFlight; } initInFlight = (async () => { try { const starryNight = await createStarryNight(grammars); globalThis[STARRY_NIGHT_KEY] = starryNight; initialized = true; // Drain queued parse requests in arrival order. for (const req of queue.splice(0)) { runParse(req); } workerScope.postMessage({ type: 'init-ack' }); } catch (error) { // Surface init failures to the client so `parseSourceAsync` can fail // fast instead of hanging on an unresolved init promise. const message = error instanceof Error ? error.message : String(error); workerScope.postMessage({ type: 'init-error', error: message }); throw error; } })(); return initInFlight; } async function registerGrammars(grammars) { // Wait for init to finish (the client always inits before registering), then // add the new grammars to the existing instance. if (initInFlight) { await initInFlight; } try { const starryNight = globalThis[STARRY_NIGHT_KEY]; if (!starryNight) { workerScope.postMessage({ type: 'register-error', error: 'worker not initialized' }); return; } await starryNight.register(grammars); workerScope.postMessage({ type: 'register-ack' }); } catch (error) { const message = error instanceof Error ? error.message : String(error); workerScope.postMessage({ type: 'register-error', error: message }); } } function runParse(req) { try { const hast = parseSource(req.source, req.fileName, req.language); workerScope.postMessage({ type: 'parse', id: req.id, ok: true, hast }); } catch (error) { workerScope.postMessage({ type: 'parse', id: req.id, ok: false, error: error instanceof Error ? error.message : String(error) }); } } workerScope.addEventListener('message', event => { const data = event.data; if (data.type === 'init') { // The promise rejection is observed by the client via `init-error`; the // local `.catch` here just prevents an unhandled rejection in the worker. init(data.grammars).catch(() => {}); return; } if (data.type === 'register') { // Failures are reported to the client via `register-error`; the `.catch` // just prevents an unhandled rejection in the worker. registerGrammars(data.grammars).catch(() => {}); return; } if (data.type === 'parse') { if (initialized) { runParse(data); } else { queue.push(data); } } });