perf(code): replace react-syntax-highlighter and prismjs with the Shiki worker
Route all non-markdown code highlighting through the off-main-thread Shiki worker, removing react-syntax-highlighter and prismjs entirely. - Extend the worker with highlightLines: tokenize a whole block once and return per-line inner HTML, so per-line layouts (diffs, gutters, virtualization) make one worker call instead of one highlighter per line. - Add shared WorkerHighlightedCode (whole-block) and useWorkerHighlightedLines (per-line) primitives. Colors resolve via the --md-syntax-* CSS variables, so theme changes never re-highlight. - Migrate all 12 react-syntax-highlighter call sites: PermissionCard, ToolPart, ContextSidebarTab, ToolOutputDialog (whole block) and DiffPreview/WritePreview (per line). - Migrate VirtualizedCodeBlock off prismjs to the worker, keeping virtua virtualization; whole-block tokenization also restores cross-line syntax context that per-line highlighting lost. - Drop react-syntax-highlighter (+types) from ui and web, prismjs (+types) from ui, and the orphaned create-element type shim.
This commit is contained in:
@@ -7,9 +7,11 @@ import type { MarkdownWorkerRequest, MarkdownWorkerResponse } from './markdown-w
|
||||
// tokenization error) the promise resolves to `null` and the caller keeps the
|
||||
// escaped plain-text code — highlighting never falls back onto the main thread.
|
||||
|
||||
type PendingResolver = (response: MarkdownWorkerResponse | null) => void;
|
||||
|
||||
let worker: Worker | undefined;
|
||||
let nextId = 0;
|
||||
const pending = new Map<number, (html: string | null) => void>();
|
||||
const pending = new Map<number, PendingResolver>();
|
||||
|
||||
const failAll = (): void => {
|
||||
pending.forEach((resolve) => resolve(null));
|
||||
@@ -30,7 +32,7 @@ const getWorker = (): Worker | undefined => {
|
||||
const resolve = pending.get(event.data.id);
|
||||
if (!resolve) return;
|
||||
pending.delete(event.data.id);
|
||||
resolve(event.data.type === 'highlight' ? event.data.html : null);
|
||||
resolve(event.data);
|
||||
};
|
||||
worker.onerror = failAll;
|
||||
worker.onmessageerror = failAll;
|
||||
@@ -38,16 +40,31 @@ const getWorker = (): Worker | undefined => {
|
||||
return worker;
|
||||
};
|
||||
|
||||
const request = (payload: (id: number) => MarkdownWorkerRequest): Promise<MarkdownWorkerResponse | null> => {
|
||||
const instance = getWorker();
|
||||
if (!instance) return Promise.resolve(null);
|
||||
const id = ++nextId;
|
||||
return new Promise<MarkdownWorkerResponse | null>((resolve) => {
|
||||
pending.set(id, resolve);
|
||||
instance.postMessage(payload(id));
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Highlight a complete code block in the worker. Resolves to Shiki `<pre>` HTML,
|
||||
* or `null` if highlighting is unavailable or failed (caller keeps plain code).
|
||||
*/
|
||||
export const highlightCodeInWorker = (code: string, lang: string): Promise<string | null> => {
|
||||
const instance = getWorker();
|
||||
if (!instance) return Promise.resolve(null);
|
||||
const id = ++nextId;
|
||||
return new Promise<string | null>((resolve) => {
|
||||
pending.set(id, resolve);
|
||||
instance.postMessage({ type: 'highlight', id, code, lang } satisfies MarkdownWorkerRequest);
|
||||
});
|
||||
export const highlightCodeInWorker = async (code: string, lang: string): Promise<string | null> => {
|
||||
const response = await request((id) => ({ type: 'highlight', id, code, lang }));
|
||||
return response?.type === 'highlight' ? response.html : null;
|
||||
};
|
||||
|
||||
/**
|
||||
* Highlight a whole block and return per-line inner HTML (one entry per source
|
||||
* line). For per-line layouts (diffs, gutters, virtualization) — one worker
|
||||
* round-trip instead of one per line. Resolves to `null` on failure.
|
||||
*/
|
||||
export const highlightLinesInWorker = async (code: string, lang: string): Promise<string[] | null> => {
|
||||
const response = await request((id) => ({ type: 'highlightLines', id, code, lang }));
|
||||
return response?.type === 'highlightLines' ? response.lines : null;
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user